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

Time:06-02

In a Windows batch file I need to process the output of a command with

for /f %%a in ('aCommand') do (
    ...
)

However, if aCommand fails I want to exit the script with exit /b 1. I tried

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

but that did not work; the execution of the script proceeds anyway. Any clues?

CodePudding user response:

There are at least three possible solutions.

The first one is for processing multiple lines output by the executed command and processed by the FOR loop.

@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

There is made sure first that the environment variable OutputProcessed is not defined by chance before running for /F which results in starting in background cmd.exe with option /c and the command line between ' appended as additional argument(s).

The environment variable OutputProcessed is defined with a string value which does not really matter if any line of captured output is processed inside the FOR loop which means that the command line executed by cmd.exe executed in background output something which is processed by the commands inside the FOR loop.

The IF condition below the FOR loop checks if the environment variable OutputProcessed is not defined which means nothing useful processed from the execution of the command. In this case the batch file processing is exited with an exit code 1 indicating an error condition to the parent process.

This solution is best if data from the output of the executed command is assigned to at least one environment variable as this environment variable can be used for checking the success of the command execution instead of using a separate environment variable as used by the example.

The second solution is very similar to the first one, just the environment variable handling is inverted.

@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

This solution first explicitly defines the environment variable NoOutputProcessed with a value which does not matter and undefines the environment variable on any line processed from output of the executed command. The batch file processing is exited if the environment variable NoOutputProcessed is still defined after execution of the FOR loop with error exit code 1 returned to the parent process.

This solution is for the use cases on which multiple lines of the output of the executed commands are processed further with commands inside the FOR loop without assigning data to one or more environment variables.

The third solution is best on a command is executed on which just one output line contains the data of interest which is processed further by the batch file.

@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

In this case the execution of the FOR loop is exited with command GOTO after having the data of interest from the captured output of the command executed by the background command process. The batch file processing is continued with the lines below the label. Otherwise the entire batch file processing is exited with exit code 1 on data of interest not get from the executed command.

For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.

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

Read the Microsoft documentation about Using command redirection operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded command line in a separate command process started in background.

CodePudding user response:

The major question is how do you define success or failure of the executed command. The following code elaborates on three distinct definitions – no output at STDOUT, some output at STDERR, exit code or ErrorLevel set:

@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
  • Related