04 Windows批處理中的條件執行

公子奇的博客發表於2024-09-04

if 命令在所有程式語言中都很常見,它只會在條件為真時執行一行或多行程式碼,而只有在條件為假時才會執行另一段程式碼。

其基本原理很簡單,但是在批處理中,條件子句為true或false的實體與其他語言中的類似子句有很大不同。大多數比較運算子都是批處理所獨有的,在本文中,我們將學習確定路徑或檔案是否存在以及是否填充變數的語法。理解計算返回程式碼的不同技術也很重要。

此外,我們還將瞭解如何有效地管理需要評估多個條件的例項,以及需要避免的一些常見問題。編寫一個 if 命令非常容易,它在大多數情況下都能正常工作,但在某些條件下要麼中止,要麼無法按預期執行。

if 命令的基礎

在其最基本的形式中,如果條件為真,if 命令執行一行或多行程式碼。我將演示如何使用該命令,以便在相同條件為假的情況下執行不同的程式碼,不過首先讓我們從它的基本結構開始。

幾乎每個批處理命令實現都以命令名本身開頭,後面通常跟著引數和/或選項。例如,set命令總是以這三個字母開頭。通常,它後面跟一個由變數名、等號和值組成的引數,但在前面的文章中,我們瞭解到它在沒有引數或選項的情況下也可以工作。(這樣的命令會輸出一個可用變數的列表。)

if 命令是特殊的。它也是以它的命令名開始,但相似之處僅限於此;它可以跨行,有兩個主要組成部分。下面是其語法結構的一般形式:

if 條件 (
	條件為true時執行的程式碼塊
)

條件是一個計算結果為true(真)false(假)的表示式。如果它為true,直譯器執行true程式碼塊中的一條命令或多條命令,如果它為false,則不執行該程式碼。關於程式碼塊的內容我們後續再深入的討論,但是現在,我們只需瞭解程式碼塊是括號之間的一行或多行程式碼。

使用這種語法,左括號不僅必須跟在條件後面,同時它們也必須在同一行上。其他語言允許您將左括號放在下一行,並將其與右括號對齊,但這在批處理中是禁止的。但是,良好的格式要求右括號應與 if 命令的開頭對齊,中間的命令應縮排。我的習慣是要求縮排三個空格,但其實任何數量的空格都可以。這裡是一個例子:

if "%today%" equ "2024-09-01" (
   set event=sestercentennial
)

條件子句"%today%" equ "2024-09-01"正在查詢已解析變數和一些硬編碼文字之間的相等性。這個條件子句相當簡單,但我們很快就會使用不同的比較運算子、關鍵字甚至一個選項來演示更令人印象深刻的條件子句。許多程式語言將條件子句放在括號內;無論好壞,該子句在批處理中獨立存在,括號用於括住即將執行的程式碼塊。

如果條件子句為真,則執行程式碼塊中的命令(在本例中為 set 命令),從而將 event 設定為 sestercentennial。

以下使用更緊湊的單行格式,在功能上等同於前面的示例:

if "%today%" equ "2024-09-01"  (set event=sestercentennial)

當使用單行時,可以不再需要括號。下面的程式碼在功能上等同於前面的兩個例子:

if "%today%" equ "2024-09-01"  set event=sestercentennial

從技術上講,由於缺少括號,set 命令不再位於程式碼塊中。現在,它不再是一個簡潔的術語,而是當條件子句為真時執行的命令。

在前面的兩個例子中,我在條件子句後面留了兩個空格,而且我經常留兩個以上的空格。從語法上講,這是不需要的,但是因為沒有什麼東西能夠清楚地描述條件子句和它後面的東西,所以稍微分開一點可以增強可讀性。

你甚至可以用&命令分隔符在一行上執行多個命令,如前面文章所述:

if "%today%" equ "2024-09-01"  set event=sestercentennial& set code=ugly

如果條件子句為真,則執行一個附加的set命令。我偶爾看到使用這種技術,通常是在設定錯誤程式碼和錯誤訊息時,但是在這種情況下,使用單行只會使程式碼難以理解。

如果在執行的邏輯中有任何有意思的內容,請使用多行程式碼:

if "%today%" equ "2024-09-01" (
   set event=sestercentennial
   set code=elegant
)

在這裡說這段程式碼很好倒不一定,但我推薦您使用該方式使程式碼更具可讀性。讀者只需看一眼就知道,如果條件為真,則設定了兩個變數。

條件子句

前一節中的示例都使用了簡單的條件子句,但該子句可以更加動態,採用許多其他形式並使用不同的運算子和關鍵字。

比較運算子

比較運算子比較兩個運算元是否相等或其中一個大於另一個。您可能已經猜到前面示例中的equ運算子表示相等。下面是批處理比較運算子的完整列表:

  • equ 或者 == 相等
  • neq 不相等
  • lss 小於
  • leq 小於或等於
  • gtr 大於
  • geq 大於或等於

對於相等運算子,您可以在兩個功能等效的備選方案之間進行選擇。為了區別於用於賦值的單等號,例如在set命令中,批處理使用雙等號作為比較運算子。我更喜歡的語法是equ運算子,因為它看起來與其他運算子相似,但有些編碼人員出於相反的原因更喜歡==運算子。

如果比較的兩個運算元不相等,則neq運算子將條件子句計算值為true。最後四個運算子確定哪個運算元大於或小於另一個運算元。例如,假設age被設定為一個數值,只有當變數被設定為大於12的值時,下面的一行程式碼塊才會執行:

if %age% gtr 12 (
   rem 這裡執行年齡大於12的程式碼塊
   > con echo You are greater than 12 years old.
)

你可能想嘗試%age% > 12作為條件子句,但是大於號在批處理中已經有了明確的用途;實際上,它在這個程式碼塊中用於向控制檯寫入一條訊息。因此,必須使用gtr作為運算子。類似地,本節中列出的運算子用於大於等於、小於和小於等於比較。

不那麼直觀的是,這些運算子也適用於字母數字值。所有的數字都小於所有的字母;a小於A;A小於b;b小於B;等等......。這種規則會存在各種討論,但至少在批處理的世界中,Picard 比 Kirk 更大。

條件子句關鍵字

您將在 if 命令的幫助中找到以下不可或缺的關鍵字,但請不要搞錯;這些關鍵詞是特定於條件子句的:

exist 關鍵字檢查是否存在路徑或檔案,如果找到則返回true。您可以硬編碼路徑或檔案,或者為了靈活性,您可以使用包含潛在路徑或檔案的變數:

if exist D:\Batch\myFile.txt  set do=something
if exist %pathAndFile%        set do=something

您還可以將多個變數串在一起以構建路徑或檔名。

defined 下面的條件子句使用defined關鍵字檢查變數是否已定義—也就是說,它是否解析為任何內容,甚至是一個空格?一個常見的錯誤是在變數周圍使用百分號,但以下是使用該關鍵字的正確語法:

if defined varThatMayBeEmpty  set do=something

這在功能上相當於下面用百分號解析變數:

if "%varThatMayBeEmpty%" neq ""  set do=something

這個關鍵字通常用於驗證預期的輸入變數。如果沒有一個或多個 defined,則可以採取適當的操作,可能會啟動中止。(即判斷一個或多個變數是否存在,若不存在則做一些操作或者直接中止程式的啟動)

not 關鍵字在條件子句的開頭使用時,否定任何條件子句。這在為尚未被其他人或其他事物設定的變數設定預設值時非常有用。例如,下面的程式碼確保將skyColor設定為其通常的顏色:

if not defined skyColor  set skyColor=BLUE

可以將not關鍵字與exist關鍵字相結合,以確定特定檔案是否存在。有了這些知識,您就可以建立一個檔案,啟動或中止,或者做任何對您的應用程式有意義的事情。一些編碼人員將not關鍵字與equ運算子一起使用,但我發現這其實是有問題的,我更喜歡單獨使用neq運算子。邏輯上沒有區別,但無論你的偏好是什麼,都要保持一致。

警告:

經過幾年的批處理編碼,我仍然經常嘗試在exist關鍵字後面新增s,儘管我不願意承認。notepad++每次都會忠實地提醒我,因為它增加了關鍵字;這個冒牌字元會將整個單詞的粗體去掉,使其更加突出。

不區分大小寫的選項

if 命令只有一個選項,就像我們到目前為止看到的關鍵字一樣,它適用於條件子句。/i 選項使條件子句中的相等(和不相等)運算子不區分大小寫。

為了演示,下面 if 命令中不帶選項的條件子句只有在 myMood 解析為 happy時才計算為true:

if "%myMood%" equ "happy"  set do=something

下面是新增了/i選項的相同程式碼:

if /i "%myMood%" equ "happy"  set do=something

如果變數解析為HAPPY、Happy、happy或其他可能排列中的任何一種,那麼條件子句現在的計算結果為true。

注意:

/i 選項可能看起來與我已經提到的和即將出現的其他選項有些不同。如前所述,我對批處理命令使用小寫,儘管大小寫對直譯器無關緊要。在任何情況下,選項都可以工作。儘管如此,由於選項通常只是一個正斜槓後面跟著一個字元,我通常將它們大寫以示強調。但是根據字型的不同,大寫的I通常看起來像小寫的L,所以我對/I選項的使用違背了我的個人習慣,它通常與 if 命令一起使用。是的,具有諷刺意味的是,這是與大小寫不敏感有關的選項。

errorlevel 變數

在呼叫可執行檔案或執行許多批處理命令之後,返回程式碼儲存在errorlevel偽環境變數中。更多關於偽環境變數的資訊我們將在後面的文章中進行介紹,但是現在,將errorlevel視為一個包含返回程式碼的變數,您不應該使用set命令設定它。(如果這樣做,就會破壞errorlevel變數。)errorlevel變數可以像批處理中的其他變數一樣作為 if 命令的一部分進行計算。例如,以下程式碼將返回碼大於等於1視為失敗:

if %errorlevel% geq 1  set msg=FAILURE

批處理還支援一種比較舊的語法,這種語法只適用於這個唯一的變數,其中刪除了百分號和相等運算子。下面的程式碼在功能上等同於前面的例子:

if errorlevel 1  set msg=FAILURE

乍一看,這似乎很簡單,很吸引人,因為內容被刪除了,沒有新增任何內容,但語法掩蓋了一個令人驚訝的問題。許多批處理編碼器錯誤地將此條件子句解釋為尋找返回碼與1之間的相等性。畢竟,用返回碼0正確地測試它將返回false,用返回碼1正確地測試它將返回true。但是條件子句errorlevel 1等價於%errorlevel% geq 1和%errorlevel% gtr 0。對於所有正整數,它的計算結果為真。

將此語法與not關鍵字結合使用,以實現真正不透明的內容:

if not errorlevel 0  set msg=The Return Code is NEGATIVE

這看起來不像一個條件子句,當返回碼不等於0時,它的值為真嗎?它實際上是返回碼大於等於0的負數。%errorlevel% lss 0條件子句在功能上是等效的,而且可讀性強得多。缺少比較運算子的語法的另一個問題是,通常情況下,0表示良好的返回程式碼,而所有其他程式碼,包括負值,表示存在某種問題。

neq 運算子使得該條件子句對所有非零值都為真:

if %errorlevel% neq 0  set msg=FAILURE

您可能會遇到晦澀難懂的語法,因此理解它的工作原理很重要,但更重要的是不要擴充套件它。總是使用百分號(或感嘆號)和比較運算子來計算errorlevel偽環境變數。

if...else 結構

編碼語言的不成文規則之一是if命令必須帶有else關鍵字的可能性。我前面提到的關鍵字與條件子句相關聯,但是這個關鍵字與if命令本身相關聯。這是if...else的常見結構:

if 條件子句 (
   rem 條件子句為true時執行
   > con echo true
) else (
   rem 條件子句為false時執行
   > con echo false
)

前三行和第四行開始的右括號與我們在本文開頭展示的一般形式相同。else關鍵字緊隨其後,其後是設定在第二組括號之間的false程式碼塊。這表示當條件子句計算結果為false時執行的程式碼。

這裡有一個簡單的 if...else 構造:

if %fahrenheit% gtr 70 (
   set pants=shorts
) else (
   set pants=jeans
)

如果華氏溫度的變數大於70,則pants變數被設定為shorts。否則,將pants變數設定為jeans。一個或另一個程式碼塊將始終執行。

你可以將這個結構壓縮成一行程式碼:

if %fahrenheit% gtr 70 (set pants=shorts) else (set pants=jeans)

在false程式碼塊中,程式碼兩邊的括號在技術上是可選的,但為了可讀性,應該包括它們。

除非你忽視那些將要閱讀你程式碼的人,否則一行 if…else 構造通常是不好的做法,儘管您可能會對最簡單的任務產生例外。

與其他語言不同,在批處理中,else 關鍵字不能單獨編碼在一行中;它甚至不能作為一行的開始或結束。為了清楚地劃分這兩個程式碼塊,最好將夾在左括號和右括號之間的關鍵字編碼成一行。

else if 結構

if…else 邏輯流中有兩個分支,一個表示真,一個表示假時,else結構非常好。當有兩個以上的分支時,else if 結構允許使用多個條件子句。以下程式碼有三個子句和四個分支,每個條件子句對應一個分支,當所有子句的計算結果都不為真時執行一個預設分支。

if %fahrenheit% gtr 80 (
   set pants=shorts
) else if %fahrenheit% gtr 60 (
   set pants=light khakis
) else if %fahrenheit% gtr 32 (
   set pants=jeans
) else (
   set pants=lined jeans
)

這個邏輯假設華氏溫度被設定為一個描述溫度的整數。如果它大於80度,則執行第一個set命令。如果它大於60,也就是說在61到80(包括61和80)之間,則執行第二個set命令。如果前兩個條件子句為假,並且水銀顯示在冰點以上,則執行第三個set命令。如果三個子句都為假,則溫度為32度或更低,因此第四個也是最後一個set命令分配一條非常暖和的褲子。

在第一個else關鍵字後面沒有左括號。相反,它後面跟著另一個帶有自己的條件子句的if命令,並且只有在if命令後面加上左括號。

程式碼包含另外兩個if子句,但是您可以根據需要編寫任意數量的程式碼。直譯器執行與計算結果為true的第一個條件子句對應的程式碼塊;然後,控制跳轉到整個結構的末尾,而不計算其他子句。

很多時候,如果條件子句都不為真,則需要執行最終程式碼塊——即預設程式碼塊。例如,如果程式碼未設定某個變數,則某人有可能在沒有適當著裝的情況下離開房子。最後一個else關鍵字後面沒有if命令,因此它的程式碼塊,即預設程式碼塊,在它之前的三個條件子句都不為true時執行。

在程式碼中,正在詢問fahrenheit以確定它落在四個範圍中的哪一個。這是else if條件從句的常用用法,但它們不必如此緊密地聯絡在一起。每個條件子句可以查詢完全不同的變數,或者使用前面介紹的三個關鍵字。例如,下面是對上述程式碼的重新構想,其中只更改了三個條件子句:

if /i "%season%" equ "Summer" (
   set pants=shorts
) else if exist D:\Batch\Spring.txt (
   set pants=light khakis
) else if %celsius% gtr 0 (
   set pants=jeans
) else (
   set pants=lined jeans
)

第一個條件子句執行不區分大小寫的比較,以確定已解析變數和硬編碼文字是否相等。第二個子句查詢檔案是否存在,第三個子句檢視celsius變數的值是否高於冰點。同樣,由於預設程式碼塊,此程式碼保證將變數設定為四個值之一。

增強的相等判斷方法

如果我沒有提到與這些條件子句相關的一個重要問題,那就是我的疏忽。在本文的一些示例中,我在條件子句中用雙引號把等式的兩邊都括起來,但是沒有它們,大多數情況下命令仍然可以工作。下面兩個if命令非常相似,但在功能上並不等同:

if /i "%myMood%" equ "happy"  set do=something
if /i %myMood% equ happy  set do=something

如果myMood設定為happy,則該子句的計算結果為true,如果設定為sad,則結果為false。無論哪種方式,它都適用於兩個命令。

這很好,但是現在假設變數沒有被設定,或者它被設定為null或一些空格。沒有雙引號的命令將崩潰,但它暗示了以下神秘訊息的問題(假設你沒有使用前面文章中提到的echo off命令):

happy was unexpected at this time.

D:\Batch> if /i equ happy  set do=something

這裡的第一行是錯誤訊息,後面是讓直譯器感到困惑的內容。要理解錯誤,我們必須像解釋者那樣思考。一旦它看到if命令開始行,它就會從有限的項列表中取出一個(可能不存在、不存在、不定義或/i),任何無法識別的都假定是條件子句的左側。顯然,它找到了/i。假設三個關鍵字中的一個沒有出現在後面,直譯器現在就會按照特定的順序期待三個專案:一些文字;equ、neq或==等運算子;還有一些文字。如果myMood的值幾乎是任何東西,它將解析為第一個文字欄位。然後,直譯器會很高興地找到equ運算子,並且知道它正在處理一個等式,它會將硬編碼的happy解釋為該等式的右側。這裡表示成功執行。

當變數解析為零或任意數量的空格時,這一切都會崩潰。直譯器看到if /i開始語句,所以它不希望接下來看到的是equ。關鍵字not是有意義的,但不是equ。因此,它錯誤地認為equ是可能是等式的左邊,後面是運算子。但接下來是happy文字,而運算子列表顯然不包含這個詞。正如訊息所述,編譯器不希望在這個時候看到happy。至此表示執行失敗。

幸運的是,有兩種計算變數的方法,否則可能什麼也解決不了。

前置點的方法

解決這個問題的一種常用技術是在等式的兩邊加上一個點,或者幾乎任何字元,只要它被一致地應用,這樣直譯器肯定會在等式的兩邊找到一些東西:

if /i .%myMood% equ .happy  set do=something

當變數被設定為null時,前面的點可以很好地工作,因為該命令解析為if /i . equ .happy。這個點不等於後面跟單詞的點,所以它的計算結果是false,然後繼續。如果將變數設定為happy,則該命令將被解析為if /i .happy equ .happy,並且將找到相等。則執行成功。

但我不喜歡這種方法,因為它很容易受到另一個bat的攻擊。現在想象一下,將變數設定為兩個單詞的情緒,比如煩躁地沮喪,這在許多與批處理程式碼無關的級別上並不好。再執行一次,它崩潰了:

depressed was unexpected at this time.
D:\Batch> if /i .irritably depressed equ .happy set do=something

別灰心。直譯器被嵌入的空格受騙了。別告訴任何人,但這真的沒那麼好。它認為。irritably是子句的左側,而發現depression是一個完全意想不到的運算子。執行失敗。但還有另一種方法。

雙引號的方法

兜了一圈,這把我們帶回到等式兩邊都加了雙引號的例子:

if /i "%myMood%" equ "happy"  set do=something

這裡的雙引號在等式兩邊提供了一些東西,就像點一樣,但這不是全部。

直譯器將雙引號內的所有內容視為一個實體。當包含嵌入空格的變數被解析後,直譯器看到如下:

if /i "irritably depressed" equ "happy"

儘管有嵌入的空間,直譯器將“irritably depressed”視為一個實體,或者在這種情況下是等式的左邊,右邊是“happy”。結果是批處理正確地識別出這兩個實體不相等。計算成功。

如果查詢的字母數字變數可能沒有設定,或者它可能包含嵌入的空格,那麼我幾乎總是用雙引號把子句的兩邊都括起來。但是,您可能已經注意到,在條件子句中使用%errorlevel%時,我沒有使用雙引號。該變數將始終被設定為一個數字,因此不需要引號。更重要的是,當直譯器看到沒有引號的數字比較時,它會進行數字比較,這意味著000等於0。新增引號會導致文字比較,並且“000”不等於“0”。

前置點和雙引號

當比較字母數字值時,我通常用雙引號把等式的兩邊都括起來。當值為null時,它會工作;當一個值是一個或多個空格時,它起作用;當值有嵌入的空格時,它會起作用;它適用於更典型的非空無空格值。然而,我之所以使用不確定限定符,通常是因為一個非常細微的問題。

考慮一個變數的情況,它包含一個末尾有空格的值。也許值sad用尾隨空格填充以形成一個四個字元的值。它是否等於三個字元的值sad?在最純粹和最準確的意義上,不,它們是不相等的——正確使用雙引號方法會發現它們是不同的。但是在不太嚴格的情況下,您可能認為這些值是相等的。

使用點的方法將發現兩個值之間的相等,因為尾隨空格變成了等式左側和運算子之間的另一個空格。在這個尾隨空格的狹義例項中,點方法更好,但它只在變數沒有任何嵌入空格的情況下才有效。

在最後的分析中,雙引號方法遠遠優於點的方法,有一個非常獨特的情況下,它不是。養成在幾乎所有非數字比較中使用雙引號的習慣。

總結

if 命令在幾乎所有編碼語言中都很有用,批處理也不例外。在本文中,我們學習了條件子句,包括用於比較兩個運算元的有效運算子和用於證明變數、路徑或檔案存在的關鍵字,以及當子句的計算結果為true和false時發生的情況。還學習瞭如何對多個子句求值,以便有條件地執行多個邏輯分支。

通常情況下,批處理會給您更多的考慮,因此我詳細介紹了一些有用的方法,以增強您的條件子句對字母數字和數字值的比較。但是,是什麼使一個值成為字母數字或數字呢?

本文由部落格一文多發平臺 OpenWrite 釋出!

相關文章