批量转到丢失错误级别

Pat*_*ick 12 windows cmd batch-file

考虑以下bat,test.bat(PC01关闭):

mkdir \\PC01\\c$\Test || goto :eof
Run Code Online (Sandbox Code Playgroud)

如果我从命令shell运行该bat:

> test.bat || echo 99
> if ERRORLEVEL 1 echo 55
Run Code Online (Sandbox Code Playgroud)

输出只有55.没有99.有一个错误级别,但||操作员没有看到它.

如果我用那个蝙蝠跑 cmd /c -

> cmd /c test.bat || echo 99
> if ERRORLEVEL 1 echo 55
Run Code Online (Sandbox Code Playgroud)

输出为空白.Errorlevel为0.

如果我删除了|| goto :eof,一切都按照人们的预测 - 即输出即可

99 55

有谁知道为什么这种半生半熟的ERRORLEVEL行为正在发生?

dbe*_*ham 23

在大多数情况下,||检测错误是最可靠的方法.但是你偶然发现了ERRORLEVEL工作但很少发生的罕见情况||.

问题源于您在批处理脚本中引发错误,并||响应最近执行的命令的返回代码.您正在考虑将test.bat作为单个"命令",但实际上它是一系列命令.脚本中执行的最后一个命令是GOTO :EOF,并且执行成功.所以你test.bat||echo 99的回应是成功的GOTO :EOF.

当您||GOTO :EOF从脚本中删除from时,您test.bat||echo99将看到失败的结果mkdir.但是如果你REM要在test.bat的末尾添加一个命令,那么test.bat||echo 99就会响应成功REM,并且错误会再次被掩盖.

该ERRORLEVEL之后仍然是不为零test.bat||echo 99,因为类似这样的命令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
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为该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
Run Code Online (Sandbox Code Playgroud)

因为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
Run Code Online (Sandbox Code Playgroud)

如果没有CALL,则CMD /C返回最后执行的命令的返回码,即GOTO :EOF.它CMD /C还将ERRORLEVEL设置为相同的返回码,因此现在没有证据表明脚本中存在错误.

我们走了兔洞

RLH在他的回答和对我的回答的评论中担心||有时会清除ERRORLEVEL.他提供的证据似乎支持了他的结论.但情况并非如此简单,事实证明这||是检测错误的最可靠(但仍然不完美)的方法.

正如我之前所说,退出时所有外部命令返回的返回码与cmd.exe ERRORLEVEL不同.

ERRORLEVEL是cmd.exe会话本身内维护的状态,完全不同于返回代码.

这甚至记录在EXIT帮助
(help exitexit /?)中的exitCode定义中

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.
Run Code Online (Sandbox Code Playgroud)

当CMD.EXE运行外部命令时,它会检测可执行文件的返回码并将ERRORLEVEL设置为匹配.请注意,只有约定0表示成功,非零表示错误.某些外部命令可能不遵循该约定.例如,HELP命令(help.exe)不遵循约定 - 如果您指定无效命令,则返回0 help bogus,但如果您请求有效命令的帮助,则返回1,如help rem.

||执行外部命令时,操作员永远不会清除ERRORLEVEL.检测到进程退出代码并||在其为非零时触发,并且ERRORLEVEL仍将与退出代码匹配.话虽如此,出现在ERRORLEVEL 之后&&和/或||可能修改ERRORLEVEL的命令,所以必须要小心.

但除了外部命令之外还有许多其他情况,我们开发人员关心成功/失败和返回代码/ ERRORLEVEL.

  • 执行内部命令
  • 重定向操作符<,>>>
  • 执行批处理脚本
  • 执行无效命令失败

不幸的是,CMD.EXE在如何处理这些情况的错误条件方面并不完全一致.CMD.EXE有多个内部点,它必须检测错误,可能是通过某种形式的内部返回代码,不一定是ERRORLEVEL,并且在这些点中的每一个CMD.EXE都可以根据它找到的内容设置ERRORLEVEL .

对于下面的测试用例,请注意(call ),使用空格是一种神秘的语法,在每次测试之前将ERRORLEVEL清除为0.稍后,我还将使用(call)没有空格的ERRORLEVEL设置为1

另请注意,在我的命令会话中,通过
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
Run Code Online (Sandbox Code Playgroud)

然后至少有一个命令,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
Run Code Online (Sandbox Code Playgroud)

如果删除失败,请参阅"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
Run Code Online (Sandbox Code Playgroud)

有关DEL错误的更多信息,请参阅/sf/answers/2244813231/.

以同样的方式,当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
Run Code Online (Sandbox Code Playgroud)

我无法证明这一点,但我怀疑在命令执行过程中很晚才发现命令失败的检测和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 /B 0清除了ERRORLEVEL,但EXIT /B没有值保留了先前的ERRORLEVEL.
  • 对于
  • 如果
  • KEYS
  • 暂停
  • POPD
  • RD
  • REM
  • RMDIR
  • 转移
  • 开始
  • 标题

然后,如果从命令行或带有.bat扩展名的脚本发出,则这些命令在成功时不会清除ERRORLEVEL ,但如果从具有.cmd扩展名的脚本发出,则将ERRORLEVEL清除为0 .有关详细信息,请参阅/sf/answers/10429401/https://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
Run Code Online (Sandbox Code Playgroud)

在我的所有错误检测代码示例中,我依赖的事实是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!
Run Code Online (Sandbox Code Playgroud)

以下是脚本具有.bat扩展名时的输出:

C:\test>test.bat
ERRORLEVEL = 1
ERROR 1
ERROR 1
ERROR 1
ERRORLEVEL = 1
ERR = 1
Run Code Online (Sandbox Code Playgroud)

以下是脚本具有.cmd扩展名时的输出:

C:\test>test.cmd
ERRORLEVEL = 1
ERROR 1
ERROR 1
ERROR 0
ERRORLEVEL = 0
ERR = 1
Run Code Online (Sandbox Code Playgroud)

请记住,每个执行的命令都有可能改变ERRORLEVEL.因此,即使&&并且||是检测命令成功或失败的最可靠方法,如果您关心ERRORLEVEL值,必须注意在这些运算符之后使用的命令.

而现在是时候爬出这个发臭的兔子洞,并获得一些新鲜空气!

所以我们学了什么?

没有一种完美的方法可以检测任意命令是成功还是失败.但是,&&||是检测成功和失败的最可靠方法.

通常,既不直接&&也不||修改ERRORLEVEL.但是有一些罕见的例外.

  • || 正确设置ERRORLEVEL,否则在RD或重定向失败时会错过
  • ||在执行无效命令失败时设置不同的ERRORLEVEL,如果||未使用则会发生(1对9009).

最后,||除非使用了CALL命令,否则不会将批处理脚本返回的非零ERRORLEVEL检测为错误.

如果您严格依赖if errorlevel 1 ...if %errorlevel% neq 0 ...检测错误,那么您冒着丢失RD和重定向(以及其他?)可能抛出的错误的风险,并且您还冒着错误地认为某些内部命令失败的风险,而实际上它可能是从先前失败的命令中保留.


归档时间:

查看次数:

4987 次

最近记录:

7 年,5 月 前