"|| 之间的区别退出/b"和"||退出/b !errorlevel!" [英] Difference between "|| exit /b" and "|| exit /b !errorlevel!"

查看:24
本文介绍了"|| 之间的区别退出/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!


我已阅读 SO 问题 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):

& .	est.bat; $LastExitCode

这是根据批处理文件中两行中的哪一行执行的输出:

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

PS> & .	est.bat; $LastExitCode
0
PS> & .	est.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 .	est.bat"; $LastExitCode
99

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

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.

更新:感谢到目前为止的所有讨论,但我觉得它在杂草中迷失了,这可能是我的错,因为我的原始问题太含糊了.我认为我真正想要的是(如果可能的话)明确声明关于何时在以下声明中评估errorlevel:

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 运行后执行 exit 并且内部 errorlevel 已更新时),更类似对此:

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 调用你的批处理文件...`&退出,在这种情况下||exit/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 . est.bat `&退出

  • 注意 ` 转义的 &,这会阻止 PowerShell 预先解释 &.如果您从不涉及 shell 的环境(例如从计划任务)运行命令,请省略 `.
  • 或者,您可以使用 cmd/c "... &exit",但这需要转义作为参数一部分的任何 " 字符(或使用 here-string).如果没有使用 PowerShell 变量或表达式,则可以选择单引号来避免该问题:cmd/c ';... &退出'
  • 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 (or the use of a here-string). 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 ...`&退出 例行从外部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 进程作为一个整体,但是退出代码(错误级别)em> 然后可靠地报告(至少在 <command> || exit 语句的上下文中)还带有来自 cmd.exe直接 调用代码>,例如<代码>&. est.bat(或者,在这个简单的例子中,只是 . est.bat)来自 PowerShell.

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 & . est.bat (or, in this simple case, just . est.bat) from PowerShell.

同时将 setlocal EnableDelayedExpansionexit/b !ERRORLEVEL! 结合也可以工作(except(...) - 请参阅这篇文章) - 由于使用了显式退出代码 - 这显然是更麻烦,可能会产生副作用,特别是悄悄地从诸如 echo hi! 之类的命令中删除 ! 字符(虽然可以通过放置 setlocal EnableDelayedExpansion 来最小化该问题 就在 exit/b 调用之前在线上调用,如果有多个退出点,则需要重复).

While combining setlocal EnableDelayedExpansion with exit /b !ERRORLEVEL! works too (except inside (...) - see this post) - 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 ;...`&退出

  • 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 解决方法,来自批处理文件的无参数 exit/b 调用 is 反映在 %ERRORLEVEL% 变量 intra-cmd.exe-session,但这不会转换为 cmd.exeprocess 退出代码,然后默认为 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 does 正确设置 cmd.exe 的退出代码,即使在 中||exit/b 语句,在这种情况下, 的退出代码按预期传递.

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.

exit/b ,即传递一个退出代码 显式总是有效[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 正确报告在批处理文件退出之前执行的最后一条语句的退出代码.

[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 的前期变量扩展 - <命令>||exit/b %ERRORLEVEL% 没有按预期工作,因为 %ERRORLEVEL% 在这一点上扩展到 prior 的错误级别到这个语句,而不是由 设置的那个;这就是为什么 延迟 扩展,通过运行 setlocal enabledelayedexpansion 或使用 /V 调用 cmd.exe选项,在这种情况下是必需的:||退出/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!

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

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