批量跳转丢失错误级别 [英] Batch goto loses errorlevel

查看:14
本文介绍了批量跳转丢失错误级别的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Consider the following bat, test.bat (PC01 is off):

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

如果我从命令 shell 运行那个 bat:

If I run that bat from a command shell:

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

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

The output is just 55. No 99. There is an errorlevel, but the || operator did not see it.

如果我使用 cmd/c -

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

输出为空白.错误级别为 0.

The output is blank. Errorlevel is 0.

如果我删除了 ||goto :eof,一切都像人们预测的那样 —即输出将是

If I remove the || goto :eof, everything works as one would predict — i.e the output would be

99 55

有谁知道为什么会出现这种半生不熟、半存在的 ERRORLEVEL 行为?

Does anyone know why this half-baked semi-existent ERRORLEVEL behaviour is occurring?

推荐答案

在大多数情况下,||是检测错误的最可靠方法.但是您偶然发现了 ERRORLEVEL 有效但 || 无效的罕见情况之一.

In most circumstances, || is the most reliable way to detect an error. But you have stumbled on one of the rare cases where ERRORLEVEL works but || does not.

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

The problem stems from the fact that your error is raised within a batch script, and || responds to the return code of the most recently executed command. You are thinking of test.bat as a single "command", but actually it is a sequence of commands. The last command executed within the script is GOTO :EOF, and that executed successfully. So your test.bat||echo 99 is responding to the success of the GOTO :EOF.

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

When you remove the ||GOTO :EOF from within the script, then your test.bat||echo99 sees the result of the failed mkdir. But if you were to add a REM command to the end of test.bat, then test.bat||echo 99 would respond to the success of the REM, and the error would be masked again.

test.bat||echo 99 之后的 ERRORLEVEL 仍然是非零的,因为像 GOTOREM 这样的命令不会清除任何先前的成功时非零 ERRORLEVEL.这是 ERRORLEVEL 和返回码并非完全相同的众多证据之一.这肯定会让人感到困惑.

The ERRORLEVEL is still non-zero after test.bat||echo 99 because commands like GOTO and REM do not clear any prior non-zero ERRORLEVEL upon success. This is one of many pieces of evidence that ERRORLEVEL and the return code are not quite the same thing. It definitely gets confusing.

您可以将 test.bat 视为一个单元命令,并通过使用 CALL 获得您想要的行为.

You can treat test.bat as a unit command and get the behavior you want by using CALL.

C:	est>call test.bat && echo OK || echo FAIL
FAIL

C:	est>if ERRORLEVEL 1 (echo FAIL2) else echo OK2
FAIL2

这是有效的,因为 CALL 命令暂时将控制转移到被调用的脚本.当脚本终止时,控制返回到 CALL 命令,并返回当前的 ERRORLEVEL.所以 ||echo 99 正在响应 CALL 命令本身返回的错误,而不是脚本中的最后一个命令.

This works because the CALL command temporarily transfers control to the called script. When the script terminates, control is returned to the CALL command, and it returns the current ERRORLEVEL. So ||echo 99 is responding to the error returned by the CALL command itself, not the last command within the script.

现在是 CMD/C 问题.

CMD/C返回的返回码是最后执行的命令的返回码.

The return code returned by CMD /C is the return code of the last command executed.

这有效:

C:	est>cmd /c call test.bat && echo OK || echo FAIL
FAIL

C:	est>if ERRORLEVEL 1 (echo FAIL2) else echo OK2
FAIL2

因为CMD/C返回的是CALL语句返回的ERRORLEVEL

because CMD /C returns the ERRORLEVEL returned by the CALL statement

但这完全失败了:

C:	est>cmd /c test.bat && echo OK || echo FAIL
OK

C:	est>if ERRORLEVEL 1 (echo FAIL2) else echo OK2
OK2

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

Without the CALL, the CMD /C returns the return code of the last executed command, which is the GOTO :EOF. The CMD /C also sets the ERRORLEVEL to the same return code, so now there is no evidence that there ever was an error within the script.

然后我们去兔子洞

R.L.H. 在他的回答和对我的回答的评论中,担心 || 有时会清除 ERRORLEVEL.他提供了似乎支持他的结论的证据.但情况并没有那么简单,结果证明 || 是检测错误的最可靠(但仍不完美)的方法.

R.L.H., in his answer and in his comments to my answer, is concerned that || sometimes clears the ERRORLEVEL. He provides evidence that appears to back up his conclusion. But the situation isn't that simple, and it turns out that || is the most reliable (but still not perfect) way to detect errors.

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

As I stated earlier, the return code that all external commands return upon exit is not the same thing as the cmd.exe ERRORLEVEL.

ERRORLEVEL 是在 cmd.exe 会话本身中维护的一种状态,与返回代码完全不同.

The ERRORLEVEL is a state maintained within the cmd.exe session itself, wholly distinct from return codes.

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

This is even documented in the definition of the exitCode within the EXIT help
(help exit or 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 运行外部命令时,它会检测可执行文件的返回代码并将 ERRORLEVEL 设置为匹配.请注意,0 表示成功,非零表示错误只是一个约定.某些外部命令可能不遵循该约定.例如,HELP 命令 (help.exe) 不遵循约定 - 如果您在 help bogus 中指定无效命令,则返回 0,但如果您请求有关有效命令的帮助,则返回 1,如help rem.

When an external command is run by CMD.EXE, it detects the executeable's return code and sets the ERRORLEVEL to match. Note that it is only a convention that 0 means success, and non-zero means error. Some external commands may not follow that convention. For example, the HELP command (help.exe) does not follow the convention - it returns 0 if you specify an invalid command as in help bogus, but returns 1 if you ask for help on a valid command, as in help rem.

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

The || operator never clears the ERRORLEVEL when an external command is executed. The process exit code is detected and fires || if it is non-zero, and the ERRORLEVEL will still match the exit code. That being said, the commands that appear after && and/or || may modify the ERRORLEVEL, so one has to be careful.

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

But there are many other situations besides external commands where we developers care about success/failure and return codes/ERRORLEVELs.

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

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

Unfortunately, CMD.EXE is not at all consistent in how it handles error conditions for these situations. CMD.EXE has multiple internal points where it must detect errors, presumably through some form of internal return code that is not necessarily the ERRORLEVEL, and at each of these points CMD.EXE is in a position to set the ERRORLEVEL depending on what it finds.

对于我下面的测试用例,请注意带有空格的 (call ) 是一种神秘的语法,可以在每次测试之前将 ERRORLEVEL 清除为 0.后面我也会用(call),不用空格,把ERRORLEVEL设为1

For my test cases below, note that (call ), with a space, is arcane syntax that clears the ERRORLEVEL to 0 before each test. Later on, I will also use (call), without a space, to set the ERRORLEVEL to 1

另请注意,已在我的命令会话中使用
启用了延迟扩展cmd/v: on 在运行我的测试之前

Also note that delayed expansion has been enabled within my command session by using
cmd /v: on prior to running my tests

绝大多数内部命令在失败时将 ERRORLEVEL 设置为非零值,并且错误条件也会触发 ||.在这些情况下,|| 永远不会清除或修改 ERRORLEVEL.下面是几个例子:

The vast majority of internal commands set the ERRORLEVEL to a non-zero value upon failure, and the error condition also fires ||. The || never clears or modifies the ERRORLEVEL in these cases. Here are a couple examples:

C:	est>(call ) & set /a 1/0
Divide by zero error.

C:	est>echo !errorlevel!
1073750993

C:	est>(call ) & type notExists
The system cannot find the file specified.

C:	est>echo !errorlevel!
1

C:	est>(call ) & set /a 1/0 && echo OK || echo ERROR !errorlevel!
Divide by zero error.
ERROR 1073750993

C:	est>(call ) & type notExists.txt && echo OK || echo ERROR !errorlevel!
The system cannot find the file specified.
ERROR 1

然后至少有一个命令 RD(可能更多),以及在出错时触发 || 的重定向操作符,但不要设置 ERRORLEVEL,除非 || 使用.

Then there is at least one command, RD, (possibly more), as well as the redirection operators that fire || upon error, but do not set ERRORLEVEL unless || is used.

C:	est>(call ) & rd notExists
The system cannot find the file specified.

C:	est>echo !errorlevel!
0

C:	est>(call ) & echo x >adPathout.txt
The system cannot find the path specified.

C:	est>echo !errorlevel!
0

C:	est>(call ) & rd notExists && echo OK || echo ERROR !errorlevel!
The system cannot find the file specified.
ERROR 2

C:	est>(call ) & echo x >adPathout.txt && echo OK || echo ERROR !errorlevel!
The system cannot find the path specified.
ERROR 1

参见rd"删除失败时错误级别设置为 0 退出,等等Windows 中的文件重定向和 %errorlevel%了解更多信息.

See "rd" exits with errorlevel set to 0 on error when deletion fails, etc and File redirection in Windows and %errorlevel% for more information.

我知道一个内部命令(可能还有其他命令)加上基本失败的 I/O 操作,这些操作可以向 stderr 发出错误消息,但它们不会触发 || 也不会设置非零错误级别.

I know of one internal command (there may be others) plus basic failed I/O operations that can issue error messages to stderr, yet they don't fire || nor do they set a non-zero ERRORLEVEL.

如果文件是只读的或不存在,DEL 命令可以打印错误,但不会触发 || 或将 ERRORLEVEL 设置为非零

The DEL command can print an error if the file is read only, or does not exist, but it does not fire || or set ERRORLEVEL to non-zero

C:	est>(call ) & del readOnlyFile
C:	est
eadOnlyFile
Access is denied.

C:	est>echo !errorlevel!
0

C:	est>(call ) & del readOnlyFile & echo OK || echo ERROR !errorlevel!
C:	est
eadOnlyFile
Access is denied.
OK

有关 DEL 错误的更多信息,请参阅 https://stackoverflow.com/a/32068760/1012053.

See https://stackoverflow.com/a/32068760/1012053 for a bit more information related to DEL errors.

以同样的方式,当 stdout 已成功重定向到 USB 设备上的文件,但在 ECHO 等命令尝试写入设备之前删除了设备,则 ECHO 将失败并显示错误消息到 stderr,但 || 不会触发,并且 ERRORLEVEL 未设置为非零.请参阅 http://www.dostips.com/forum/viewtopic.php?f=3&t=6881 了解更多信息.

In much the same way, when stdout has been successfully redirected to a file on a USB device, but then the device is removed before a command such as ECHO tries to write to the device, then the ECHO will fail with an error message to stderr, yet || does not fire, and ERRORLEVEL is not set to non-zero. See http://www.dostips.com/forum/viewtopic.php?f=3&t=6881 for more info. 

然后我们遇到了执行批处理脚本的情况 - OP 问题的实际主题.如果没有 CALL|| 运算符会响应脚本中执行的最后一个命令.使用 CALL|| 运算符响应 CALL 命令返回的值,这是批处理终止时存在的最终 ERRORLEVEL.

Then we have the case where a batch script is executed - the actual subject of the OP's question. Without CALL, The || operator responds to the last command executed within the script. With CALL, the || operator responds to the value returned by the CALL command, which is the final ERRORLEVEL that exists upon batch termination.

最后,我们遇到了 R.L.H.报告,其中无效命令通常报告为 ERRORLEVEL 9009,但如果使用 ||,则报告为 ERRORLEVEL 1.

Finally, we have the case that R.L.H. reports, where an invalid command is reported as ERRORLEVEL 9009 normally, but as ERRORLEVEL 1 if || is used.

C:	est>(call ) & InvalidCommand
'InvalidCommand' is not recognized as an internal or external command,
operable program or batch file.

C:	est>echo !errorlevel!
9009

C:	est>(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 错误,而是处理和设置错误的替代途径.

I can't prove this, but I suspect that the detection of command failure and setting of ERRORLEVEL to 9009 occurs very late in the command execution process. I'm guessing that || intercepts the error detection before the 9009 is set, at which point it sets it to 1 instead. So I don't think || is clearing the 9009 error, but rather it is an alternate pathway by which the error is handled and set.

这种行为的另一种机制是无效命令可以始终将 ERRORLEVEL 设置为 9009,但返回码不同,为 1.|| 随后可以检测到 1 返回代码并将 ERRORLEVEL 设置为匹配,从而覆盖 9009.

An alternate mechanism for this behavior is that the invalid command could always set the ERRORLEVEL to 9009, yet have a different return code of 1. The || could subsequently detect the 1 return code and set the ERRORLEVEL to match, thus overwriting the 9009.

无论如何,我不知道有任何其他情况会根据是否使用 || 导致非零 ERRORLEVEL 结果不同.

Regardless, I am not aware of any other situation where a non-zero ERRORLEVEL result is different depending on whether || was used or not.

这样就可以处理命令失败时发生的情况.但是当内部命令成功时呢?不幸的是,CMD.EXE 的一致性甚至不如错误时的一致性.它因命令而异,也可能取决于它是从命令提示符执行的、从带有 .bat 扩展名的批处理脚本还是从带有 .cmd 扩展.

So that takes care of what happens when a command fails. But what about when an internal command succeeds? Unfortunately, CMD.EXE is even less consistent than it was with errors. It varies by command, and may also depend on whether it is executed from the command prompt, from a batch script with a .bat extension, or from a batch script with a .cmd extension.

我下面的所有讨论都基于 Windows 10 行为.我怀疑与使用 cmd.exe 的早期 Windows 版本存在差异,但这是可能的.

无论上下文如何,以下命令总是在成功时将 ERRORLEVEL 清除为 0:

The following commands always clear the ERRORLEVEL to 0 upon success, regardless of context:

  • CALL:如果 CALLed 命令没有设置它,则清除 ERRORLEVEL.
    示例:call echo OK
  • 光盘
  • CHDIR
  • 颜色
  • 复制
  • 日期
  • DEL:始终清除 ERRORLEVEL,即使 DEL 失败
  • 目录
  • ERASE :始终清除 ERRORLEVEL,即使 ERASE 失败
  • MD
  • MKDIR
  • MKLINK
  • 移动
  • 推送
  • 重命名
  • SETLOCAL
  • 时间
  • 类型
  • VER
  • 验证
  • 音量

无论上下文如何,下一组命令都不会在成功时将 ERRORLEVEL 清除为 0,而是保留任何现有的非零值 ERRORLEVEL:

The next set of commands never clear the ERRORLEVEL to 0 upon success, regardless of context, but instead preserve any existing non-zero value ERRORLEVEL:

  • BREAK
  • CLS
  • 回声
  • 本地
  • EXIT :显然 EXIT/B 0 清除了 ERRORLEVEL,但是 EXIT/B 没有值会保留之前的 ERRORLEVEL.
  • 为了
  • 转到
  • 如果
  • 按键
  • 暂停
  • POPD
  • 研发
  • 快速眼动
  • RMDIR
  • SHIFT
  • 开始
  • 标题
  • BREAK
  • CLS
  • ECHO
  • ENDLOCAL
  • EXIT : Obviously EXIT /B 0 clears the ERRORLEVEL, but EXIT /B without value preserves the prior ERRORLEVEL.
  • FOR
  • GOTO
  • IF
  • KEYS
  • PAUSE
  • POPD
  • RD
  • REM
  • RMDIR
  • SHIFT
  • START
  • TITLE

然后有这些命令如果从命令行发出或在带有 .bat 扩展名的脚本中发出,则在成功时不会清除 ERRORLEVEL,但如果从带有 .cmd 扩展名的脚本.请参阅 https://stackoverflow.com/a/148991/1012053https://groups.google.com/forum/#!msg/microsoft.public.win2000.cmdprompt.admin/XHeUq8oe2wk/LIEViGNmkK0J 了解更多信息.

And then there are these commands that do not clear ERRORLEVEL upon success if issued from the command line or within a script with a .bat extension, but do clear the ERRORLEVEL to 0 if issued from a script with a .cmd extension. See https://stackoverflow.com/a/148991/1012053 and https://groups.google.com/forum/#!msg/microsoft.public.win2000.cmdprompt.admin/XHeUq8oe2wk/LIEViGNmkK0J for more info.

  • 协会会员
  • DPATH
  • FTYPE
  • 路径
  • 提示
  • 设置

无论任何 ERRORLEVEL 值如何,&& 运算符都会检测前一个命令是否成功,如果成功,则仅执行后续命令.&& 运算符会忽略 ERRORLEVEL 的值,并且从不修改它.

Regardless of any ERRORLEVEL value, the && operator detects if the prior command was successful, and only executes the subsequent command(s) if it was. The && operator ignores the value of ERRORLEVEL, and never modifies it.

以下两个示例表明,如果先前的命令成功,则 && 始终会触发,即使 ERRORLEVEL 不为零.CD 命令是该命令清除任何先前的 ERRORLEVEL 的示例,而 ECHO 命令是该命令不清除先前的 ERRORLEVEL 的示例.注意,在发出成功的命令之前,我使用 (call) 将 ERRORLEVEL 强制为 1.

Here are two examples that show that && always fires if the prior command was successful, even if the ERRORLEVEL is non-zero. The CD command is an example where the command clears any prior ERRORLEVEL, and the ECHO command is an example where the command does not clear the prior ERRORLEVEL. Note, I am using (call) to force ERRORLEVEL to 1 before issuing the command that succeeds.

C:TEST>(call)

C:TEST>echo !errorlevel!
1

C:	est>(call) & cd 	est

C:	est>echo !errorlevel!
0

C:	est>(call) & cd 	est && echo OK !errorlevel! || echo ERROR !errorlevel!
OK 0

C:	est>(call) & echo Successful command
Successful command

C:	est>echo !errorlevel!
1

C:	est>(call) & echo Successful command && echo OK !errorlevel! || echo ERROR !errorlevel!
Successful command
OK 1

在我用于错误检测的所有代码示例中,我依赖于 ECHO 永远不会清除以前存在的非零 ERRORLEVEL 的事实.但下面的脚本是在 &&|| 之后使用其他命令时可能发生的情况的示例.

In all of my code examples for error detection, I was relying on the fact that ECHO never clears a previously existing non-zero ERRORLEVEL. But the script below is an example of what can happen when other commands are used after && or ||.

@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 扩展名时的输出:

Here is the output when the script has a .bat extension:

C:	est>test.bat
ERRORLEVEL = 1
ERROR 1
ERROR 1
ERROR 1
ERRORLEVEL = 1
ERR = 1

这是当脚本具有 .cmd 扩展名时的输出:

And here is the output when the script has a .cmd extension:

C:	est>test.cmd
ERRORLEVEL = 1
ERROR 1
ERROR 1
ERROR 0
ERRORLEVEL = 0
ERR = 1

请记住,每个执行的命令都有可能改变 ERRORLEVEL.因此,尽管 &&|| 是检测命令成功或失败的最可靠方法,但如果你关心 ERRORLEVEL 值.

Remember that every executed command has the potential to alter the ERRORLEVEL. So even though && and || are the most reliable ways to detect command success or failure, one must be careful about what commands are used after those operators if you care about the ERRORLEVEL value.

现在是时候爬出这个臭臭的兔子洞呼吸新鲜空气了!

那么我们学到了什么?

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

There is no single perfect method to detect whether any arbitrary command was successful or failed. However, && and || are the most reliable methods to detect success and failure.

一般来说,&&|| 都不会直接修改 ERRORLEVEL.但也有少数例外.

In general, neither && nor || modify the ERRORLEVEL directly. But there are a few rare exceptions.

  • || 正确设置 ERRORLEVEL,否则会在 RD 或重定向失败时丢失
  • || 会在无效命令执行失败时设置不同的 ERRORLEVEL,如果未使用 ||(1 与 9009),则会发生这种情况.
  • || properly sets the ERRORLEVEL that would otherwise be missed when RD or redirection fails
  • || sets a different ERRORLEVEL upon failed execution of an invalid command then would occur if || were not used (1 vs. 9009).

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

Finally, || does not detect a non-zero ERRORLEVEL returned by a batch script as an error unless the CALL command was used.

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

If you rely strictly on if errorlevel 1 ... or if %errorlevel% neq 0 ... to detect errors, then you run the risk of missing errors that RD and redirection (and others?) might throw, and you also run the risk of mistakenly thinking certain internal commands failed when in reality it could be a holdover from a prior command that failed.

这篇关于批量跳转丢失错误级别的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆