之间的差异|||退出/b“和"||退出/b!errorlevel!" [英] Difference between "|| exit /b" and "|| exit /b !errorlevel!"

查看:78
本文介绍了之间的差异|||退出/b“和"||退出/b!errorlevel!"的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们有一堆 .bat 构建脚本,这些脚本由基于PowerShell的GitLab运行程序调用,最近从以下代码中进行了重构:

We have a bunch of .bat build scripts which are invoked by a PowerShell based GitLab runner that were recently refactored from:

program args
if !errorlevel! neq 0 exit /b !errorlevel!

更简洁:

program args || exit /b

今天,我调查了一个构建作业,如果您查看错误日志,该构建作业显然会失败,但是据报告是成功的.经过大量实验后,我发现此模式始终无法按预期运行:

Today I investigated a build job which obviously failed if you looked at the error log but which was reported as a success. After much experimentation I discovered that this pattern didn't always work as expected:

program args || exit /b

但是这似乎在前者不起作用时起作用:

but this did appear to work when the former didn't:

program args || exit /b !errorlevel!


我已经阅读了这样的问题> Windows批处理退出选项b带有或不带有错误级别,以及下面来自 https://www.robvanderwoude.com/exit的声明.php ,但仍然无法完全解释我正在观察的内容.


I've read the SO question Windows batch exit option b with or without errorlevel and the statement below from https://www.robvanderwoude.com/exit.php but still can't quite explain what I'm observing.

DOS联机帮助(HELP EXIT)不能清楚地表明/B参数退出脚本的当前实例,而不必等同于退出当前脚本.IE.如果脚本是在CALLed代码段中,则EXIT/B退出CALL,而不是脚本.

The DOS online help (HELP EXIT) doesn't make it clear that the /B parameter exits the current instance of script which is not necessarily the same as exiting the current script. I.e. if the script is in a CALLed piece of code, the EXIT /B exits the CALL, not the script.


这是我用来浏览此文件的最小批处理文件:


This is the minimal batch file I used to explore this:

@echo off
setlocal EnableDelayedExpansion
cmd /c "exit 99" || exit /b
:: cmd /c "exit 99" || exit /b !errorlevel!

这就是我调用批处理文件的方式(以模拟基于GitLab PowerShell的运行程序如何调用它):

And this is how I invoked the batch file (to simulate how it was invoked by the GitLab PowerShell based runner):

& .\test.bat; $LastExitCode

以下是输出,具体取决于执行批处理文件中的两行:

Here is the output depending on which of the two lines in the batch file is executed:

PS> & .\test.bat; $LastExitCode
0
PS> & .\test.bat; $LastExitCode
99


还有一种获取正确行为的方法,即使用 CALL 在PowerShell中长期调用批处理文件:


There is another way to get the correct behaviour which is to invoke the batch file longhand from within PowerShell using CALL as well:

PS> & cmd.exe /c "call .\test.bat"; $LastExitCode
99

虽然我很欣赏这可能是从PowerShell调用批处理文件的正确方法,但根据我所看到的许多示例,这似乎不是常识.我也想知道,如果PowerShell是正确的方式",为什么它不以这种方式调用批处理文件.最后,我仍然不明白为什么,当放弃 call 时,行为会根据是否将!errorlevel!添加到 exit/b 语句.

While I appreciate that this may be the correct way to invoke a batch file from PowerShell, that does not appear to be common knowledge based on the many examples I've seen. Also I wonder why PowerShell doesn't invoke a batch file this way if it's "the right way". Lastly I still don't understand why, when leaving off the call, the behaviour changes depending on whether we add the !errorlevel! to the exit /b statement.

更新:到目前为止,感谢您进行的所有讨论,但我感到迷失在草丛中,这可能是我对原始问题过于含糊的错.我认为(如果可能的话),我真正想得到的是确定性声明,有关何时(应该)在以下声明中评估错误级别:

UPDATE: Thanks for all the discussion so far but I feel it's getting lost in the weeds which is probably my fault for being too vague in my original question. What I think I'm really after (if possible) is a definitive statement about when the errorlevel is (supposedly) evaluated in the following statement:

program || exit /b

它是否真的像这样早评估了(即在运行 program 之前):

Is it really evaluated early (i.e. before program is run) like this:

program || exit /b %errorlevel%

还是懒惰地评估(即在运行 program 之后并且内部已更新 errorlevel 之后执行 exit 时),更相似对此:

Or is it evaluated lazily (i.e. when exit is being executed after program has run and internally errorlevel has been updated), more analogous to this:

program || exit /b !errorlevel!

因此,我并不是很在乎猜测,除非可悲的是,这是我们可以做的最好的,在这种情况下,知道没有明确的答案或它是一个错误是我可以接受的答案:o).

Hence I'm not really after speculation unless, sadly, that is the best that we can do in which case knowing there is no definitive answer or that it's a bug is an acceptable answer to me :o).

推荐答案

解决方法:

  • 通过 cmd/c< batch-file>调用您的批处理文件...`&退出 ,在这种情况下为 ||没有显式退出代码的退出/b 解决方案 可以正常工作.

  • Call your batch file via cmd /c <batch-file> ... `& exit, in which case the || exit /b solution without an explicit exit code works as expected.

  • cmd/c.\ test.bat`&退出

  • 请注意,使用`进行转义的& ,可以防止PowerShell预先解释& .如果要在不涉及外壳的环境中运行命令,请省略`,例如计划任务.
  • 或者,您可以使用 cmd/c< batch-file>...&退出" ,但是需要转义作为参数一部分的任何" 字符.如果没有使用PowerShell变量或表达式,则可以选择单引号,这样可以避免该问题: cmd/c'< batch-file>...&退出"
  • Note the `-escaped &, which prevents PowerShell from interpreting & up front. Omit the ` if you're running the command from an environment that doesn't involve a shell, such as from aa scheduled task.
  • Alternatively, you can use the form cmd /c "<batch-file> ... & exit", but that requires escaping of any " characters that are part of arguments. If no PowerShell variables or expressions are being used, single-quoting is an option, which avoids that problem: cmd /c '<batch-file> ... & exit'

使用 cmd/c< batch-file>...`&建议退出 定期从外部 cmd.exe 调用批处理文件,因为即使批处理文件没有显式 exit/b (或 exit )调用可能会出现异常情况-请参见此答案.

Using cmd /c <batch-file> ... `& exit routinely to call batch files from outside cmd.exe is advisable, as even batch files without explicit exit /b (or exit) calls can otherwise behave unexpectedly - see this answer.

或者-但仅当您的批处理文件永远不需要从另一个批处理文件中调用 时,才应将控件返回到该文件,并且它永远不需要成为 cmd的一部分/c multi -不是 last 命令的命令行 [1] -您可以使用||退出,而不是 ||exit/b -这会立即整体退出正在执行的 cmd.exe 进程 ,但是退出代码(错误级别)为 cmd.exe调用 direct 直接可靠地报告em(至少在< command> || exit 语句的上下文中) ,例如&.\ test.bat (或者在这种简单情况下,只需.\ test.bat ).

Alternatively - but only if your batch file never needs to be called from another batch file to which control should be returned and if it never needs to be part of a cmd /c multi-command command line where it isn't the last command[1] - you can use || exit instead of || exit /b - this exits the executing cmd.exe process as a whole, instantly, but the exit code (error level) is then reliably reported (at least in the context of a <command> || exit statement) also with direct invocation from outside cmd.exe, such as & .\test.bat (or, in this simple case, just .\test.bat) from PowerShell.

同时将 setlocal EnableDelayedExpansion exit/b!ERRORLEVEL!结合使用时-由于使用了 explicit 退出代码-显然更多麻烦并且可能会有副作用,特别是从 echo hi!之类的命令中悄悄地删除字符(尽管可以通过放置 setlocal EnableDelayedExpansion exit/b 调用之前的行上调用,如果有多个退出点,则需要重复.)

While combining setlocal EnableDelayedExpansion with exit /b !ERRORLEVEL! works too - due to using an explicit exit code - it is obviously more cumbersome and can have side effects, notably quietly removing ! characters from commands such as echo hi! (while it's possible to minimize that problem by placing the setlocal EnableDelayedExpansion call on the line just before an exit /b call, that would require duplication if there are multiple exit points).

cmd.exe 的行为很不幸,但无法避免.

cmd.exe's behavior is unfortunate in this case, but can't be avoided.

cmd.exe 外部调用批处理文件时:

When calling a batch file from outside cmd.exe:

  • exit/b -不带退出代码(错误级别)参数-仅设置 cmd.exe 预期的处理退出代码-即批处理文件中最近执行的命令的退出代码-如果跟踪批处理文件调用,则&退出 ,即 cmd/c< batch-file>...`&退出

  • exit /b - without an exit-code (error-level) argument - only sets the cmd.exe process exit code as expected - namely to the exit code of the most recently executed command in the batch file - if you follow the batch-file call with & exit, i.e. as cmd /c <batch-file> ... `& exit

  • 没有 &退出解决方法,来自批处理文件的无参数 exit/b 调用反映在%ERRORLEVEL%变量 intra- cmd.exe -session ,但这不会转换为 cmd.exe process 退出代码,然后默认为 0 . [1]

  • Without the & exit workaround, an argument-less exit /b call from a batch file is reflected in the %ERRORLEVEL% variable intra-cmd.exe-session, but that doesn't translate to cmd.exe's process exit code, which then defaults to 0.[1]

使用 &exit 解决方法,批处理文件内无参数的 exit/b 确实正确设置了 cmd.exe 的退出代码,即使在< command>中||exit/b 语句,在这种情况下,将按预期中继< command> 的退出代码.

With the & exit workaround, intra-batch-file argument-less exit /b does properly set cmd.exe's exit code, even in a <command> || exit /b statement, in which case <command>'s exit code is relayed, as intended.

退出/b< code> ,即明确传递退出代码< code> 明确始终有效 [2] ,即&这样就不需要 退出.

exit /b <code>, i.e. passing an exit code <code> explicitly, always works[2], i.e. the & exit workaround is then not needed.

这种区别是一种模糊的不一致,可以合理地称为错误 Jeb的有用答案包含示例代码,演示了该行为(使用不太全面的 cmd/c调用... 解决方法,但它同样适用于 cmd/c"...& exit" ).

This distinction is an obscure inconsistency that could justifiably be called a bug; Jeb's helpful answer has sample code that demonstrates the behavior (using the less comprehensive cmd /c call ... workaround as of this writing, but it applies equally to cmd /c "... & exit").

[1]使用 cmd/c ,您可以传递多个语句来执行,而 last 语句确定 cmd.exe 进程的退出代码.例如 cmd/c"ver&dir nosuch" 报告退出代码 1 ,因为不存在的文件系统项 nosuch 导致 dir 设置错误级别到 1 ,无论前面的命令( ver )是否成功.不一致之处是,对于名为 test.bat 的批处理文件,该文件以 exit/b 退出而没有显式的退出代码参数 cmd/c test.bat 始终报告 0 ,而 cmd/c test.bat`&exit 正确报告在批处理文件退出之前执行的 last语句的退出代码.

[1] With cmd /c, you can pass multiple statements for execution, and it is the last statement that determines the cmd.exe process' exit code. E.g, cmd /c "ver & dir nosuch" reports exit code 1, because the non-existent file-system item nosuch caused dir to set the error level to 1, irrespective of whether or not the preceding command (ver) succeeded. The inconsistency is that, for a batch file named test.bat which exits with exit /b without an explicit exit-code argument, cmd /c test.bat always reports 0, whereas cmd /c test.bat `& exit properly reports the exit code of the last statement executed before the batch file exited.

[2]可以直接指定退出代码,也可以通过变量指定退出代码,但陷阱是-由于 cmd.exe 的前期变量扩展-< command>||exit/b%ERRORLEVEL%不能按预期工作,因为此时%ERRORLEVEL%扩展到错误级别 prior 此语句,而不是< command> 所设置的语句;这就是为什么通过运行 setlocal enabledelayedexpansion 或使用/V 调用 cmd.exe 延迟扩展的原因选项,在这种情况下是必需的:< command>||退出/b!ERRORLEVEL!

[2] The exit code may be specified literally or via a variable, but the pitfall is that - due to cmd.exe's up-front variable expansion - <command> || exit /b %ERRORLEVEL% does not work as intended, because %ERRORLEVEL% at that point expands to the error level prior to this statement, not to the one set by <command>; this is why delayed expansion, via having run setlocal enabledelayedexpansion or having invoked the cmd.exe with the /V option, is necessary in this case: <command> || exit /b !ERRORLEVEL!

这篇关于之间的差异|||退出/b“和"||退出/b!errorlevel!"的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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