在大多數情況下,||
is the most reliable way to detect an error。但是你偶然發現了一個ERRORLEVEL工作的罕見情況,但||
沒有。
問題源於這樣一個事實,即您的錯誤在批處理腳本中引發,||
響應最近執行的命令的返回代碼。你正在考慮test.bat作爲一個單一的「命令」,但實際上它是一系列命令。在腳本中執行的最後一條命令是GOTO :EOF
,並且執行成功。所以你的test.bat||echo 99
正在迴應GOTO :EOF
的成功。
當您從腳本中刪除||GOTO :EOF
時,您的test.bat||echo99
將看到失敗的結果mkdir
。但是,如果要在test.bat結尾添加REM
命令,則test.bat||echo 99
將響應REM
的成功,並且錯誤將再次被屏蔽。
由於諸如GOTO
和REM
之類的命令在成功之後不會清除任何之前的非零ERRORLEVEL,所以ERRORLEVEL在test.bat||echo 99
之後仍然不爲零。這是ERRORLEVEL和返回代碼不完全相同的許多證據之一。它肯定會讓人困惑。
您可以將test.bat作爲單位命令並通過使用CALL來獲取所需的行爲。
C:\test>call test.bat && echo OK || echo FAIL
FAIL
C:\test>if ERRORLEVEL 1 (echo FAIL2) else echo OK2
FAIL2
這是可行的,因爲CALL
命令臨時將控制轉移到被調用的腳本。腳本終止時,控制返回到CALL
命令,並返回當前的ERRORLEVEL。因此||echo 99
正在響應CALL命令本身返回的錯誤,而不是腳本中的最後一個命令。
現在爲CMD /C
問題。
CMD /C
返回的返回碼是最後執行的命令的返回碼。
這工作:
C:\test>cmd /c call test.bat && echo OK || echo FAIL
FAIL
C:\test>if ERRORLEVEL 1 (echo FAIL2) else echo OK2
FAIL2
因爲CMD /C
返回由CALL
聲明
返回的ERRORLEVEL但這完全失敗:
C:\test>cmd /c test.bat && echo OK || echo FAIL
OK
C:\test>if ERRORLEVEL 1 (echo FAIL2) else echo OK2
OK2
沒有CALL
,在CMD /C
返回的返回碼最後執行的命令是GOTO :EOF
。 CMD /C
也將ERRORLEVEL設置爲相同的返回碼,所以現在沒有證據表明腳本中曾經有過錯誤。
和向下的兔子洞,我們去
R.L.H.,在他的回答和他的評論是我的答案,關注的是,有時||
清除ERRORLEVEL。他提供的證據似乎支持他的結論。但情況並非如此簡單,事實證明||
是檢測錯誤最可靠的方法(但仍不完善)。
正如前面所述,所有外部命令在退出時返回的返回碼與cmd.exe ERRORLEVEL不同。
ERRORLEVEL是在cmd.exe會話本身內維護的狀態,與返回碼完全不同。
這甚至被記錄在退出碼的定義退出幫助
(help exit
或exit /?
)
EXIT [/B] [exitCode]
/B specifies to exit the current batch script instead of
CMD.EXE. If executed from outside a batch script, it
will quit CMD.EXE
exitCode specifies a numeric number. if /B is specified, sets
ERRORLEVEL that number. If quitting CMD.EXE, sets the process
exit code with that number.
當外部命令由CMD.EXE運行中,它檢測到executeable的返回碼將ERRORLEVEL設置爲匹配。請注意,這只是一個約定,0表示成功,非零意味着錯誤。一些外部命令可能不符合該慣例。例如,HELP命令(help.exe)不遵循約定 - 如果您指定一個無效的命令(如help bogus
),則返回0,但如果您要求獲得有效命令的幫助,則返回1,如help rem
。當執行外部命令時,||
運算符不會清除ERRORLEVEL。檢測到進程退出代碼,如果它不爲零,則觸發||
,並且ERRORLEVEL仍將匹配退出代碼。也就是說,出現在&&
和/或||
之後的命令可能會修改ERRORLEVEL,所以必須小心。
但除了外部命令外,還有許多其他情況,我們開發人員關心的是成功/失敗和返回碼/ ERRORLEVEL。
- 內部命令的執行
- 重定向操作符
<
,>
和批處理腳本的>>
- 執行
- 失敗無效的命令
不幸的執行,CMD.EXE不在在處理這些情況的錯誤條件方面都一致。 CMD.EXE有多個內部點,它必須檢測錯誤,可能通過某種形式的內部返回碼(不一定是ERRORLEVEL),並且在每個點上CMD.EXE都可以設置ERRORLEVEL,取決於它發現的內容。
對於我下面的測試用例,請注意(call)
帶有一個空格,它是在每次測試之前將ERRORLEVEL清除爲0的神祕語法。後來,我也將運行我的測試之前
cmd /v: on
使用(call)
,沒有空間,ERRORLEVEL設置成1
還要注意,推遲擴張一直是我的命令會話中啓用使用
絕大多數內部命令在發生故障時將ERRORLEVEL設置爲非零值,並且錯誤條件也會觸發||
。在這些情況下,||
從不清除或修改ERRORLEVEL。 這裏有幾個例子:
C:\test>(call) & set /a 1/0
Divide by zero error.
C:\test>echo !errorlevel!
1073750993
C:\test>(call) & type notExists
The system cannot find the file specified.
C:\test>echo !errorlevel!
1
C:\test>(call) & set /a 1/0 && echo OK || echo ERROR !errorlevel!
Divide by zero error.
ERROR 1073750993
C:\test>(call) & type notExists.txt && echo OK || echo ERROR !errorlevel!
The system cannot find the file specified.
ERROR 1
再就是在誤差爲至少一個命令,RD,(可能更多),以及重定向操作符火||
,但除非使用||
不設置ERRORLEVEL 。
C:\test>(call) & rd notExists
The system cannot find the file specified.
C:\test>echo !errorlevel!
0
C:\test>(call) & echo x >\badPath\out.txt
The system cannot find the path specified.
C:\test>echo !errorlevel!
0
C:\test>(call) & rd notExists && echo OK || echo ERROR !errorlevel!
The system cannot find the file specified.
ERROR 2
C:\test>(call) & echo x >\badPath\out.txt && echo OK || echo ERROR !errorlevel!
The system cannot find the path specified.
ERROR 1
見batch: Exit code for "rd" is 0 on error as well和File redirection in Windows and %errorlevel%以獲取更多信息。
我知道一個內部命令(可能有其他)以及基本的失敗I/O操作,它們可以向stderr發出錯誤消息,但它們不會觸發||
,也不會設置非零的ERRORLEVEL。
如果只讀的文件,或者不存在的DEL命令可以打印一個錯誤,但它不火||
或者設置錯誤級別爲非零
C:\test>(call) & del readOnlyFile
C:\test\readOnlyFile
Access is denied.
C:\test>echo !errorlevel!
0
C:\test>(call) & del readOnlyFile & echo OK || echo ERROR !errorlevel!
C:\test\readOnlyFile
Access is denied.
OK
一個多一點信息,請參閱https://stackoverflow.com/a/32068760/1012053與DEL錯誤有關。
以幾乎相同的方式,當標準輸出已成功重定向到USB設備上的文件,但在ECHO等命令嘗試寫入設備之前移除設備時,ECHO將失敗,錯誤消息到stderr,但||
不會觸發,並且ERRORLEVEL未設置爲非零。有關更多信息,請參見http://www.dostips.com/forum/viewtopic.php?f=3&t=6881。
然後,我們有一個批處理腳本被執行的情況 - 這是OP問題的實際主題。如果沒有CALL
,||
操作員響應腳本內執行的最後一個命令。對於CALL
,||
運算符響應CALL
命令返回的值,該命令是批處理終止時存在的最後一個ERRORLEVEL。
最後,我們有這樣的情況,即R.L.H.報告中,無效命令通常報告爲ERRORLEVEL 9009,但如果使用||
則報告爲ERRORLEVEL 1。
C:\test>(call) & InvalidCommand
'InvalidCommand' is not recognized as an internal or external command,
operable program or batch file.
C:\test>echo !errorlevel!
9009
C:\test>(call) & InvalidCommand && echo OK || echo ERROR !errorlevel!
'InvalidCommand' is not recognized as an internal or external command,
operable program or batch file.
ERROR 1
我不能證明這一點,但我懷疑,命令失敗和ERRORLEVEL的設置的檢測,以9009發生在命令執行過程中非常晚。我猜||
在9009設置之前攔截錯誤檢測,此時它將它設置爲1。所以我不認爲||
正在清除9009錯誤,而是它是錯誤處理和設置的替代路徑。
無論如何,我不知道任何其他情況下,根據是否使用||
,非零ERRORLEVEL結果不同。
因此,照顧一個命令失敗時會發生什麼。但是當內部命令成功時呢?不幸的是,CMD.EXE比錯誤更不一致。它因命令而異,也可能取決於它是從命令提示符處執行的,還是從具有.bat
擴展名的批處理腳本中執行的,還是從具有.cmd
擴展名的批處理腳本執行的。
我基於以下關於Windows 10行爲的所有討論。我懷疑與使用cmd.exe的早期Windows版本存在差異,但這是可能的。
下列命令總是成功時清除ERRORLEVEL爲0,而不管上下文的:
- CALL:清除ERRORLEVEL如果被叫命令不否則設置它。
例子:call echo OK
- CD
- CHDIR
- COLOR
- COPY
- DATE
- DEL:總是清除ERRORLEVEL,即使DEL失敗
- DIR
- ERASE:總是清除ERRORLEVEL,即使ERASE失敗
- MD
- MKDIR
- MKLINK
- MOVE
- PUSHD
- REN
- RENAME
- SETLOCAL
- TIME
- TYPE
- VER
- 校驗
- VOL
下一個命令集從未在成功清除ERRORLEVEL爲0,而不管上下文,而是保留所有現有的非零值ERRORLEVEL:
- BREAK
- CLS
- ECHO
- ENDLOCAL
- EXIT:顯然
EXIT /B 0
清除ŧ他ERRORLEVEL,但沒有值的EXIT /B
保留了先前的ERRORLEVEL。
- FOR
- GOTO
- IF
- KEYS
- PAUSE
- POPD
- RD
- REM
- RMDIR
- SHIFT
- START
- TITLE
然後還有這些命令是做成功時不明確ERRORLEVEL如果在命令行或用.bat
擴展名的腳本內發出,但做清除ERRORLEVEL爲0;如果從腳本發出擴展名爲.cmd
。有關更多信息,請參閱https://stackoverflow.com/a/148991/1012053和https://groups.google.com/forum/#!msg/microsoft.public.win2000.cmdprompt.admin/XHeUq8oe2wk/LIEViGNmkK0J。
- ASSOC
- DPATH
- FTYPE
- PATH
- PROMPT
- SET
不管任何ERRORLEVEL值時,&&
操作者檢測是否先前命令是成功的,如果是的話只執行後續的命令。 &&
運算符忽略ERRORLEVEL的值,並且永遠不會修改它。
以下是兩個示例,顯示&&
總是在先前命令成功時觸發,即使ERRORLEVEL不爲零。 CD命令是一個例子,其中該命令清除任何先前的ERRORLEVEL,並且ECHO命令是其中命令不清除先前的ERRORLEVEL的示例。 請注意,我使用(call)
強制ERRORLEVEL爲1,然後發出命令。
C:\TEST>(call)
C:\TEST>echo !errorlevel!
1
C:\test>(call) & cd \test
C:\test>echo !errorlevel!
0
C:\test>(call) & cd \test && echo OK !errorlevel! || echo ERROR !errorlevel!
OK 0
C:\test>(call) & echo Successful command
Successful command
C:\test>echo !errorlevel!
1
C:\test>(call) & echo Successful command && echo OK !errorlevel! || echo ERROR !errorlevel!
Successful command
OK 1
在我所有用於錯誤檢測的代碼示例中,我都依賴於ECHO從不清除先前存在的非零ERRORLEVEL的事實。但下面的腳本是在&&
或||
之後使用其他命令時可能發生的情況的一個示例。
@echo off
setlocal enableDelayedExpansion
(call)
echo ERRORLEVEL = !errorlevel!
(call) && echo OK !errorlevel! || echo ERROR !errorlevel!
(call) && (echo OK !errorlevel! & set "err=0") || (echo ERROR !errorlevel! & set "err=1" & echo ERROR !errorlevel!)
echo ERRORLEVEL = !errorlevel!
echo ERR = !ERR!
這裏是輸出時,腳本的.bat
擴展:
C:\test>test.bat
ERRORLEVEL = 1
ERROR 1
ERROR 1
ERROR 1
ERRORLEVEL = 1
ERR = 1
這裏是輸出時,腳本的.cmd
擴展:
C:\test>test.cmd
ERRORLEVEL = 1
ERROR 1
ERROR 1
ERROR 0
ERRORLEVEL = 0
ERR = 1
請記住,每一個執行命令有可能改變ERRORLEVEL。因此,儘管&&
和||
是檢測命令成功或失敗的最可靠方法,但如果您關心ERRORLEVEL值,則必須注意那些運算符後使用的命令。
現在是時候爬出這個發臭的兔子洞,並得到一些新鮮空氣!
那麼我們學到了什麼?
沒有一種完美的方法可以檢測任何命令是否成功或失敗。但是,&&
和||
是檢測成功和失敗的最可靠方法。
通常,&&
和||
都不能直接修改ERRORLEVEL。但有一些罕見的例外。
||
正確地設置時RD或重定向失敗
||
設置在無效命令的執行失敗不同ERRORLEVEL然後將發生如果||
未使用否則將被錯過ERRORLEVEL(1比9009) 。
最後,||
不檢測由批處理腳本作爲錯誤返回非零ERRORLEVEL除非使用CALL命令。
如果嚴格依靠if errorlevel 1 ...
或if %errorlevel% neq 0 ...
檢測錯誤,然後運行缺少RD和重定向(和其他人?)可能會引發錯誤的風險,你也跑誤以爲某些內部命令失敗時的風險實際上它可能是一個先前命令失敗的結果。
它不僅調用批處理文件。我在單個批處理文件中得到了與錯誤級別1相同的測試結果。 '<失敗的命令> || echo%errorlevel%'然後是下一行'echo%errorlevel%',第一個和第二個不同。 – RLH
嘿確實'呼叫'確實使||工作!我堅持使用CMD/C,因爲我從C#啓動..等待..看起來像我可以使用'呼叫test.bat'從C#... – Patrick
@RLH - 這是因爲'%errorlevel%'擴大時該命令被解析,並且整行被一次解析。您必須使用延遲擴展(必須先啓用)來執行您嘗試過的操作' || echo!errorlevel!'完美地工作 –
dbenham