for-loop header 命令失败时如何退出脚本?

How do I exit script when for-loop header command fails?

在 Windows 批处理文件中,我需要使用

处理命令的输出
for /f %%a in ('aCommand') do (
    ...
)

但是,如果 aCommand 失败,我想使用 exit /b 1 退出脚本。我试过了

for /f %%a in ('aCommand') do (
    ...
) || exit /b 1

但这没有用;无论如何,脚本的执行都会继续。有什么线索吗?

至少有三种可能的解决方案。

第一个用于处理执行命令输出的多行并由FOR循环处理。

@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "OutputProcessed="
for /F %%I in ('aCommand 2^>nul') do (
    set "OutputProcessed=1"
    rem Other commands processing the string(s) assigned to the loop variable(s).
)
if not defined OutputProcessed exit /B 1
set "OutputProcessed="
rem Other commands to run by the batch file.
endlocal

首先要确保环境变量 OutputProcessed 不是在 运行 for /F 之前偶然定义的,这会导致在后台 cmd.exe 中使用选项 /c' 之间的命令行附加为附加参数。

环境变量 OutputProcessed 是用一个字符串值定义的,如果在 FOR 循环中处理任何一行捕获的输出,这并不重要,这意味着由 cmd.exe 执行的命令行在后台输出一些由 FOR 循环内的命令处理的内容。

FOR 循环下面的 IF 条件检查环境变量 OutputProcessed 是否未定义,这意味着没有任何有用的处理从命令的执行。在这种情况下,批处理文件处理退出,退出代码 1 向父进程指示错误情况。

如果将已执行命令的输出数据分配给至少一个环境变量,则此解决方案最好,因为此环境变量可用于检查命令执行是否成功,而不是像使用的那样使用单独的环境变量举个例子。

第二个解决方案与第一个非常相似,只是环境变量处理颠倒了。

@echo off
setlocal EnableExtensions DisableDelayedExpansion
set "NoOutputProcessed=1"
for /F %%I in ('aCommand 2^>nul') do (
    set "NoOutputProcessed="
    rem Other commands processing the string(s) assigned to the loop variable(s).
)
if defined NoOutputProcessed exit /B 1
rem Other commands to run by the batch file.
endlocal

此解决方案首先使用无关紧要的值显式定义环境变量 NoOutputProcessed,并在从已执行命令的输出处理的任何行上取消定义环境变量。如果在执行 FOR 循环后仍然定义了环境变量 NoOutputProcessed 并且错误退出代码 1 返回到父进程,则退出批处理文件处理。

此解决方案适用于使用 FOR 循环内的命令进一步处理已执行命令的多行输出而不将数据分配给一个或多个环境的用例变量。

第三个解决方案最好是执行一条命令,在该命令上只有一个输出行包含感兴趣的数据,该数据将由批处理文件进一步处理。

@echo off
setlocal EnableExtensions DisableDelayedExpansion
for /F %%I in ('aCommand 2^>nul') do (
    rem Other commands processing the string(s) assigned to the loop variable(s).
    goto HaveData
)
exit /B 1
:HaveData
rem Other commands to run by the batch file.
endlocal

在这种情况下,FOR 循环的执行在从捕获的输出中获得感兴趣的数据后使用命令 GOTO 退出后台命令进程执行的命令。批处理文件继续处理标签下方的行。否则,整个批处理文件处理将退出,退出代码 1 感兴趣的数据不是从执行的命令中获取的。

要了解使用的命令及其工作原理,请打开 command prompt window,在其中执行以下命令,并仔细阅读为每个命令显示的所有帮助页面。

  • echo /?
  • endlocal /?
  • exit /?
  • for /?
  • goto /?
  • if /?
  • rem /?
  • set /?
  • setlocal /?

阅读有关 Using command redirection operators 的 Microsoft 文档,了解 2>nul 的解释。重定向运算符 > 必须在 上用脱字符 ^ 进行转义,以便在 Windows 命令解释器处理此命令时将其解释为文字字符 命令行执行命令 FOR 之前的行,它在后台启动的单独命令进程中执行嵌入式命令行。

主要问题是如何定义执行命令的成功或失败。 以下代码详细阐述了三个不同的定义——在 STDOUT, some output at STDERR, exit code or ErrorLevel 集没有输出:

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem // Define constants here:
set "_CMDL=ver"                      & rem // (successful command line)
::set "_CMDL=set ^="                 & rem // (alternative failing command line)
set "_TMPF=%TEMP%\%~n0_%RANDOM%.log" & rem // (temporary file)

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

rem /* In case of an error, the command does not return anything at STDOUT:
rem    hence `for /F` has got nothing to capture, in which case it sets the
rem    exit code (but not the `ErrorLevel`!) to `1`; this can be detected
rem    using the conditional execution operator `||`, given that the entire
rem    `for /F` loop is put in between a pair of parentheses: */
(
    rem // Remove `2^> nul` to also return a potential error message:
    for /F delims^=^ eol^= %%K in ('%_CMDL% 2^> nul') do (
        rem // Process an output line here...
        echo(%%K
    )
) || (
    >&2 echo ERROR!& rem exit /B
)

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

rem /* In case of an error, the command returns something at STDERR:
rem    so let us redirect STDOUT to a file for later usage and STDERR to STDOUT
rem    to be captured by `for /F`; if the loop iterates STDERR was not empty: */
(
    for /F delims^=^ eol^= %%K in ('%_CMDL% 2^>^&1 ^> "%_TMPF%"') do (
        >&2 echo ERROR!& del "%_TMPF%" & rem exit /B 1
    )
) || (
    rem /* Now process the temporary file containing the original STDOUT data;
    rem    remember that `for /F` would anyway not iterate until the command has
    rem    finished its execution, hence there is no additional delay: */
    for /F usebackq^ delims^=^ eol^= %%K in ("%_TMPF%") do (
        rem // Process an output line here...
        echo(%%K
    )
    del "%_TMPF%"
)

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

rem /* In case of an error, the command sets the exit code or `ErrorLevel`:
rem    hence let us suppress a potential error message (unless `2^> nul` is
rem    removed), redirect STDOUT to a file for later usage and just output `#`
rem    to STDOUT in case the exit code is set (detected by the `||` operator);
rem    `for /F` captures the `#` and iterates once in case it is present: */
(
    for /F %%K in ('%_CMDL% 2^> nul ^> "%_TMPF%" ^|^| echo #') do (
        >&2 echo ERROR!& del "%_TMPF%" & rem exit /B 1
    )
) || (
    rem // Now process the temporary file containing the original STDOUT data:
    for /F usebackq^ delims^=^ eol^= %%K in ("%_TMPF%") do (
        rem // Process an output line here...
        echo(%%K
    )
    del "%_TMPF%"
)

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

endlocal
exit /B