Windows 程式設計/程式設計 CMD
在 Windows NT(XP、Vista、7、8、10 等)中,可以使用命令提示符 (cmd.exe) 編寫批處理檔案。它們可用於自動化檔案系統任務,如備份或基本安裝,也可與其他命令列實用程式結合使用。批處理檔案可以看作是一種簡單的指令碼語言,包含邏輯和跳轉。使用批處理檔案的優勢在於編寫簡單,無需編譯即可編輯檔案,跨 Windows NT 作業系統相容,並且由於其基於 MS-DOS,因此具有操作檔案系統的能力。批處理檔案指令碼不區分大小寫,但字串和資料區分大小寫。該檔案由一系列命令組成,這些命令按行分隔並像在命令提示符下執行的普通命令一樣執行,儘管某些功能有所不同。批處理檔案可以從 Windows 資源管理器執行,但用於顯示它們的控制檯在批處理檔案結束時會自動關閉,因此需要在最後新增一個命令來防止立即退出,以便在控制檯視窗關閉之前讀取任何剩餘的輸出。儘管批處理檔案能夠操作其環境,例如顏色設定和環境變數,但所有更改都是臨時的,就像在標準命令提示符會話中一樣。但是,顏色設定在 Windows NT 的後續版本中會被保留。為了嘗試學習有關批處理檔案的資訊,瞭解命令提示符命令非常有用。請參閱:Windows 命令指南。
指令碼儲存在一個批處理檔案中,副檔名為 .bat 或 .cmd。雖然 .bat 更為人所知,因為它曾用於命令提示符之前的 MS-DOS 環境,但命令提示符對批處理檔案的解釋方式與解釋 DOS 批處理檔案的方式大不相同,而且 .cmd 檔案只能由命令提示符解釋,因此使用 .cmd 副檔名可以防止在較舊的環境中被誤用。
執行從檔案頂部開始,到檔案末尾結束。當檔案結束時,如果檔案是從命令提示符中呼叫的,則退出到命令提示符;如果檔案是從 Windows 資源管理器或 START 命令呼叫的,則控制檯視窗關閉。
通常,批處理檔案以 'echo off' 命令開頭,這會停止在執行期間顯示輸入和提示符,因此只會顯示命令輸出。'@' 符號會阻止命令顯示輸入和提示符。它用在 'echo off' 命令上,以防止第一個命令顯示輸入和提示符
@ECHO OFF
為了列印行,再次使用 ECHO 命令,但這次使用除 'off' 之外的文字引數
ECHO.Hello World!
句點('.')用於防止與嘗試輸出 ON 或 OFF 混淆,而不是開啟和關閉輸入和提示符顯示。程式碼中的最後一行應該是
ECHO ON
'Echo on' 會重新開啟輸入/提示符顯示,以防程式退出到命令提示符,如果沒有 'echo on' 命令,將不會留下可見的提示符供使用。
提示:從 Windows XP 開始,ECHO ON 命令是可選的。命令直譯器在 BAT 檔案終止後會自動啟用它。
使用上面的程式碼,我們可以建立一個 Hello World 程式,如下所示
@ECHO OFF ECHO Hello World! ECHO ON
在批處理檔案中,有兩種方法可以編寫註釋。首先是這種形式
REM Comment here.
這種形式被包含在內,因為它存在於 MS-DOS 批處理檔案指令碼中。另一種形式是這種
::Comment here.
這種形式通常更受青睞,因為它執行和編寫速度更快,並且易於與普通命令區分開來。對於這種型別的註釋,只需要兩個雙冒號('::'),註釋在行尾結束。批處理檔案沒有多行註釋型別。
您也可以在命令末尾添加註釋
command &::Comment here.
在批處理指令碼中,所有變數都是儲存在系統記憶體中的字串。 使用 SET 命令分配變數。以這種方式分配的變數在系統環境中與預定義的全域性系統變數一起存在,直到定義它們的環境終止,無論是透過使用 ENDLOCAL 命令還是關閉命令提示符會話。應注意避免覆蓋 'PATH' 'TIME' 'DATE' 等全域性環境變數,因為其他程式可能依賴於它們具有 '正確' 的值。
- 在使用 'SETLOCAL' 命令建立了子環境的環境中使用 'GOTO :EOF' 命令意味著並等效於 'ENDLOCAL' 命令。
SET name=John Smith
此命令建立一個名為 name 的環境變數,並將它的值設定為字串 "John Smith"。第一個 '=' 符號兩側的空格包含在變數引用名稱(在本例中為 'name')和變數值的分配中 [變數名可以包含空格!]。變數值的尾隨空格也會被分配,如果未檢測到,這會導致某些指令碼意外失敗。因此,建議將變數的分配用雙引號括起來
SET "name=John Smith"
通常使用 '%' 字元標記變數引用字串的開頭和結尾,在其他命令中使用變數。
ECHO %name%
在批處理指令碼中,使用 '%' 擴充套件展開的變數在執行該行或程式碼塊之前由直譯器 [解析]。因此,這意味著命令將使用變數在該程式碼段開始之前儲存的值執行。這在使用程式碼塊時尤其重要,因為變數的值在該程式碼塊內被重新分配。批處理指令碼中的程式碼塊是指使用 '&' 將多個命令連結到同一行,以及在任何括號程式碼中,例如 'IF' 語句和 'FOR' 迴圈。
下面是一個演示此問題存在的示例
@Echo off Set "count=0" For /L %%n in (1 1 3)Do ( Set /A count+=1 Echo %count% ) Echo %count%
輸出
0 0 0 3
有兩種方法可以解決這個問題。第一個也是最常用的方法是啟用延遲擴充套件,這允許使用 '!' (延遲) 擴充套件展開變數
@Echo off Set "count=0" SETLOCAL EnableExtensions EnableDelayedExpansion For /L %%n in (1 1 3)Do ( Set /A count+=1 Echo !count! ) Echo %count%
輸出
1 2 3 3
使用延遲擴充套件會導致變數在執行期間展開,而不是在直譯器最初解析命令時展開。
在程式碼塊中擴充套件變數當前值的第二種方法是使用 'CALL' 命令,在執行命令期間有效地重置解析器,以便讀取當前值。
@Echo off Set "count=0" For /L %%n in (1 1 5)Do ( Set /A count+=1 Call Echo %%count%% ) Echo %count%
呼叫方法速度明顯較慢,但對於處理可能包含 '!' 字元的字串很有用,直譯器在啟用延遲擴充套件時會嘗試將這些字元解析為變數。它不適合包含插入符號 '^' 的字串,因為呼叫命令會將呼叫後命令字串中出現的插入符號加倍。
使用延遲擴充套件允許使用具有唯一索引的公共引用名稱定義 '關聯' 變數,以模擬陣列。這是可能的,因為在啟用延遲擴充套件的情況下,變數的擴充套件分為兩個步驟。首先,展開 '%' 變數,然後展開 '!' 變數。例如
@Echo off
Set "str[1]=one,three,five"
Set "str[2]=two,four,six"
Setlocal EnableExtensions EnableDelayedExpansion
rem for each in 1 2
For %%i in (1 2)Do (
rem reset sub index variable
Set "{i}=0"
rem for each in variable str[index]
For %%G in (!str[%%i]!)Do (
rem increment sub index count
Set /A {i}+=1
rem define element '{i}' from str[index] to str[index][subindex]
Set "str[%%i][!{i}!]=%%G"
)
)
Set Str
Goto :Eof
輸出
str[1]=one,three,five str[1][1]=one str[1][2]=three str[1][3]=five str[2]=two,four,six str[2][1]=two str[2][2]=four str[2][3]=six
| 此頁面需要關注。您可以幫助 改進它,請求專案幫助,或檢視 當前進度。 |
set 命令也可以用於輸入
SET /P var=Enter a value for var:
此命令顯示 "Enter a value for var:",當用戶輸入資料時,var 將被賦予該值。
請注意,如果使用者按下 Enter 鍵但不輸入任何內容,則 var 中的值將保持不變,因此為了提示,最好給出預設值,或者如果變數之前被使用過,則先清除變數的值
SET var= SET /P var=Enter a value for var:
下面是一個示例
@ECHO OFF SET /P answer= Enter name of file to delete: DEL /P %answer% ECHO ON
此批處理檔案獲取要刪除的檔案的名稱,然後使用帶有提示引數 '/P' 的 DEL 命令詢問使用者是否確定要刪除該檔案。
條件語句
[edit | edit source]IF
[edit | edit source]IF 命令可以在批處理檔案中建立程式邏輯。IF 命令允許三種基本檢查:ERRORLEVEL、兩個字串的相等性和檔案或資料夾的存在性。對 ERRORLEVEL 的第一個檢查將檢視它是否大於或等於某個特定數字
IF ERRORLEVEL 5 ECHO.The ERRORLEVEL is at least 5.
對於這種樣式,第一個引數始終是 ERRORLEVEL,第二個引數是它檢查的值。在本例中,如果 ERRORLEVEL 至少為 5,那麼該行末尾的命令將被執行,輸出訊息“ERRORLEVEL 至少為 5”。第二種形式是在兩個字串之間進行檢查
IF "%str1%"=="Hello." ECHO.The strings are equal.
這裡,第一個引數是兩個字串,它們位於雙等號 (==) 的兩側,表示檢查它們是否相等。如果變數 str1 恰好等於“Hello.”,這是一個區分大小寫的檢查,那麼將輸出“字串相等”。如果你希望進行不區分大小寫的檢查,你可以按如下方式重寫它
IF /I "%str1%"=="Hello." ECHO.The strings are equal.
現在,例如,str1 可以包含“HELLO.”,但檢查仍然會導致該行末尾的命令被執行,因為檢查現在不區分大小寫。最後一種基本 IF 型別是存在性檢查,用於檢視檔案或資料夾是否存在。
IF EXIST myfile.txt TYPE myfile.txt
這裡,如果檔案“myfile.txt”存在於當前資料夾中,那麼命令 TYPE myfile.txt 將被執行,該命令將在控制檯視窗中顯示“myfile.txt”的內容。
所有前面的示例都有一個可選的 NOT 引數,可以在 IF 後面寫入,如果條件不為真,則將在該行末尾執行該命令。例如
IF NOT EXIST myfile.txt ECHO.File missing.
如果檔案“myfile.txt”不存在於當前資料夾中,則將輸出“檔案丟失”。還有一些其他的 IF 型別帶有命令擴充套件,你可以透過在命令提示符下使用 IF /? 命令檢視。
ELSE
[edit | edit source]ELSE 運算子可以與括號組合使用,以提供多行邏輯語句,如果條件不為真,則提供一組備用命令。
IF condition ( commands to be executed if the condition is true ) ELSE ( commands to be executed if the condition is false )
與一些語言不同,在批處理檔案中,指令碼要求 IF condition (、) ELSE ( 和 ) 行按這種非常特定的方式編寫。但是,可以將其重寫為在同一行上使用單行結果
IF condition (command if true) ELSE command if false
以下是一個 ELSE 運算子使用示例
@ECHO OFF ::Prompt for input. SET /P answer=Enter filename to delete: IF EXIST %answer% ( DEL /P %answer% ) ELSE ( ECHO.ERROR: %answer% can not be found in this folder! ) ECHO ON
這個批處理檔案將刪除一個檔案,除非它不存在,在這種情況下它會用訊息“ERROR: %answer% 在此資料夾中找不到!”告訴你。
與大多數計算機語言不同,多行 IF...ELSE 樣式語句不能巢狀在批處理檔案中。
跳轉
[edit | edit source]你可以使用 GOTO 語句來控制程式流程。批處理檔案沒有結構化程式設計指令碼的所有元素,但是可以模擬一些結構化程式設計元素,比如函式。但是,控制程式流程的最簡單方法是 GOTO 語句,它跳轉到指定的標籤。
GOTO labelnam
此程式碼將程式流程引導到標籤 labelnam,該標籤位於此行的第一次出現處
:labelnam
重要的是要記住,標籤只儲存 8 個字元,所以如果一個標籤超過 8 個字元,只有前 8 個字元會被看到。這意味著標籤 labelname1 和 labelname2 無法區分,因為它們的區別只出現在前 8 個字元之後。雖然並不嚴格錯誤,但最好避免使用超過 8 個字元的標籤名稱,以避免這些容易區分的問題。
以下是之前示例的重新設計版本,它會迴圈直到被要求停止
@ECHO OFF :prompt ::Clear the value of answer ready for use. SET answer= SET /P answer=Enter filename to delete (q to quit): IF EXIST %answer% ( DEL /P %answer% GOTO prompt ) IF /I "%answer%"=="q" GOTO :EOF ::By this point an error must have occurred as all ::the correct entries have already been dealt with. ECHO.ERROR: Incorrect entry! GOTO prompt ECHO ON
請注意 GOTO :EOF 命令。此命令將指令碼帶到檔案末尾並結束當前批處理指令碼。
FOR 迴圈
[edit | edit source]對一組檔案中的每個檔案執行指定命令。
FOR %variable IN (set) DO command [command-parameters]
%variable Specifies a single letter replaceable parameter.
(set) Specifies a set of one or more files. Wildcards may be used.
command Specifies the command to carry out for each file.
command-parameters
Specifies parameters or switches for the specified command.
要在批處理程式中使用 FOR 命令,請指定 %%variable 而不是 %variable。變數名稱區分大小寫,因此 %i 與 %I 不同。
批處理檔案示例
for %%F IN (*.txt) DO @echo %%F
此命令將列出當前目錄中所有以 .txt 結尾的檔案。
如果啟用了命令擴充套件,則支援以下 FOR 命令的額外形式
FOR /D %variable IN (set) DO command [command-parameters]
如果 set 包含萬用字元,則指定與目錄名稱而不是檔名匹配。
FOR /R [[drive:]path] %variable IN (set) DO command [command-parameters]
遍歷以 [drive:]path 為根的目錄樹,在樹中的每個目錄中執行 FOR 語句。如果在 /R 後沒有指定目錄規範,則假定為當前目錄。如果 set 只是一個句點 (.) 字元,那麼它只會列舉目錄樹。
FOR /L %variable IN (start,step,end) DO command [command-parameters]
set 是從 start 到 end 的一系列數字,步長為 step amount。因此 (1,1,5) 將生成序列 1 2 3 4 5,而 (5,-1,1) 將生成序列 (5 4 3 2 1)
FOR /F ["options"] %variable IN (file-set) DO command [command-parameters]
FOR /F ["options"] %variable IN ("string") DO command [command-parameters]
FOR /F ["options"] %variable IN ('command') DO command [command-parameters]
或者,如果存在 usebackq(或 useback)選項
FOR /F ["options"] %variable IN ("file-set") DO command [command-parameters]
FOR /F ["options"] %variable IN ('string') DO command [command-parameters]
FOR /F ["options"] %variable IN (`command`) DO command [command-parameters]
(usebackq 的目的是使用包含空格的檔案集的完整名稱。)
filenameset 是一個或多個檔名。每個檔案都會被開啟、讀取和處理,然後繼續處理 filenameset 中的下一個檔案。處理包括讀取檔案,將其分解成單獨的文字行,然後將每行解析成零個或多個標記。然後呼叫 for 迴圈的主體,並將變數值設定為找到的標記字串。預設情況下,/F 傳遞每個檔案每行中第一個空格分隔的標記。空行將被跳過。你可以透過指定可選的“options”引數來覆蓋預設的解析行為。這是一個包含一個或多個關鍵字以指定不同解析選項的引號字串。關鍵字是
eol=c - specifies an end of line comment character
(just one)
skip=n - specifies the number of lines to skip at the
beginning of the file.
delims=xxx - specifies a delimiter set. This replaces the
default delimiter set of space and tab.
tokens=x,y,m-n - specifies which tokens from each line are to
be passed to the for body for each iteration.
This will cause additional variable names to
be allocated. The m-n form is a range,
specifying the mth through the nth tokens. If
the last character in the tokens= string is an
asterisk, then an additional variable is
allocated and receives the remaining text on
the line after the last token parsed.
usebackq - specifies that the new semantics are in force,
where a back quoted string is executed as a
command and a single quoted string is a
literal string command and allows the use of
double quotes to quote file names in
filenameset.
一些示例可能會有所幫助
FOR /F "eol=; tokens=2,3* delims=, " %i in (myfile.txt) do @echo %i %j %k
將解析 myfile.txt 中的每一行,忽略以分號開頭的行,將每一行中的第 2 個和第 3 個標記傳遞給 for 主體,標記以逗號和/或空格分隔。請注意,for 主體語句引用 %i 以獲取第 2 個標記,%j 以獲取第 3 個標記,%k 以獲取第 3 個標記後的所有剩餘標記。對於包含空格的檔名,你需要用雙引號引用檔名。為了以這種方式使用雙引號,你還需要使用 usebackq 選項,否則雙引號將被解釋為定義要解析的文字字串。
%i 在 for 語句中被明確宣告,%j 和 %k 透過 tokens= 選項被隱式宣告。你可以透過 tokens= 行指定最多 26 個標記,前提是它不會導致嘗試宣告一個高於字母 'z' 或 'Z' 的變數。請記住,FOR 變數是單字母的、區分大小寫的、全域性的,並且在任何時候你都最多隻能有 52 個活動的 FOR 變數。
你還可以使用 FOR /F 解析邏輯在立即字串上,透過將括號之間的 filenameset 設為一個用單引號括起來的字串。它將被視為來自檔案的單行輸入並進行解析。
最後,你可以使用 FOR /F 命令來解析命令的輸出。你可以透過將括號之間的 filenameset 設為一個反引號字串來做到這一點。它將被視為一個命令列,傳遞給一個子 CMD.EXE,並且輸出會被捕獲到記憶體中並進行解析,就好像它是一個檔案一樣。因此,以下示例
FOR /F "usebackq delims==" %i IN (`set`) DO @echo %i
將列舉當前環境中的環境變數名稱。
此外,FOR 變數引用的替換也得到了增強。你現在可以使用以下可選語法
%~I - expands %I removing any surrounding quotes (")
%~fI - expands %I to a fully qualified path name
%~dI - expands %I to a drive letter only
%~pI - expands %I to a path only
%~nI - expands %I to a file name only
%~xI - expands %I to a file extension only
%~sI - expanded path contains short names only
%~aI - expands %I to file attributes of file
%~tI - expands %I to date/time of file
%~zI - expands %I to size of file
%~$PATH:I - searches the directories listed in the PATH
environment variable and expands %I to the
fully qualified name of the first one found.
If the environment variable name is not
defined or the file is not found by the
search, then this modifier expands to the
empty string
可以將修飾符組合起來以獲得複合結果
%~dpI - expands %I to a drive letter and path only
%~nxI - expands %I to a file name and extension only
%~fsI - expands %I to a full path name with short names only
%~dp$PATH:I - searches the directories listed in the PATH
environment variable for %I and expands to the
drive letter and path of the first one found.
%~ftzaI - expands %I to a DIR like output line
在上面的示例中,%I 和 PATH 可以用其他有效值替換。%~ 語法以有效的 FOR 變數名稱結尾。選擇大寫變數名稱,比如 %I,可以使它更易讀,並避免與修飾符(不區分大小寫)混淆。
管道
[edit | edit source]這主要用於將一個程式的輸出重定向到另一個程式
a | b
表示執行“a”,並將“a”提供給控制檯的所有輸出作為“b”的輸入
dir | find ".htm"
將提供一個包含“.htm”的檔案列表
命令的輸出以及可能發生的錯誤也可以重定向到檔案(注意 >> 前面的 2)
ACommand >>TheOutputOfTheCommandLogFile.log 2>>TheErrorOutputOfTheCommandFile.log
函式
[edit | edit source]可以使用標籤來控制執行流程,並使用環境變數來返回結果返回值,從而模擬函式。 標籤可以在指令碼中的任何位置定義,不需要引用。 當遇到標籤後的程式碼時,將執行該程式碼。 由標籤標識的程式碼塊通常最方便地放置在主指令碼退出後(檔案末尾),這樣它們就不會意外執行,而只會在 GOTO 或 CALL 語句的目標時才會到達。
子程式結構之所以有效,是因為以下原因
- 可以使用內建命令 GOTO 跳轉到由標籤標識的程式碼
- 使用帶有標籤的內建命令 CALL 會為同一個指令碼建立一個新的命令處理器呼叫,並隱式 GOTO 該標籤
- 使用表示式 GOTO :EOF 會關閉當前命令處理器呼叫(與 EXIT /B 相同,只是後者允許指定 ERRORLEVEL)
子程式呼叫的結構如下所示
CALL :subroutine1 param1 param2 ... ECHO %result% was returned from subroutine1 CALL :subroutine2 param1 param2 ... ECHO %result% was returned from subroutine2 GOTO :EOF REM The above line ends the main invocation of the command processor and so exits the script :subroutine1 SETLOCAL commands using parameters %1, %2, .... and setting %retval% ENDLOCAL & SET result=%retval% GOTO:EOF REM The above line ends the child invocation of the command processor and so returns to just after CALL subroutine1 in the main script :subroutine2 SETLOCAL commands using parameters %1, %2, .... and setting %retval% ENDLOCAL & SET result=%retval% GOTO:EOF REM The above line ends the child invocation of the command processor and so returns to just after CALL subroutine2 in the main script
具有以下內容的 Bat 檔案將輸出“42”作為執行結果
:: describes and calls function for multiplication with 2 arguments @ECHO OFF CALL :multiply 21 2 ECHO %result% :multiply SETLOCAL set retval=0 set left=%1 set right=%2 :: use '/A' for arithmetic set /A "retval=left*right" ENDLOCAL & SET result=%retval% GOTO :EOF
注意
- 使用帶有標籤的 CALL 或表示式 CALL :EOF 或 EXIT /B 需要啟用命令擴充套件。
- 標籤是一行,由一個有效名稱(不包含任何分隔符,例如空格或分號)開頭,並以冒號開頭。
命令列介面
[edit | edit source]假設我們要從命令提示符呼叫程式“MyProgram”。 我們在提示符中鍵入以下內容(.exe 副檔名不必要)
C:\>myprogram.exe
這將執行 myprogram 可執行檔案。 現在,假設我們要向該程式傳遞一些引數
C:\>myprogram arg1 arg2 arg3
現在,如果我們進入標準 main 函式,我們將擁有 argc 和 argv 值
int main(int argc, char *argv[])
其中
argc = 4 argv[0] = "myprogram" (the name of the program - deduct 1 from argc to get the number of arguments) argv[1] = "arg1" argv[2] = "arg2" argv[3] = "arg3"
對於熟悉標準 C 程式設計的人來說,這並不奇怪。 但是,如果我們將此轉換為 WinMain 函式,我們將得到不同的值
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR CmdLine, int iCmdShow)
我們只有一個值來接受命令列
CmdLine = "myprogram arg1 arg2 arg3".
我們還可以使用函式 **GetCommandLine** 從應用程式中的任何位置檢索此字串。 如果我們想將其解析為標準的 argv/argc 對,我們可以使用函式 **CommandLineToArgvW** 來執行轉換。 重要的是要注意,CommandLineToArgvW 僅適用於 unicode 字串。
當我們從 C 程式返回一個值時,該值將傳遞給 CMD shell,並存儲在一個名為“ERRORLEVEL”的變數中。 ERRORLEVEL 是唯一一個不是字串的全域性變數,它可以包含一個從 0 到 255 的數字。 按照慣例,值為零表示“成功”,而其他值則表示錯誤。
假設我們要編寫一個 C 程式來返回傳遞給它的引數數量。 這在 C 中聽起來像是一個簡單的任務,但在批處理指令碼中很難實現
int main(int argc, char *argv[])
{
return (argc - 1);
}
我們將這個程式命名為“CountArgs.exe”。 現在,我們可以將其放入批處理指令碼中,傳遞一些引數,並打印出傳遞的數量
countargs.exe %* ECHO %ERRORLEVEL%
我們可以將此指令碼命名為“count.bat”,並從命令提示符執行它
C:\>count.bat arg1 arg2 arg3
執行它將返回答案:3。
注意:實際上,這可以透過批處理檔案實現,而無需使用 C 程式,只需使用 CMD 延遲變數擴充套件透過“/V:ON”引數即可
/V:ON Enable delayed environment variable expansion using ! as the delimiter. For example, /V:ON would allow !var! to expand the variable var at execution time. The var syntax expands variables at input time, which is quite a different thing when inside of a FOR loop.
然後使用以下簡單的批處理檔案來計算引數
set COUNT=0 for %%x in (%*) do ( set /A COUNT=!COUNT!+1 ) echo %COUNT%
或者一個更簡單的方法,無需啟用和使用“延遲環境擴充套件”,只需執行以下操作
set COUNT=0 for %%x in (%*) do set /A COUNT+=1 echo COUNT = %COUNT%
這將返回與 C 程式示例相同的答案。