09 Windows批處理之標籤和無序執行

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

在最基本的層面上,標籤是一種識別符號,它用盡可能少的文字簡明地定義了一種產品或一個物件。如果我們沒有標籤,商業就會停滯不前;雜貨店裡會擺滿一架又一架神秘的罐頭產品。晚餐吃什麼?它可能是豆類或南瓜派混合物;我們要開啟才能知道。

如果沒有標籤,批處理就不會陷入如此混亂的境地,但是您的編碼工具箱中將缺少一個用於建立更復雜bat檔案的重要工具。到目前為止,我們介紹的每個bat檔案、程式碼片段和清單都是順序執行的。直譯器依次解釋每一行,首先執行第一個命令,然後執行第二個命令。這種情況一直持續到以下兩種情況之一發生:bat檔案的最後一個命令被解釋,或者出現語法錯誤使bat檔案崩潰。標籤允許您以非順序的方式執行批處理命令。在本文中,我將介紹程式碼中向前和向後分支的概念,根據資料條件重複程式碼的某些部分,甚至建立一些不是批處理固有的命令。

標籤還將為我提供一個很好的機會來討論一個非常重要的主題:編碼習慣,特別是縮排。

標籤

批處理中的標籤就是您所期望的那樣,一個定義程式碼塊的標籤。更具體地說,是標記bat檔案中的某個點或位置。標籤不是命令,儘管它永遠不會被執行,但您很快就會發現它對執行流至關重要。

標籤可以包含字母、數字和一些特殊字元,最重要的是,標籤必須以冒號開頭。奇怪的是,標籤的名稱可以包含額外的冒號,但不能在第二個位置。例如,這裡有一些程式碼被定義或標記為它的確切功能,檢查特定變數的狀態:

:CheckStatus
 if /i "%status%" equ "fail" > con echo Failure
 if /i "%status%" equ "good" > con echo Success

類似地,這段程式碼處理一個非常基本的中止過程,並被標記為:

:Abort
 echo The Process is aborting
 exit /B 1

我將在後續中討論退出命令。現在,它只是用來退出bat檔案。

定義標籤是很簡單的,但是在討論標籤的影響和如何使用它們之前,請允許我說個題外話。

縮排

許多批處理程式設計師抗拒縮排他們的程式碼。我不確定為什麼,因為我熟悉的其他語言都有縮排的某種約定,如果不是硬性要求的話。我最好的猜測是,它的核心是對語言的根本不尊重,認為批處理是一個實用的麻煩,必須儘快處理,而不考慮可讀性,更不用說美觀了。為了使批處理程式碼獲得應有的尊重,我建議所有命令都以兩個空格縮排開始。將if命令程式碼塊中的所有邏輯(以及有待討論的類似結構)再縮排三個空格,巢狀結構的縮排幅度更大。

在關於標籤的那節中,這個話題似乎是一個不合邏輯的話題,但實際上,這是一個理想的位置。標籤應該稍微突出一點,甚至更突出一點。任何型別的格式良好的文件都有部分、章節、節和/或子節,其中每個部分通常都有某種標題或提示,透過不同的字型、字型大小、加粗、下劃線、著色或上述部分或全部的組合,在視覺上從其他文字中脫穎而出。當寫入bat檔案時,這些選項不可用。我們的武器庫已經減少到一個重要的項,即縮排,並對大寫和空白表示認可。

由於標籤的第一個字元總是冒號,因此我總是將冒號放在該行的第二個位元組中,從而將典型的縮排減少到一個字元。因此,當任何人檢視我編寫的bat檔案時,所有的標籤都會突出。我將每行的第一個字元保留為rem命令的開頭。例如,這裡有一個基本的註釋、標籤和兩個簡單的命令:

rem - This code does something
 :DoSomething
  set do=something
  set doMore=somethingElse

標籤中冒號後面的大寫字元也增加了它的突出性。

我希望我不會成為批處理編碼約定的斯大林(有觀點認為他是一個獨裁者)。這只是一個程式設計師的觀點,還有其他經過深思熟慮的約定與我的不同。重要的是程式碼應該易於閱讀。有很多方法可以做到這一點,但是完全沒有縮排肯定無法透過測試,即使這個話題暴露了我專制的一面。

goto 命令

現在我們有了定義程式碼片段的標籤,它有什麼用呢?一些編碼人員實際上使用標籤作為臨時註釋,但標籤的真正功能是將流程引導到標籤下的程式碼。這就是goto命令發揮作用的地方,它做了聽起來應該做的事情。它指示直譯器跳到由標籤定義的程式碼中的某個位置。考慮這兩個命令:

goto :Abort
goto :DoSomething

goto命令向本文前面定義的:Abort和:DoSomething標籤傳送控制。

這並不完全正確;第一個命令將執行傳送到中止例程,第二個goto命令永遠不會執行。在bat檔案中,標籤本身可以出現在分支到它的goto命令之前或之後,但重要的是要理解,執行永遠不會立即返回到goto命令之後的命令。一旦執行了goto,我們就完全受標籤下程式碼的支配了。

要轉到定義為:Abort的標籤,也可以使用以下命令:

goto ABORT

這裡發生了兩件事。首先,在goto命令中刪除了標籤名稱中的冒號。我懷疑這是一個早期的錯誤,微軟不會修復以保持向後相容性。其次,實際的標籤只有大寫的A,但是goto命令顯示整個標籤名稱大寫。

這個例子演示了標籤名稱是不區分大小寫的,就像批處理一般操作一樣,並且在goto命令中冒號是可選的。雖然直譯器允許這樣做,但我認為沒有理由將兩個標籤名稱以任何方式區分開來,因為這隻會造成混淆。堅持一致性是關鍵。

在上一篇文章中介紹的call命令也與標籤一起使用,但是它的行為與goto命令非常不同。我將在下一篇回到call命令和它們的區別。

正向分支

goto命令向兩個方向之一傳送控制或流程流;一種是在程式碼上向前分支。在本例中,三個echo命令向控制檯寫入文字,但只執行了第一個和第三個echo命令:

> con echo Before GOTO
got :MyLabel
> con echo After GOTO
:MyLabel
> con echo After LABEL

不難想象使用這種技術的更復雜的程式碼。goto命令可以根據if命令的結果有條件地執行,而不是在單個echo命令上進行分支,它可能會跳過更大的程式碼段。例如,如果某個檔案存在或不存在,或者如果檢測到錯誤,您可以跳過一個或多個程式的執行,您可以跳到將中止bat檔案執行的程式碼,跳過其他所有內容。

goto也可以作為跳出迴圈的工具。不幸的是,我還沒有討論過迴圈;很快我將詳細討論for命令和迴圈。但是現在,要理解這個邏輯,你只需要知道這個迴圈將對listOfNames變數中列出的每個名字執行一次,不管它包含多少個名字:

for %%n in (%listOfNames%) do (
   if /i "%%n" equ "Waldo" goto :FoundName
)
> con echo ** Name Not Found **
:FoundName

if命令正在搜尋一個特定的名稱。如果找到了,goto命令就跳出迴圈,跳到最後一行的標籤。

這一點之所以重要,有兩個原因。首先,它是高效的——如果名稱在列表的開頭附近找到,那麼CPU週期不會浪費在無意義地搜尋列表的其餘部分上。更重要的是,如果找到該名稱,則永遠不會執行echo命令。請注意,邏輯不僅過早地跳出迴圈,而且還在編寫指示未找到名稱的訊息時進行分支。

逆向分支

前一節中的示例使用goto命令在程式碼中跳轉。接下來,我將看一些反向執行的goto命令的示例。但首先,我已經討論了我們如何構建更現代語言的某些元件,這些元件不是批處理的顯式組成部分(想想布林值和浮點數),但還有許多其他元件尚未出現。批處理沒有while命令,也不支援do…while命令。在其他語言中,while命令執行從零到多次的程式碼塊,直到滿足某個條件。一個do…while命令非常相似;唯一的區別是,程式碼塊將在計算條件之前執行一次。讓我們在批處理中建立這兩個。

while 命令

為了演示批處理while命令的有用性,我們將編寫一些程式碼,將從值中去掉所有前導零,這是任何程式設計師不希望意外執行八進位制算術的必要條件。當第一個位元組為0時,while命令可能會執行一段程式碼,而該程式碼只會去掉一個前導位元組。

:StripLead0s
 if "%nbr:~0,1%" equ "0" (
    if "%nbr:~1,2" neq "" (
       set nbr=%nbr:~1%
       goto :StripLead0s
 )  )

直譯器在第一次遇到標籤時基本上忽略它,並詢問nbr的第一個字元。如果它是零,程式碼接下來會驗證是否有第二個位元組——也就是說,這個0實際上是前導。如果兩者都為真,則進入程式碼塊,在goto命令將控制傳送回If命令之前的標籤之前,它將去掉前導0。

如果變數沒有前導的0,程式碼塊就不會執行。如果它有一個前導的0,那麼程式碼塊就會被執行一次。然後,首位元組再次被檢查,由於它不再是0,因此執行流將繼續到接下來的任何內容。如果nbr有17個前導的0,那麼刪除0的程式碼塊執行17次,在首位元組被檢查18次之後,執行就繼續了。

while這個詞沒有出現在這個清單中,但是它做了一個適當的while命令所能做的一切。據我所知,這是一個批處理while命令。

重要:

前面的程式碼片段是我展示的第一個if命令巢狀在另一個if命令內的例子,但是您將在後面的文章中看到更多的巢狀命令。關於編碼約定的另一個注意事項是,在該清單中,我將後面的兩個右括號堆疊在一行上。這使得程式碼更加緊湊,特別是當巢狀多層深度時,並且它將重點放在有趣的邏輯上,但我承認我是少數人。大多數批處理程式設計師將每個右括號與各自的if命令對齊。

:StripLead0s
 if "%nbr:~0,1%" equ "0" (
    if "%nbr:~1,2" neq "" (
       set nbr=%nbr:~1%
       goto :StripLead0s
    )
 )

做你覺得對的事,並堅持做下去。另外,請注意標籤名稱包含一個數值。如前所述,我們並不侷限於字母表中的字母。順便說一下,這個縮排看起來不錯吧?

do...while 命令

批處理 do...while 命令看起來很相似;唯一的區別是主邏輯必須至少執行一次。在具有內建do...while 命令的語言中,條件子句通常出現在結構的末尾(可以理解的是,在主邏輯執行一次之後),批處理也不例外。與while命令相比,主邏輯從if命令程式碼塊內部移動到標籤之後和if命令之前。

為了演示,讓我們舉一個例子,其中textStr變數要右填充至少一個空格,以將其構建為至少25位元組的長度。如果原始字串長度小於25位元組,則結果將為25位元組;如果它最初至少有25位元組長,則會在結果後面新增一個空格。(該字串可能是要在控制檯上顯示的一些連線文字的一部分,其中空格填充將對列進行對齊。但當然,我們需要在它和接下來的內容之間留出空間,即使它需要額外的位元組。)

正確的填充必須至少完成一次,這適用於 do...while 命令:

:PadRight
 set textStr=%textStr% &
 if %textStr:~24,1% equ "" goto :PadRight

與while命令一樣,標籤位於大部分程式碼之前,但核心邏輯緊隨其後,在本例中是用一個空格填充字串的單個set命令。然後檢查第25個位元組。(記住,它是零偏移。)如果它不存在,則goto命令將執行傳送回標籤,以便在字串後面新增另一個空格。這個過程一直重複,直到第25個位元組被填充,確保字串至少有25個位元組長,並且無論長度如何,至少新增了一個空格。

:eof 標籤

一個特殊的標籤不是由程式設計師建立的,而是所有bat檔案固有的:eof,它表示檔案結束。當以下goto:eof命令在被呼叫的bat檔案的主邏輯中執行時,控制權將返回給呼叫的bat檔案:

> con echo We are about to exit the bat file.
got :eof
> con echo This command will never be executed.

在高階bat檔案中執行相同的命令將完全停止該程序,即使bat檔案中不存在定義為:eof的標籤。

如果你有相反的天性並決定定義你自己的:eof標籤,直譯器會簡單地忽略它,就好像它是一個無意義的註釋。在下一篇中,我將進一步探討這個獨特的標籤,特別是直譯器在可呼叫例程中如何處理goto:eof命令。

變數標籤

在沒有編譯器的語言中工作有一些明顯的缺點,但我已經向您展示了一些希望(例如延遲擴充套件)。還有一個是在執行時在goto命令中定義標籤名稱的能力,儘管標籤本身必須硬編碼。為此,設想一年中的每個月都有一個不同的標籤。下面顯示的是前三個,它們下面沒有各自以月份為中心的程式碼:

:MonthJanuary
:MonthFebruary
:MonthMarch

顯然,下面的命令將執行到前面程式碼片段中的一個特定標籤:goto :MonthFebruary

但那都是舊的操作了。更有趣的是,如果將變數month設定為二月,下面的命令將呼叫相同的標籤:

goto :Month%month%

這個goto命令的引數是硬編碼的:Month和month變數的值的連線。解析變數後,命令將執行指向標籤:MonthFebruary。對於其他有效月份也是如此,這意味著如果month設定為March,則同一行程式碼也將變為:MonthMarch。

但是,這確實提出了這樣一個問題:當生成的標籤名稱在bat檔案中不存在時,例如,如果month設定為Erele,會發生什麼情況。直譯器將以下訊息寫入控制檯:

The system cannot find the batch label specified - MonthErele

不幸的是,您將永遠不會看到此訊息,因為程序將立即崩潰。在下一篇中,您將看到批處理在與call命令一起使用時可以更好地處理錯誤的標籤名稱,但是如果您將此技術與goto命令一起使用,請確保引數解析為有效的標籤。

總結

在本文中,我介紹了標籤的概念以及如何透過goto命令導航到標籤。您學習瞭如何建立標籤,探索了使用標籤的技巧,並瞭解了它們在構建 while 和 do...while 命令中的重要作用。我還介紹了必不可少的 :eof 標籤。

但是,您可以透過兩種不同的方式導航到標籤。下文的大部分內容還將關注標籤,以及如何使用標籤在bat檔案中建立可呼叫的案例。我還將詳細介紹如何從一個bat檔案中呼叫另一個bat檔案,這是一個關鍵主題,因為您開始建立對於單個bat檔案來說過於複雜的專案。

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

相關文章