在最基本的層面上,標籤是一種識別符號,它用盡可能少的文字簡明地定義了一種產品或一個物件。如果我們沒有標籤,商業就會停滯不前;雜貨店裡會擺滿一架又一架神秘的罐頭產品。晚餐吃什麼?它可能是豆類或南瓜派混合物;我們要開啟才能知道。
如果沒有標籤,批處理就不會陷入如此混亂的境地,但是您的編碼工具箱中將缺少一個用於建立更復雜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 釋出!