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,因为类似这样的命令GOTO并REM没有明确在成功之前的任何非零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 exit或exit /?)中的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 echo OK成功时,下一组命令永远不会将ERRORLEVEL清除为0,无论上下文如何,而是保留任何现有的非零值ERRORLEVEL:
EXIT /B 0清除了ERRORLEVEL,但EXIT /B没有值保留了先前的ERRORLEVEL.然后,如果从命令行或带有.bat扩展名的脚本发出,则这些命令在成功时不会清除ERRORLEVEL ,但如果从具有.cmd扩展名的脚本发出,则将ERRORLEVEL清除为0 .有关详细信息,请参阅/sf/answers/10429401/和https://groups.google.com/forum/#!msg/microsoft.public.win2000.cmdprompt.admin/XHeUq8oe2wk/LIEViGNmkK0J.
无论任何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 次 |
| 最近记录: |