2016-01-21 259 views
8

考慮以下蝙蝠,test.bat的(PC01是關閉):批次轉到失去錯誤級別

mkdir \\PC01\\c$\Test || goto :eof 

如果我請從命令外殼,蝙蝠:

> test.bat || echo 99 
> if ERRORLEVEL 1 echo 55 

輸出僅僅是55沒有99.有一個錯誤級別,但'||'運營商沒有看到它。

如果我運行CMD使用/ C蝙蝠 -

> cmd /c test.bat || echo 99 
> if ERRORLEVEL 1 echo 55 

輸出是空白。 Errorlevel爲0.

如果我刪除「||」 goto:eof',一切都按照人們預測的那樣工作 - 即輸出將是99 55

有誰知道爲什麼這個半出爐的半衰期ERRORLEVEL行爲發生?

回答

13

在大多數情況下,|| 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的成功,並且錯誤將再次被屏蔽。

由於諸如GOTOREM之類的命令在成功之後不會清除任何之前的非零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 :EOFCMD /C也將ERRORLEVEL設置爲相同的返回碼,所以現在沒有證據表明腳本中曾經有過錯誤。

和向下的兔子洞,我們去

R.L.H.,在他的回答和他的評論是我的答案,關注的是,有時||清除ERRORLEVEL。他提供的證據似乎支持他的結論。但情況並非如此簡單,事實證明||是檢測錯誤最可靠的方法(但仍不完善)。

正如前面所述,所有外部命令在退出時返回的返回碼與cmd.exe ERRORLEVEL不同。

ERRORLEVEL是在cmd.exe會話本身內維護的狀態,與返回碼完全不同。

這甚至被記錄在退出碼的定義退出幫助
help exitexit /?

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 wellFile 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/1012053https://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和重定向(和其他人?)可能會引發錯誤的風險,你也跑誤以爲某些內部命令失敗時的風險實際上它可能是一個先前命令失敗的結果。

+0

它不僅調用批處理文件。我在單個批處理文件中得到了與錯誤級別1相同的測試結果。 '<失敗的命令> || echo%errorlevel%'然後是下一行'echo%errorlevel%',第一個和第二個不同。 – RLH

+0

嘿確實'呼叫'確實使||工作!我堅持使用CMD/C,因爲我從C#啓動..等待..看起來像我可以使用'呼叫test.bat'從C#... – Patrick

+0

@RLH - 這是因爲'%errorlevel%'擴大時該命令被解析,並且整行被一次解析。您必須使用延遲擴展(必須先啓用)來執行您嘗試過的操作' || echo!errorlevel!'完美地工作 – dbenham

1

真正的錯誤級別無法在雙管道(失敗)運營商中生存。

改爲使用邏輯。例如,下面是你可以做的一種方式:

mkdir \\PC01\c$\Test 
if "%errorlevel%" == "0" (
echo success 
) else (
echo failure 
) 

編輯:

如果之前這個命令不是一個命令,然後更多的已經做追蹤錯誤級別。例如,如果您調用腳本,那麼在該腳本中您必須管理將錯誤級別傳遞迴此代碼。

編輯2:

下面是測試場景,其中ERRORLEVEL應該是 '9009' 和它們的輸出。

1)沒有故障管道,使用邏輯。

setlocal enableDelayedExpansion 
mybad.exe 
if "!errorlevel!" == "0" (
echo success !errorlevel! 
) else (
echo Errorlevel is now !errorlevel! 
echo Errorlevel is now !errorlevel! 
) 

「mybad.exe」不被識別爲內部或外部的命令, 運行的程序或批處理文件。

ERRORLEVEL現在9009

ERRORLEVEL現在9009

2)故障管道,回聲

setlocal enableDelayedExpansion 
mybad.exe || echo Errorlevel is now !errorlevel! 
echo Errorlevel is now !errorlevel! 

'mybad.exe' 沒有被識別爲一個內部或外部命令, 可操作的程序或批處理文件。

ErrorLevel被立刻1

ErrorLevel被1

3)故障管道,轉到

setlocal enableDelayedExpansion 
mybad.exe || goto :fail 
exit 
:fail 
echo Errorlevel is now !errorlevel! 

'mybad.exe' 沒有被識別爲一個內部或外部命令, 可操作的程序或批處理文件。

ERRORLEVEL現在是1

所以,如果你關心的是 「東西」 失敗,然後確定。代碼1與其他任何東西一樣好。但是如果你需要知道「什麼」失敗了,那麼你不能只是讓管道失靈,讓它消除實際結果。

+0

該||在test.bat中的mkdir工作完好後 – Patrick

+0

我對各種命令的測試沒有給出一致的好結果,所以從我的角度來看它不可靠。爲了測試,將後續命令更改爲'echo%errorlevel%'而不是您擁有的。這會告訴你每個實例會發生什麼。我發佈的東西總是有用的。但是,如果您要調用批處理文件,請記住將錯誤級別從出口語句中傳出,如'exit%errorlevel%'。 – RLH

+0

看起來像是||那會'吃掉',而不是goto。然而,錯誤級別在蝙蝠內部仍然存在,它只是不會被返回。就像內存中有兩個錯誤級別 – Patrick

3

要在使用goto :eof時構建批處理文件以設置返回碼的解決方案,您可以稍微更改腳本。

mkdir \\\failure || goto :EXIT 
echo Only on Success 
exit /b 

:exit 
(call) 

現在你可以使用

test.bat || echo Failed 

唯一的缺點這裏是ERRORLEVEL的損失。
在這種情況下,返回代碼設置爲false但錯誤級別始終設置爲1,由無效(call)
目前我無法找到任何可能的方式來設定兩者的錯誤級別和返回代碼,以用戶定義的值

+0

謝謝jeb。是的,看看我的修復是總是用cmd/c調用xx.bat調用bat文件。 '通話'是魔術膠。我的情況是,蝙蝠的調用是在我的控制之下,蝙蝠文件的內容不是。 – Patrick

+1

+1,謝謝jeb。您的回答迫使我重新檢查'||'如何在沒有CALL的情況下響應批處理腳本。它允許我重新發現沒有CALL的'||'響應腳本中最後執行的命令。這是我之前所知道的,但卻被遺忘了。我編輯了我的答案,以正確解釋使用CALL和不使用CALL之間的區別。 – dbenham