10 Windows批處理之呼叫例程和bat檔案

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

在前文中,我介紹了標籤和非順序執行,這兩者在本文中也起著重要作用。我將很快介紹一個已經討論過的命令的新變化,允許您建立和呼叫由標籤定義的例程。不是簡單地在標籤之後將控制權交給程式碼,而是在例程執行後將控制權返回到呼叫它的位置。在編寫更復雜、更有趣的bat檔案時,您需要完全理解例程。

在前面文章中,我介紹了呼叫用其他語言編譯的可執行檔案的概念。我將在這裡展開討論,描述一個bat檔案呼叫另一個bat檔案的不同技術。顯然,您將瞭解最典型的呼叫型別,它將控制權返回給呼叫的bat檔案。但是,您還將學習放棄對所呼叫的bat檔案的控制的技術,以及如何生成第二個並行批處理程序。此外,您還將探索從例程或bat檔案優雅退出的不同方法,無論是否使用返回程式碼。

call 命令及重新訪問

在建立可呼叫的內部例程之前,必須瞭解使用標籤的兩個命令之間的異同。其中之一是call命令,在那裡我們使用它來呼叫用其他語言編譯的程式。另一個是goto命令,用於更改bat檔案的執行流程。

為了比較和對比這兩個命令,請回顧之前介紹的程式碼:

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

goto 命令跳過了中間的 echo 命令,導致如下輸出:

Before GOTO
After LABEL

為了演示對比,下面更改了程式碼中要呼叫的每個goto例項,包括goto命令和echo命令中的文字,而在這個非常簡潔的bat檔案中保留了其他所有內容。

> con echo Before CALL
call :MyLabel
> con echo After CALL
:MyLabel
> con echo After LABEL

執行上面的bat檔案,您將看到四行程式碼寫入控制檯,而不是某些人可能期望的三行程式碼。

Before CALL
After LABEL
After CALL
After LABEL

Before CALL 的顯示顯然是立即執行的。call 命令臨時將控制權交給標籤後面的程式碼,導致顯示 After LABEL。當這是一個 goto 命令時,此時 bat 檔案在顯示之後結束。但是使用call命令,在 :MyLabel 和 bat 檔案末尾之間的所有內容執行之後,控制立即返回到call命令之後的命令。因此,顯示 After CALL。

有些人可能期望執行在此時完成,但直譯器接下來再次遇到 :MyLabel。我們不會呼叫它;相反,它只是一行程式碼。注意,我沒有稱它為命令,甚至也沒有稱它為語句。它只是一行程式碼,一個佔位符,在這個上下文中,只不過是通往下一個命令的路徑上的一個非常微妙的減速帶。直譯器移到bat檔案的最後一行,第二次顯示文字 After LABEL。直譯器找不到其他需要解釋的命令,bat檔案就完成了。

當 goto 命令放棄控制時,call 命令記住它從哪裡來,並在它的業務完成後返回到那個位置。現在我們有了一個可呼叫的內部例程,我們將用 call 命令呼叫這個例程。

呼叫內部例程

隨著批處理程式碼變得越來越有趣,您可能希望從bat檔案中的不同位置多次執行一段程式碼。例如,您可能希望多次呼叫可執行檔案,或者您可能希望定期檢查目錄中是否有需要複製的檔案。當我們用到互動式批處理時,你可能想要問使用者一個問題並多次得到響應。

面對對一段程式碼進行多次呼叫的需求,新手程式設計師可能會採用剪下和貼上的方式——在我極其挑剔的觀點中,這是一種令人討厭的選擇。一個更好的解決方案是建立一個內部例程,並從多個位置呼叫它。您甚至可以將一些只呼叫一次的程式碼放入例程中,以便更好地組織您的bat檔案。有時直接執行一個標籤是完全可以的,但更多時候,您需要建立一個只能透過呼叫它來呼叫的例程。

對於下面的練習,我們繼續使用上面的程式碼,以便標籤定義一個可呼叫例程。也就是說,執行流將呼叫例程,從中返回,並在再次進入該例程之前退出bat檔案。為此,我需要一種方法來終止例程和bat檔案。即 After LABEL的最終將不再顯示。相反,我們期望有這三行輸出:

Before CALL
After LABEL
After CALL

下面的程式碼,看起來有點不同,正是這樣做的:

> con echo Before CALL
call :MyLabel
> con echo After CALL
goto :eof & rem End of TestCall.bat

:MyLabel
> con echo After LABEL
goto :eof & rem End of :MyBabel

:AnotherLabel
> con echo This is Never Executed
goto :eof & rem End of :AnotherLabel

在逐步執行程式碼之前,請注意三個goto :eof命令。如您所料,第一個跳轉到檔案的末尾,停止bat檔案。另外兩種說法完全不同,是新出現的。

在初始的 echo 命令之後,call 命令會呼叫MyLabel的例程,該例程只包含兩個命令。第一個是我們熟悉的 After LABEL 回顯到控制檯,第二個是 goto :eof 命令。因為這個命令是在標籤被呼叫之後執行的,所以它結束的不是檔案而是例程,並且控制在呼叫命令之後返回到命令,在控制檯寫入 After CALL。最後,主 goto :eof 命令退出bat檔案,因為直譯器知道它不在例程中。

:MyLabel例程中,轉到:eof(或檔案結束)是不恰當的;它實際上更像是例行公事的結束,但我們不要在語義上吹毛求疵。如果你刪除這個goto :eof命令,控制將繼續到 :AnotherLabel 的程式碼,然後返回主線邏輯。但是對於這個命令,下面的程式碼 :AnotherLabel 永遠不會執行。

由於 goto :eof 命令有兩種不同的用法,所以我通常在這些命令後面加上一個註釋,定義它要終止什麼,或者是例程的名稱,或者是bat檔案本身。我只是將rem命令放在一個&號後面,它在一行程式碼中將兩個命令分隔開。從程式設計的角度來說,這是不必要的,但是這種做法極大地增強了程式碼的可讀性,特別是當例程比前面的示例更長、更復雜時。

呼叫Bat檔案

短或重複的程式碼位是內部例程的最佳候選;您可以在bat檔案的末尾新增一個或多個例程,以建立一個組織良好的模組,您可以為此感到自豪。但有時這些簡短的程式碼並不那麼短,或者它們非常有用,以至於您希望將它們提供給您編寫的其他bat檔案,甚至可能是其他人。這個場景沒有使用例程,而是呼叫一個bat檔案呼叫另一個bat檔案。例如,您可以建立一個bat檔案來處理日誌記錄,並從多個其他bat檔案呼叫它。

從一個bat檔案執行另一個bat檔案的工作方式與執行內部例程略有不同。但首先,讓我們回到前面的編譯程式是如何執行的。當直譯器遇到一行只是可執行檔名稱的程式碼時,它呼叫可執行檔案。因此,這個“命令”執行程式:D:\Batch\10\MyProg.exe

程式完成其任務後,控制返回到bat檔案。您可能期望對bat檔案的呼叫以同樣的方式工作,但是遺憾的是,事實並非如此。然而,下面的程式碼行確實執行了被呼叫的bat檔案,但是使用了一個巨大的bat:call D:\Batch\10\CalledBat.bat

總而言之,無論是呼叫bat檔案還是呼叫另一種語言的編譯可執行檔案,您都可以使用call命令或省略它,但這是有區別的。在呼叫可執行檔案時,這兩種技術實際上是相同的。在呼叫同類bat檔案時,呼叫命令確保將控制權返回給呼叫者。如果沒有命令,控制就永遠不會返回。

因為我從來沒有發現不返回的bat檔案呼叫有什麼用途,所以我總是傾向於忽略可執行檔案的呼叫命令,而將其用於bat檔案。一個優點是,一眼就能看出呼叫的是什麼型別的檔案。

在我職業生涯的早期,當我無法弄清楚為什麼我的bat檔案停止執行時,我瞭解到呼叫命令關於 bat檔案的必要性。沒有掛起或中止訊息;它就這麼停了。更復雜的是,我的故障排除可以集中在所謂的bat檔案上。過了好一會兒,我才注意到那個丟失的 call 命令,更重要的是,我明白了它的重要性。但這並不是call命令的唯一特性。

呼叫標籤注意事項

在前文中,我提到可以在goto命令的引數中將冒號從標籤名稱中去掉,儘管強烈建議包括它。使用call命令,在呼叫定義內部例程的標籤時總是需要冒號。

這種明顯的不一致可能沒有意義,除非您考慮到goto命令只涉及到其bat檔案中的標籤,而call命令呼叫其bat檔案內部和外部的實體。結果是,當嘗試呼叫 :MyLabel 而不帶冒號時,會發生一些非常意想不到的事情:call MyLabel

冒號會告訴直譯器呼叫內部例程,但直譯器卻試圖呼叫外部檔案。首先,它在當前目錄中查詢可執行檔案,如MyLabel.com或MyLabel.exe。然後,它在當前目錄中查詢MyLabel.bat和其他一些具有此檔名的可執行檔案型別。然後,它遍歷path變數中的所有目錄,拼命尋找任何名為MyLabel的可以執行的內容。如果沒有找到這樣的檔案,直譯器將不會查詢該名稱的標籤,即使 :MyLabel 是bat檔案中的有效標籤;相反,它會生成一個錯誤。

當使用goto或call命令導航到標籤時,為了保持一致性,請務必使用冒號。

重要:

當沒有找到標籤時,goto命令會中止程序。call命令更容易理解一些。當它的引數是一個無效的標籤時,它們都寫出一條錯誤訊息,但是呼叫命令也將errorlevel設定為1。如果您選擇不詢問返回程式碼,則該過程將若無其事地繼續進行,就好像什麼都沒有發生一樣。

啟動Bat檔案

有時,您可能希望啟動或生成一個bat檔案作為一個新程序。也就是說,您可能希望啟動另一個bat檔案,但不希望直譯器在繼續之前等待它完成。例如,您可以並行執行多個程序以加快總體處理時間。您可以剝離出一個非關鍵但耗時的任務,比如一個日誌記錄程序,讓它在自己的時間內執行。在後文中,我將討論如何自動 kill 和重新啟動掛起的程序。為了實現這一點,我將把容易掛起的程序作為一個獨立的bat檔案生成,並從主bat檔案監視它。

要啟動或生成一個bat檔案,只需使用start命令代替call命令:start D:\Batch\10\LaunchedBat.bat

該命令建立第二個命令或DOS視窗,其中檔案 LaunchedBat.bat 與啟動它的bat檔案同時執行。

exit 命令

您可能會想到,exit 命令退出例程、bat檔案或整個執行,它甚至可以設定返回程式碼。它在功能上與 goto :eof 命令有重疊,但我很快就會展示一個重要的區別。

不帶引數的exit命令會突然結束整個程序。遺憾的是,第二個echo命令不會被執行:

> con echo The meaning is Life is...
exit
> con echo ... %meaningOfLife%

第一個echo命令將其訊息寫入控制檯,但是exit命令在您可以讀取它之前關閉了視窗。無論在哪裡呼叫退出命令,都會發生這種情況——在高階bat檔案中,在被呼叫的bat檔案中,甚至在任一型別bat檔案中的例程中。

然而,文件不清楚B代表什麼,但對我來說,它代表break,因為下面的命令從被呼叫的程式碼中跳出,無論是被呼叫的bat檔案還是bat檔案中的例程:exit /B

該命令只有在高階bat檔案的主邏輯中呼叫時才退出整個程序。它不會改變errorlevel,邏輯上等同於 goto :eof。這兩個命令都是有效的,其用法通常取決於個人偏好。我使用的是 goto :eof命令,但只在不需要返回程式碼的情況下使用。

在前面的文章中,我們介紹過基本中止邏輯,但將其解釋留到後面,也就是現在。

:Abort
  echo The Process is aborting
  exit /B 1

這個退出命令的行為與 exit /B 類似,但有一個例外。當控制返回到呼叫程式碼的位置時,該選項後面的命令的數字引數變成errorlevel中包含的新值。簡而言之,該命令脫離bat檔案或例程並返回退出或返回程式碼。在前面的例子中,返回碼是1。但是,如果沒有檢測到錯誤,則bat檔案的主邏輯可能以將返回程式碼設定為0結束:exit /B 0

如果檢測到致命錯誤,主線邏輯中的 goto :Abort 命令將把直譯器引導到中止邏輯。必須使用 goto 命令,因為 call 命令會將中止邏輯視為被呼叫的例程;將設定錯誤級別,但控制權將返回到致命錯誤的位置。但是當使用goto命令導航到標籤時,不會呼叫例程;它仍然被認為是在主線邏輯中,並且exit命令結束了bat檔案,而不是一個例程。

為了更靈活,你可以為退出程式碼建立一個變數,針對不同的失敗將其設定為不同的值:

:Abort
 echo The Process is aborting
 exit /B %exitCode%

然後,可以透過bat檔案中的多個goto命令訪問該邏輯。

(實際的中止例程將比這個簡單的echo命令有趣得多。錯誤訊息可以是多行,並且具有可變的內容,所有內容都寫入日誌檔案和控制檯,但我在這裡對其進行了簡化,以便將重點放在退出命令上。)

總結

在本文中,我詳細介紹了呼叫內部例程和其他bat檔案的不同方法。您已經學習瞭如何從這些呼叫中返回,或者如何簡單地從任何地方突然結束整個過程。您還學習瞭如何啟動或生成另一個bat檔案,該檔案完全獨立於第一個bat檔案。最重要的是,您現在瞭解了goto和call命令之間的重要而微妙的區別。簡而言之,呼叫返回控制並且可以到達它的bat檔案之外,而goto則兩者都不做。

這個謎題還有一大塊沒解開。呼叫的bat檔案可以向被呼叫的bat檔案傳遞多個引數,而被呼叫的bat檔案甚至可以設定和傳遞引數作為返回。這比人們想象的要複雜得多,我將在後面文章中詳細說明所有的細微差別。

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

相關文章