08 Windows批處理之執行編譯後的程式

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

本文是關於一個bat檔案執行或呼叫另一種語言編寫和編譯的程式。實際上,這樣做的語法非常簡單。本文最有趣的方面是,有時正在執行的程式在bat檔案中沒有定義路徑。bat檔案如何找到可執行檔案?

本文的主要重點將放在查詢此類程式的兩個非常重要的機制上,即當前目錄和路徑變數。本主題遠遠超出了程式的執行。您將在呼叫其他bat檔案時使用它,並且當資源沒有使用路徑定義時,它會影響許多其他例項。例如,在上文中,我們討論了許多用於複製、移動、刪除和重新命名檔案的命令。當每個命令中的檔案沒有在bat檔案中定義路徑時,所有這些命令都可以在bat檔案中很好地工作,但前提是您理解了這些概念。當然,您還將學習呼叫程式和傳遞引數的不同方法。

呼叫可執行檔案

通常,bat檔案只不過是呼叫已編譯程式(也稱為可執行程式)的工具或包裝器。bat檔案將簡單地設定程式所需的一些變數,呼叫可執行檔案,並在後端執行一些錯誤處理。更復雜的bat檔案可能呼叫幾十個不同的程式,甚至可能在某些呼叫中使用條件邏輯。不管簡單還是複雜,批處理的一個特性就是能夠呼叫用其他語言編寫的可執行檔案。

呼叫命令接受可執行檔案作為它的第一個引數,也可能是唯一的引數。下面的命令呼叫或執行位於D:\Batch\08\目錄下的程式UartAssist.exe:

call D:\Batch\08\UartAssist.exe

call命令呼叫一個程式;這應該不會讓人感到驚訝,但事情會變得很奇怪。這是批處理中唯一一個在省略命令名本身時執行良好的命令。下面的命令雖然在技術上不是call命令,但執行與前面示例中的call命令相同的功能:

D:\Batch\08\UartAssist.exe

思考一下這個問題。命令set x=1設定了一個變數,但是語句x=1只會混淆直譯器。如果在robocopy命令的開頭省去了文字robocopy,那麼頭腦正常的人都不會期望複製剩餘的文字。

這幾乎看起來像魔術,但從程式設計師的角度來看。當它解釋一個新行時,它通常期望第一項是一個命令。當它找到set時,它會預測一個變數、一個等號和一個值;當它找到robocopy時,它會尋找另一組引數。當它發現一些完全出乎意料的東西時,直譯器不會猶豫;它將返回有問題的內容給到編碼人員,假設它是可以執行的,並執行它-就像call命令一樣。

一些批處理程式設計師對可執行檔案使用call命令;有些則不然。我屬於後一種陣營,更喜歡可執行檔案的簡潔外觀,或者只是在一行程式碼中有一個已解析的變數,但我對那些明確地拼寫命令的人沒有任何疑慮。更重要的是,一致性是關鍵;堅持你的傳統選擇。(不管使用哪種方式,主要的是你的程式碼風格要一致。)

我還喜歡將程式名儲存在具有完全限定路徑的變數中,僅在尚未定義時設定它。這確保了所需的程式在預設情況下儲存在變數中,同時也允許其他人為了靈活性將其設定為替代程式:

if not defined UartAssist set UartAssist=D:\Batch\08\UartAssist.exe

然後,當需要執行程式時,這個簡單的命令(如果我可以稱之為命令的話)將呼叫所需的程式:

%UartAssist%

這個變數包含可執行檔案的路徑,但是讓我們回到程式碼行只包含硬編碼的路徑和檔名的概念。

你可以透過刪除路徑來縮短它,只留下程式的名稱和副檔名:UartAssist.exe

這看起來更簡單,但是當你停下來思考直譯器在機器甚至網路上的什麼地方找到程式時,複雜性就增加了。在深入研究這些細節之前,我需要先談談兩個命令/變數。

cd 命令和變數

cd命令也是一個變數,一個特殊的變數,是少數批處理偽環境變數之一。在後續中,我將對這些變數進行更多的介紹。現在,只需將它們視為直譯器最初設定的具有一些獨特特性的變數。

該變數表示當前目錄。這個命令有點模稜兩可,因為它也可以表示更改目錄,因為它用於去…好吧,就是更改當前目錄。

雙擊或開啟bat檔案時,當前目錄為該bat檔案所在的目錄或資料夾。如果從不同的程序呼叫相同的bat檔案,則從該程序繼承當前目錄。簡單地呼叫不同目錄中的bat檔案或可執行檔案不會改變當前目錄,但是cd命令會改變。

下面顯示的第一行和最後一行使用cd變數顯示當前目錄。這個核心是cd命令,它熟練地將當前目錄更改為其引數,假設該目錄存在:

> con echo Current Directory is: %cd%
cd D:\Batch\
> con echo Current Directory is: %cd%

如果一個包含這三行程式碼的bat檔案位於D:\Batch\08目錄下,執行它將顯示原來的當前目錄和新分配到控制檯的當前目錄:

Current Directory is: D:\Batch\08
Current Directory is: D:\Batch

您也可以設定當前目錄相對於現有的當前目錄。一個點表示它的現有值,所以這將cd變數分配給一個子目錄:

cd .\Child\

兩個點表示現有當前目錄的父目錄,所以下面的程式碼將當前目錄向上移動一級:

cd ..

你甚至可以將cd變數重新賦值給一個兄弟目錄,首先用兩個點向上一層:

cd ..\Sibling\

我甚至不願提及這一點,但是chdircd命令的批處理同義詞。也就是說,前面示例中的命令在功能上等同於chdir ..\Sibling\。但是,cd變數沒有同義詞,因此可以使用chdircd來更改當前目錄,但是在解析當前目錄時需要使用cd。我發現將cd用於這兩種目的是最簡單的。

在討論當前目錄的用途之前,我需要介紹另一個命令,它也是一個變數。

path 命令和變數

與cd非常相似,path也是命令和偽環境變數。該變數是在Windows計算機上預定義的,其中包含計算機所需的以分號分隔的目錄列表,例如Java和Windows可執行檔案的路徑。

就像cd命令設定當前目錄一樣,path命令設定路徑變數。在下一行程式碼中,現有的值被新增到另外兩個目錄中;請注意,在每個附加目錄的末尾插入的分號作為分隔符:

path D:\PrependDir\;%path%;C:\AppendDir\;

您可以完全重新分配path變數—如果引數只是一個分號,甚至可以完全刪除它。這個變數中的各種目錄的存在是有目的的,可能允許一些必要的程序執行。要非常謹慎地在機器上持久地更改變數,例如使用setx命令,但是前面顯示的path命令只更改執行bat檔案的路徑。在最壞的情況下,你可能會破壞你的bat檔案,但你不會破壞你的電腦上的任何其他東西。在下一節中,我將解釋為什麼您可能希望更改path變數。

警告:

set命令提供了重置cd和path變數的另一種方法,但是出於一致性考慮,我拒絕使用這種方法,因為其他一些偽環境變數不能或不應該使用該命令重置,而且它需要更多的按鍵操作。

查詢可執行檔案

讓我們回到簡單地透過呼叫程式的名稱和副檔名來執行程式,就像這樣:UartAssist.exe

直譯器在哪裡找到可執行檔案?它查詢的第一個位置是當前目錄。如果在那裡找到,那就是執行的檔案。否則,直譯器將在path變數中定義的每個目錄中依次查詢它,並執行它找到的第一個目錄。如果在這些目錄中找不到具有此名稱的可執行檔案,直譯器只會將errorlevel設定為9009。(奇怪的是,如果call命令位於可執行檔案的名稱之前,則錯誤的返回碼為1。)

讓我們執行同一行程式碼,假設UartAssist.exe位於D:\Batch\08\中。如果這個目錄是當前目錄,程式將被找到並執行。否則,如果該目錄位於path變數中,則可能會找到並執行該程式。假定該程式沒有被位於path變數層次結構的當前目錄或更高目錄中的具有相同名稱和副檔名的不同程式所取代。

如果這些都不為真,則無法找到程式,但是有不同的方法可以確保直譯器找到可執行檔案。首先,我們可以使用cd命令在執行程式之前更改當前目錄:

cd D:\Batch\08\

或者,我們可以透過以下兩種方式之一修改path變數以包含該目錄。這裡我在path前面加上字首:

path D:\Batch\08\;%path%

這裡我附加了path:

path %path%;D:\Batch\08\

如果附加了該目錄,並且在path變數中先前定義的目錄中存在另一個名為UartAssist.exe的檔案,則將執行該程式。新增目錄可確保我的可執行檔案在其他任何檔案之前被獲取,但這並非沒有其自身的危險。它可能會在path變數中引入一些內容,這些內容將覆蓋其他程序使用的資源。

這絕不是一個糟糕的技術;事實上,如果管理得當,它是非常有用的。使用當前目錄或路徑變數查詢可執行檔案的一個重要用途是使程式碼可移植。您可以將bat檔案儲存在單個資料夾中,也可以將其他bat檔案以及任意數量的可執行檔案、配置檔案和其他資源儲存在更復雜的資料夾結構中。然後,您可以將該資料夾複製到具有不同根目錄結構的其他計算機和網路。由於當前目錄基本上遵循高階bat檔案,因此如果使用當前目錄查詢其其他元件,它將在這些不同的位置工作。

您可以將預設的可執行檔案放置在與bat檔案相同的資料夾中。如果單獨執行,它將使用這個可執行檔案。如果從具有不同當前目錄的另一個bat檔案呼叫,它可能會找到一個不同的程式,允許其他人使用您的bat檔案來呼叫他們自己的可執行檔案。簡而言之,您可以建立同名程式的層次結構,其中不同的程式在不同的例項中執行。

為了進一步說明這一點,我在前面暗示過,呼叫程式甚至不需要擴充套件。也就是說,如果UartAssist.exe儲存在當前目錄中,它可能會被這行程式碼呼叫:UartAssist

直譯器透過另一個偽環境變數pathext查詢沒有副檔名的可執行檔案,該偽環境變數包含以分號分隔的擴充套件的層次結構,就像path變數包含目錄的層次結構一樣。直譯器仍然在當前目錄中查詢可執行檔案,然後是path變數中的目錄,但是在每個資料夾中,它現在查詢它能找到的第一個具有UartAssist檔名和給定層次結構中列出的副檔名的可執行檔案。

如果pathext變數沒有被別人或其他東西修改過,那麼它可能包含大約十幾個副檔名,以.com.exe.bat.cmd開頭——按照這個順序。因此,唯一能阻止前一個命令從當前目錄執行UartAssist.exe的實體是當前目錄下的UartAssist.com。(如果必須重置這個變數,請使用set命令。pathext變數只是一個變數,而不是命令。)

推送和彈出當前目錄

cd命令在更改當前目錄方面做得很好,但是之前的當前目錄會丟失,再也不會知道了。通常這是完全可以的,但在其他情況下,您可能希望在將當前目錄恢復到以前的狀態之前臨時更改當前目錄。也許一個實用程式bat檔案被編寫為可以從許多其他bat檔案呼叫。在短時間內,我將討論如何從一個bat檔案呼叫另一個bat檔案,但是現在,我們只需要理解被呼叫的bat檔案的透檢視。

被呼叫的bat檔案可能在某個資料夾中建立或使用資源,因此在bat檔案開始時更改當前目錄是有意義的。但是,當被呼叫的bat檔案完成並將控制轉回撥用的bat檔案時,應該恢復先前的當前目錄。這很簡單,因為呼叫bat檔案可能在不同的目錄中工作,更改其當前目錄很可能會導致問題。一個更安全的操作是,被呼叫的bat檔案希望將其目錄保留給自己。如果被呼叫的bat檔案沒有恢復當前目錄,則呼叫的bat檔案可能會在當前目錄中刪除不需要的檔案。被稱為bat的檔案可以隱藏它的目錄,同時也表現得很有禮貌。

為了解決這個問題,可以在執行cd命令之前將先前的當前目錄儲存在一個變數中,然後可以在bat檔案末尾執行另一個cd命令來設定它。但是批處理提供了兩個命令,它們一起可以更優雅地完成這個任務,即pushdpopd命令。

pushd命令像cd命令一樣更改當前目錄,但它也將先前的當前目錄推入堆疊以供以後使用。它有時被稱為push目錄命令,不過為了簡潔起見,通常將其讀成書寫的樣子,即“push-d”命令。在bat檔案的開頭,這個命令將簡潔地執行這兩個任務:

pushd D:\Batch\08\

在bat檔案末尾或接近末尾時,以下簡短命令將移動到D:\Batch\08\作為當前目錄,並從堆疊中檢索或彈出先前的當前目錄,使用它來恢復當前目錄:popd

這有時被稱為彈出目錄命令,但更常見的是“pop-d”命令。

注意這裡沒有引數;popd是很少接受任何引數的命令。當執行多個pushd命令時,每個命令都將另一個先前的當前目錄推入堆疊,並且每個後續的popd命令將恢復最近新增的目錄。

同樣值得注意的是,如果傳遞給pushd命令的引數是一個網路路徑,則將未使用的最高驅動器號分配給該路徑,並且popd命令將取消分配。最後,不帶引數的pushd命令顯示堆疊上目錄的完整列表,從最近新增的目錄開始。

警告:

push和popd命令的平衡非常重要。如果pushd分配了一個網路路徑,那麼相應的popd應該始終執行,即使處理了錯誤。如果沒有,任何對映的驅動器號將保持對映,即使在bat檔案完成之後。如果這種情況經常發生,計算機將耗盡可用的驅動器號。

透過當前目錄查詢其他資源

當前目錄的用途遠不止於查詢要執行的程式。對於任何資源(如檔案),如果沒有定義路徑,則假定當前目錄為其路徑。例如,在前面文章中,這個命令刪除了一個顯式檔案和所有以特定副檔名結尾的檔案:

del /Q D:\Batch\07\Source\Junk.txt D:\Batch\07\*.OLD

如果當前目錄是D:\Batch\07\Source\,這是從前一個命令中刪除兩次的路徑,下面的命令用更少的按鍵執行相同的任務:

del /Q Junk.txt *.OLD

對於xcopy命令的源引數和接受路徑和檔名作為引數的任何其他命令也是如此。我通常更喜歡使用顯式路徑來避免任何歧義,但這種技術為本文中描述的大量命令提供了相同型別的靈活性。再次思考前一篇文章,想象一下在沒有明確路徑的情況下複製、移動和重新命名檔案的所有命令。如果直譯器在當前目錄中找到一個或多個檔案,那麼它們都是很好的命令。

將引數傳遞給可執行檔案

在本文的開頭,我演示瞭如何呼叫編譯後的程式。在繼續之前,我要分享關於這個語法的最後一個觀察結果。

可執行檔案在執行時通常接受一個或多個引數。只需在程式後面列出這些形參,就可以將它們作為實參傳遞給程式。為了便於閱讀,我將三個引數放入變數中:

set inFile=D:\Batch\08\Input.dat
set outFile=D:\Batch\08\Output.dat
set logFile=D:\Batch\08\Log.dat

%UartAssist% %inFile% %outFile% %logFile%

輸入檔案是傳遞給程式的第一個引數;在許多語言中,這將被認為是程式中的args[0]。同樣,輸出檔案是第二個引數,args[1],日誌是第三個引數,args[2]。你也可以使用硬編碼的值,引數可以是任何你喜歡的;它們不一定是檔案。

總結

執行編譯後的程式一開始看起來很簡單。畢竟,您甚至不需要命令。但是,如果不瞭解我在這裡詳細介紹的當前目錄和path變數的細節,您就無法真正理解它是如何工作的。您已經瞭解瞭直譯器如何使用它們來查詢可執行檔案、檔案和任何其他資源,以及管理這些重要變數內容的多種方法。

執行另一個bat檔案與執行編譯後的程式類似,但又不相同,我們將在後面的文章中瞭解這些區別。但在深入研究之前,您將在下一篇文章中瞭解標籤及其許多重要用途,主要是它們對命令執行的時間和頻率的影響。

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

相關文章