"|| 之间的区别退出/b"和"||退出/b !errorlevel!" [英] Difference between "|| exit /b" and "|| exit /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 EnableDelayedExpansion
与 exit/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 thecmd.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. ascmd /c <batch-file> ... `& exit
没有
&exit
解决方法,来自批处理文件的无参数exit/b
调用 is 反映在%ERRORLEVEL%
变量 intra-cmd.exe
-session,但这不会转换为cmd.exe
的 process 退出代码,然后默认为0
.[1]
Without the
& exit
workaround, an argument-lessexit /b
call from a batch file is reflected in the%ERRORLEVEL%
variable intra-cmd.exe
-session, but that doesn't translate tocmd.exe
's process exit code, which then defaults to0
.[1]
使用&exit
解决方法,批处理文件内无参数 exit/b
does 正确设置 cmd.exe
的退出代码,即使在
语句,在这种情况下,
的退出代码按预期传递.
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
选项,在这种情况下是必需的:
[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屋!