errorlevel of 'command' in for /f loop

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
Liviu
Expert
Posts: 470
Joined: 13 Jan 2012 21:24

errorlevel of 'command' in for /f loop

#1 Post by Liviu » 14 Jul 2012 10:38

Is there a trick to retrieve the errorlevel of a (failed) command executed in a for /f loop?

EDIT - barebones code added in the P.S. to better describe the root problem, which also verifies Dave's neat resolution posted below.
EDIT #2 - added code to cover the case of a piped command, and also Jeb's alternative solution.

Question arose in the context of attempting to distinguish between an empty vs. a missing value in a reg query /v. The code below does work as expected, but is not able to retrieve the actual errorlevel, only to detect failure vs. success. Specifically, the "1" in "echo ErrorLevel%%TAB%%reg_failed%%TAB%%1" is hardcoded, and I found no obvious syntax to replace it with the actual %errorlevel% returned by "reg".

Code: Select all

@echo off
setlocal disableDelayedExpansion

@rem this must be a literal TAB character
set "TAB=   "

set "KEY=%~1"
set "VAL=%~2"

if "%VAL%"=="" (set $VAL=@) else (set $VAL="%VAL%")
echo. & echo [%KEY%]
call :regQuery "%KEY%" "%VAL%" DATA TYPE
if errorlevel 1 (              echo %$VAL%=
) else if "%TYPE%"=="REG_SZ" ( echo %$VAL%="%DATA%"
) else (                       echo %$VAL%="%TYPE%:%DATA%")

endlocal
goto :eof

:: %1 [input, required]  registry key
:: %2 [input, optional]  value to be queried, default value if missing
:: %3 [output, optional] name of variable to receive the data
:: %4 [output, optional] name of variable to receive the data type
::
:: returns errorlevel 0 when OK, 1 if failed e.g. key/value doesn't exist
::
:: to do: capture/return actual !errorlevel! from 'reg' vs. hardcoded '1'
::
:regQuery
if not "%~3"=="" (set "%~3=" & if not "%~4"=="" set "%~4=")
@rem 'reg query /ve' for the default value, 'reg query /v' otherwise
@rem catch 'reg' failure, build and echo a string for 'find' to recognize
@rem check output for the error signature, exit accordingly
for /f "tokens=1,2,* delims=%TAB%" %%a in (
  '^(^(if "%~2"^=^="" ^(reg query "%~1" /ve^) ^
       else           ^(reg query "%~1" /v "%~2"^)^) 2^>nul ^
    ^|^| echo ErrorLevel%%TAB%%reg_failed%%TAB%%1^) ^
  ^| find /i "REG_"'
) do (
  if "%%~b"=="reg_failed" (
    if "%%~a"=="ErrorLevel" (
      @rem 'reg' failed, or highly unlikely false negative
      exit /b %%~c
    ) else (
      @rem unexpected 'reg_failed' data type, or unlikely false positive
    )
  )
  @rem data to be returned, possibly none
  if not "%~3"=="" (set "%~3=%%~c" & if not "%~4"=="" set "%~4=%%~b")
)
exit /b 0

FWIW "reg" is documented to only ever return 0 or 1, so this is not an issue in this particular case. However, it may matter in other cases, therefore the question.

Liviu

EDIT #2 - P.S. Leaving out the 'reg query' distractions, issue was about loops like this.

Code: Select all

@echo off
setlocal disableDelayedExpansion

set "p1Error=%%errorlevel%%"
cmd /c exit 1
for /f "tokens=1,2,3,4" %%a in ('cmd /c exit 9 ^|^| call echo %errorlevel% %%errorlevel%% %%^^errorlevel%% %%p1Error%%') do (
  echo %%~a = errorlevel before the 'for' loop
  echo %%~b = errorlevel of the new instance of cmd running the 'in' command, always 0
  echo %%~c = %%~d = errorlevel returned by the master 'in' command
)

set "p2Error=%%p1Error%%"
cmd /c exit 1
for /f "tokens=1,2,3,4" %%a in ('^(cmd /c exit 9 ^|^| call echo %errorlevel% %%errorlevel%% %%^^^^errorlevel%%  %%p2Error%%^) ^| more') do (
  @rem echo %%~a = errorlevel before the 'for' loop
  @rem echo %%~b = errorlevel of the new instance of cmd running the 'in' command, always 0
  echo %%~c = %%~d = errorlevel returned by the piped master 'in' command
)

exit /b 0
Expected output, verified here under xp sp3.

Code: Select all

1 = errorlevel before the 'for' loop
0 = errorlevel of the new instance of cmd running the 'in' command, always 0
9 = 9 = errorlevel returned by the master 'in' command
9 = 9 = errorlevel returned by the piped master 'in' command

Original question was how to get the '9' values on the last lines. I had tried variations of call's and escaping, but missed the "obvious" ;-) %%^^errorlevel%% trick (courtesy Dave) and %%p*Error%% indirection (courtesy Jeb). Thanks again everybody.
Last edited by Liviu on 15 Jul 2012 10:41, edited 2 times in total.

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: errorlevel of 'command' in for /f loop

#2 Post by dbenham » 14 Jul 2012 13:24

It is important to note that the command(s) in a FOR IN() clause are executed in a new CMD shell using command line context, not batch context.

What you probably want to do is something like - ('CommandThatRaisesError ^|^| echo %errorlevel%'). But the line is parsed all at once, so the errorlevel is the value before the command was executed.

You might want to enable delayed expansion and use ('CommandThatRaisesError ^|^| echo ^^^!errorlevel^^^!'). But that doesn't work either. I'm not sure I got the escape sequence correct, but it doesn't matter because the new CMD shell does not inherit the delayed expansion state. The default for every instantiation of CMD is to have delayed expansion disabled unless you override some default registry setting. You could add your own CMD shell layer with cmd /v:on /c...., but the escape sequencing really becomes mind numbing.

The best bet is to use the CALL trick, but because it is in command line context instead of batch context, the caret escape trick is used instead of doubling the percents. There is still one set of percent doubling for the parent batch. Also the caret must be escaped for the main batch.

Here is a simple example to show how it can work

Code: Select all

@echo off
setlocal enableDelayedExpansion
for /f %%A in ('set /a 1/0 ^|^| call echo %%^^errorlevel%%') do set error=%%A
set error

I'm not going to tackle your specific example Liviu. I'll let you solve that monster. :D

Note - The same issue and solution exists when using pipes.

Dave Benham

Liviu
Expert
Posts: 470
Joined: 13 Jan 2012 21:24

Re: errorlevel of 'command' in for /f loop

#3 Post by Liviu » 14 Jul 2012 16:23

dbenham wrote:The best bet is to use the CALL trick, but because it is in command line context instead of batch context, the caret escape trick is used instead of doubling the percents. There is still one set of percent doubling for the parent batch. Also the caret must be escaped for the main batch.
Thank you. Amazing how much sense things can sometimes make in hindsight ;-)

dbenham wrote:I'm not going to tackle your specific example Liviu. I'll let you solve that monster. :D
Given the cmd beast, even the tidiest bits end up looking like monsters ;-)
That said, point taken. My original post had the actual question buried way too deeply inside irrelevant context. I made an edit which hopefully both clears it up and settles the issue.

dbenham wrote:Note - The same issue and solution exists when using pipes.

...and at the plain cmd prompt, too.

Code: Select all

C:\>cmd /c exit 9 || call echo %^errorlevel%
9

Liviu

Liviu
Expert
Posts: 470
Joined: 13 Jan 2012 21:24

Re: errorlevel of 'command' in for /f loop

#4 Post by Liviu » 14 Jul 2012 21:13

dbenham wrote:Note - The same issue and solution exists when using pipes.

For the record, since my original post had a pipe in the 'for /f' command, that combined for+piped context requires an extra caret doubling.

Code: Select all

@echo off
setlocal disableDelayedExpansion

cmd /c exit 1
for /f "tokens=1,2,3" %%a in ('^(cmd /c exit 9 ^|^| call echo %errorlevel% %%errorlevel%% %%^^^^errorlevel%%^) ^| more') do (
  echo %%~c = errorlevel returned by the master 'in' command
)
exit /b 0

Output:

Code: Select all

9 = errorlevel returned by the master 'in' command

Liviu

foxidrive
Expert
Posts: 6031
Joined: 10 Feb 2012 02:20

Re: errorlevel of 'command' in for /f loop

#5 Post by foxidrive » 14 Jul 2012 21:32

dbenham wrote:The best bet is to use the CALL trick, but because it is in command line context instead of batch context, the caret escape trick is used instead of doubling the percents. There is still one set of percent doubling for the parent batch. Also the caret must be escaped for the main batch.

Here is a simple example to show how it can work

Code: Select all

@echo off
setlocal enableDelayedExpansion
for /f %%A in ('set /a 1/0 ^|^| call echo %%^^errorlevel%%') do set error=%%A
set error



What does the caret do in this context? It normally escapes the letter following...

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: errorlevel of 'command' in for /f loop

#6 Post by dbenham » 14 Jul 2012 22:55

It is not really escaping anything. It is a hack that postpones the expansion of the variable.

Remember that in a command line context, the result of %DoesNotExist% is %DoesNotExist%. Also remember that a caret before any character results in that character being preserved. It doesn't have to be a special character.

The caret is treated as part of the variable name during the expansion phase. So lets say we have a variable named "myVar". Then a command line statement like call echo %^myVar%, tries to expand the variable "%^myVar%" before the CALL, but it doesn't exist. The caret is then removed (the m is preserved), and you are left with %myVar%. Then after the CALL it tries again and properly expands the properly named variable.

Of course the above will fail if a variable named "^myVar" exists. But carets are not normally used in variable names.


Dave Benham

foxidrive
Expert
Posts: 6031
Joined: 10 Feb 2012 02:20

Re: errorlevel of 'command' in for /f loop

#7 Post by foxidrive » 14 Jul 2012 23:12

Thanks Dave. It's logical, but bizarre. :)

jeb
Expert
Posts: 1062
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: errorlevel of 'command' in for /f loop

#8 Post by jeb » 15 Jul 2012 03:49

Dave's solution works, but it's not bullet proof.
dbenham wrote:Of course the above will fail if a variable named "^myVar" exists. But carets are not normally used in variable names.


I prefer a safe way without using carets, as they will also make trouble if you try to use quotes, then you need less carets.

Code: Select all

set "p1Error=%%errorlevel%%"
set "p2Error=%%p1Error%%"
for /f "tokens=*" %%a in ('^(cmd /c exit 9 ^|^|  call echo %%p2Error%%^) ^| more') do (
  echo %%~a = errorlevel returned by the master 'in' command
)


The p2Error variable (pointer to the p1Error variable) expands in the first cmd parsing phase, and the p1Error will expand in the call-phase.

jeb

aGerman
Expert
Posts: 4741
Joined: 22 Jan 2010 18:01
Location: Germany

Re: errorlevel of 'command' in for /f loop

#9 Post by aGerman » 15 Jul 2012 04:50

Haha, fine. But I fear that's not the final solution since it doesn't differentiate the Errorlevel and the output of a command. Is there a way to distinguish?

Code: Select all

set "p1Error=%%errorlevel%%"
set "p2Error=%%p1Error%%"
for /f "tokens=*" %%a in ('^(findstr /x "1" "test.txt" ^|^|  call echo %%p2Error%%^) ^| more') do (
  echo %%~a = errorlevel returned by the master 'in' command
)

Did or did not findstr fail?

Regards
aGerman

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: errorlevel of 'command' in for /f loop

#10 Post by dbenham » 15 Jul 2012 06:20

Good idea jeb :!:

@aGerman - The only 3 solutions I've been able to come up with are

1) - Echo a "unique" string along with the errorlevel that "guarantees" the output is the result of an error. For example. echo FINDSTR failed with error %%p2Error%%. Of course the string can always be spoofed, so there is no true guarantee.

2) - Redirect the output of the ECHOed errorlevel to an error log file.

3) - Take an entirely different approach: Run the command before the FOR statement, redirecting output to a file and capturing any error normally. Then read the output file with the FOR statement normally if and only if there was no error. This is probably the simplest solution, and also the fastest, especially if the command output is large.

Code: Select all

>tempFile.txt commandThatMightFail 
if errorlevel 1 (
  echo command failed with error %errorlevel%
else (
  for /f ... in (tempFile.txt) do ...
)


Dave Benham
Last edited by dbenham on 15 Jul 2012 06:42, edited 1 time in total.

jeb
Expert
Posts: 1062
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: errorlevel of 'command' in for /f loop

#11 Post by jeb » 15 Jul 2012 06:40

The detection of an errorlevel should be easy.
Simply append it alwys at the end, then its always the Last line.

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: errorlevel of 'command' in for /f loop

#12 Post by dbenham » 15 Jul 2012 06:46

How does that help with aGerman's example :?:

Edit
Oh, never mind, I get it now. Always echo the errorlevel, even if no error :D


Dave Benham

Aacini
Expert
Posts: 1932
Joined: 06 Dec 2011 22:15
Location: México City, México
Contact:

Re: errorlevel of 'command' in for /f loop

#13 Post by Aacini » 15 Jul 2012 08:26

Several of my auxiliary programs returns two types of values: text results that can be directly stored in variables, and an errorlevel value with a different type of value. For example, StdDate program show the Year, Month, Day parts of the date, and return its Julian Day Number via errorlevel. Dave's method allows me to get both values from the same execution:

Code: Select all

@echo off
for /F %%a in ('StdDate ^& call echo JDN^=%%^^errorlevel%%') do echo %%a
Output wrote:YYYY=2012
MM=07
DD=15
JDN=2456124
The same method may be used to distinguish the final errorlevel value from any previous text result, but this requires to store all previous results in an array:

Code: Select all

set i=0
for /F "delims=" %%a in ('any command ^& call echo %%^^errorlevel%%') do (
   set /A i+=1
   set "line[!i!]=%%a"
)
set /A numLines=i-1
rem Final errorlevel is stored in last line
if !line[%i%]! gtr 0 (
   echo The errorlevel is: !line[%i%]!
) else (
   rem Process output lines
   for /L %%a in (1,1,%numLines%) do (
      echo Processing line %%a: !line[%%a]!
   )
)

This method is particularly useful if the command display normal results, but return the number of results in errorlevel (like some of my auxiliary programs!)

Antonio

aGerman
Expert
Posts: 4741
Joined: 22 Jan 2010 18:01
Location: Germany

Re: errorlevel of 'command' in for /f loop

#14 Post by aGerman » 15 Jul 2012 09:15

Thanks guys. Enough methods to pick one for a special use.

Regards
aGerman

Liviu
Expert
Posts: 470
Joined: 13 Jan 2012 21:24

Re: errorlevel of 'command' in for /f loop

#15 Post by Liviu » 15 Jul 2012 10:46

jeb wrote:I prefer a safe way without using carets, as they will also make trouble if you try to use quotes, then you need less carets.

Code: Select all

set "p1Error=%%errorlevel%%"
set "p2Error=%%p1Error%%"
for /f "tokens=*" %%a in ('^(cmd /c exit 9 ^|^|  call echo %%p2Error%%^) ^| more') do (
  echo %%~a = errorlevel returned by the master 'in' command
)

The p2Error variable (pointer to the p1Error variable) expands in the first cmd parsing phase, and the p1Error will expand in the call-phase.


Neat! Guess it was too obvious for you to mention ;-) but without the "| more" pipe one would use %%p1Error%% instead. I have updated the sample code in my original post, just for reference. Thanks again.

Liviu

Post Reply