簡體   English   中英

批量轉到丟失錯誤級別

[英]Batch goto loses errorlevel

考慮以下bat,test.bat(PC01關閉):

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

如果我從命令shell運行該bat:

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

輸出只有55.沒有99.有一個錯誤級別,但是|| 操作員沒有看到它。

如果我用cmd /c -運行那個蝙蝠cmd /c -

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

輸出為空白。 Errorlevel為0。

如果我刪除|| goto :eof || goto :eof ,一切都按照人們的預測 - 即輸出即可

99 55

有誰知道為什么這種半生半熟的ERRORLEVEL行為正在發生?

在大多數情況下, || 是檢測錯誤的最可靠方法 但你偶然發現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的成功,並且錯誤將再次被屏蔽。

test.bat||echo 99之后,ERRORLEVEL仍為非零,因為GOTOREM類的命令在成功時不會清除任何先前的非零ERRORLEVEL。 這是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

如果沒有CALLCMD /C將返回上次執行的命令的返回碼,即GOTO :EOF CMD /C還將ERRORLEVEL設置為相同的返回碼,因此現在沒有證據表明腳本中存在錯誤。

我們走了兔洞

RLH在他的回答和他對我的回答的評論中擔心|| 有時會清除ERRORLEVEL。 他提供的證據似乎支持了他的結論。 但情況並非如此簡單,事實證明|| 是檢測錯誤的最可靠(但仍然不完美)的方法。

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

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

這甚至記錄在EXIT幫助中的exitCode定義中
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運行外部命令時,它會檢測可執行文件的返回碼並將ERRORLEVEL設置為匹配。 請注意,只有約定0表示成功,非零表示錯誤。 某些外部命令可能不遵循該約定。 例如,HELP命令(help.exe)不遵循約定 - 如果您在help bogus指定了無效命令,則返回0,但如果您在有效命令上請求幫助,則返回1,如help rem

|| 執行外部命令時,operator永遠不會清除ERRORLEVEL。 檢測到進程退出代碼並觸發|| 如果它不為零,則ERRORLEVEL仍將匹配退出代碼。 話雖如此,在&&和/或||之后出現的命令 可能會修改ERRORLEVEL,所以必須要小心。

但除了外部命令之外還有許多其他情況,我們開發人員關心成功/失敗和返回代碼/ ERRORLEVEL。

  • 執行內部命令
  • 重定向運算符<>>>
  • 執行批處理腳本
  • 執行無效命令失敗

不幸的是,CMD.EXE在如何處理這些情況的錯誤條件方面並不完全一致。 CMD.EXE有多個內部點,它必須檢測錯誤,可能是通過某種形式的內部返回代碼,不一定是ERRORLEVEL,並且在這些點中的每一個CMD.EXE都可以根據它找到的內容設置ERRORLEVEL 。

對於下面的測試用例,請注意(call ) ,帶有空格,是一種神秘的語法,在每次測試之前將ERRORLEVEL清除為0。 稍后,我還將使用(call) ,沒有空格,將ERRORLEVEL設置為1

另請注意,我的命令會話中已啟用延遲擴展
cmd /v: on運行測試之前cmd /v: on

絕大多數內部命令在失敗時將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

如果刪除失敗,請參閱“rd”退出,錯誤級別設置為0,等等,以及Windows中的文件重定向和%errorlevel%以獲取更多信息。

我知道一個內部命令(可能還有其他命令)加上可以向stderr發出錯誤消息的基本失敗I / O操作,但它們不會觸發|| 他們也沒有設置非零ERRORLEVEL。

如果文件是只讀的,或者不存在,DEL命令可以打印錯誤,但它不會觸發|| 或者將ERRORLEVEL設置為非零

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

有關DEL錯誤的更多信息,請參閱https://stackoverflow.com/a/32068760/1012053

以同樣的方式,當stdout成功重定向到USB設備上的文件,但是在ECHO之類的命令嘗試寫入設備之前移除設備,則ECHO將失敗並向stderr發送錯誤消息,但是|| 不會觸發,並且ERRORLEVEL未設置為非零。 有關詳細信息,請參閱http://www.dostips.com/forum/viewtopic.php?f=3&t=6881

然后我們有一個批處理腳本被執行的情況 - 這是OP問題的實際主題。 沒有CALL|| 運算符響應腳本中執行的最后一個命令。 使用CALL|| 運算符響應CALL命令返回的值,這是批量終止時存在的最終ERRORLEVEL。

最后,我們有RLH報告的情況,其中無效命令通常報告為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始終設置為9009,但有1不同的返回碼|| 隨后可以檢測到1個返回碼並將ERRORLEVEL設置為匹配,從而覆蓋9009。

無論如何,我不知道任何其他情況,根據是否||非零ERRORLEVEL結果會有所不同 是否使用過。

這樣可以處理命令失敗時發生的事情。 但是內部命令何時成功呢? 不幸的是,CMD.EXE甚至比錯誤更不一致。 它因命令而異,也可能取決於它是從命令提示符,帶有.bat擴展名的批處理腳本還是帶有.cmd擴展名的批處理腳本執行的。

我基於以下關於Windows 10行為的所有討論。 我懷疑使用cmd.exe的早期Windows版本存在差異,但這是可能的。

無論上下文如何,以下命令在成功時始終將ERRORLEVEL清除為0:

  • CALL:如果CALLed命令沒有設置它,則清除ERRORLEVEL。
    示例: call echo OK
  • 光盤
  • CHDIR
  • 顏色
  • 復制
  • 日期
  • DEL:即使DEL失敗,也始終清除ERRORLEVEL
  • DIR
  • 刪除:即使ERASE失敗,也始終清除ERRORLEVEL
  • MD
  • MKDIR
  • MKLINK
  • 移動
  • PUSHD
  • REN
  • 改名
  • SETLOCAL
  • 時間
  • 類型
  • VER
  • 校驗
  • VOL

成功時,下一組命令永遠不會將ERRORLEVEL清除為0,無論上下文如何,而是保留任何現有的非零值ERRORLEVEL:

  • 打破
  • CLS
  • 回聲
  • ENDLOCAL
  • EXIT:顯然EXIT /B 0清除了ERRORLEVEL,但沒有值的EXIT /B保留了先前的ERRORLEVEL。
  • 對於
  • 如果
  • KEYS
  • 暫停
  • POPD
  • RD
  • REM
  • RMDIR
  • 轉移
  • 開始
  • 標題

然后,如果從命令行或具有.bat擴展名的腳本中發出,則這些命令在成功時不會清除ERRORLEVEL,但如果從具有.cmd擴展名的腳本發出,則將ERRORLEVEL清除為0。 有關詳細信息,請參閱https://stackoverflow.com/a/148991/1012053https://groups.google.com/forum/#!msg/microsoft.public.win2000.cmdprompt.admin/XHeUq8oe2wk/LIEViGNmkK0J

  • ASSOC
  • DPATH
  • FTYPE
  • 路徑
  • 提示

無論任何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。 但是有一些罕見的例外。

  • || 正確設置ERRORLEVEL,否則在RD或重定向失敗時會錯過
  • || 在執行無效命令失敗時設置不同的ERRORLEVEL,如果||則會發生 未使用(1對9009)。

最后, || 除非使用了CALL命令,否則不會將批處理腳本返回的非零ERRORLEVEL檢測為錯誤。

如果你嚴格依賴於if errorlevel 1 ...或者if %errorlevel% neq 0 ...來檢測錯誤,那么你冒着丟失RD和重定向(以及其他?)可能拋出的錯誤的風險,你也運行錯誤地認為某些內部命令失敗的風險實際上它可能是先前失敗的命令的保留。

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

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

:exit
(call)

現在你可以使用了

test.bat || echo Failed

這里唯一的缺點是錯誤級別的丟失。
在這種情況下,返回代碼設置為falsefalse級別始終設置為1 ,由無效(call)
目前我找不到任何可能的方法來設置錯誤級別和返回代碼到用戶定義的值

真正的Errorlevel不會在雙管道(失敗時)運算符中存在。

請改用邏輯。 例如,下面是您可以執行此操作的一種方法:

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

編輯:

如果此前的命令不是單個命令,則必須執行更多操作來跟蹤errorlevel。 例如,如果您調用腳本,那么在該腳本中您必須管理將errorlevel傳遞回此代碼。

編輯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

所以,如果你關心的是“Something”失敗,那么好的。 代碼1和其他任何東西一樣好。 但是如果你需要知道“什么”失敗了,那么你不能只是失敗管道並讓它消滅實際結果。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM