ERRORLEVEL is not %ERRORLEVEL%


The command interpreter cmd.exe has a concept known as the error level, which is the exit code of the program most recently run. You can test the error level with the IF ERRORLEVEL command:

IF ERRORLEVEL 1 ECHO error level is 1 or more

<sidebar>
The IF ERRORLEVEL n test succeeds if the error level is n or more. This was presumably because there were programs that expressed different degrees of failure with higher and higher exit codes. For example, the diff program has three exit codes: 0 means the files are the same; 1 means the files are different; 2 means that something terrible happened. There are also programs that use an exit code of zero to mean success and anything else to mean failure.
</sidebar>

In addition to this internal state, you can, if you wish, create an environment variable with the name ERRORLEVEL, in the same way that you can create an environment variable called FRED. But, as with FRED, that variable won't have any effect on the error level.

rem this next command sets the error level to zero
CMD /C EXIT 0
set ERRORLEVEL=1
if ERRORLEVEL 1 echo Does this print?

The message is not printed because the ERRORLEVEL environment variable has no effect on the error level. It's just a variable whose name happens to coincide with a command processor concept.

set BANKBALANCE=$1,000,000.00

"Hey, when I tried to withdraw the money, I got an insufficient funds error. What am I doing wrong?"

Now, it does happen to be the case that if command extensions are enabled and you say %ERRORLEVEL%, then the command processor first looks for an environment variable called ERRORLEVEL, and if it can't find one, then it replaces %ERRORLEVEL% with the current value of the internal error level value. It's a fallback step, in the same way that your neighbor is a fallback delivery location if you aren't home. If you file a change-of-address form for yourself, that doesn't affect packages sent to your neighbor.

The same behavior can be seen with %CD%: If you did not explicitly set an environment variable called CD, then %CD% expands to the command processor's current directory. But you can't change directories by saying set CD=C:\Windows.

I can think of a few reasons why this feature may have been added.

  • So you can include the error level in a log file:
    ECHO error level is %ERRORLEVEL%>logfile

  • So you can perform other types of tests against the error level, for example, to perform an equality test:
    IF %ERRORLEVEL% EQU 1 echo Different!

But I'm digressing. My point for today is that the error level is not the same as the ERRORLEVEL environment variable.

Comments (15)
  1. Tom says:

    Oops.

    Thanks for pointing out the differences between ERRORLEVEL and %ERRORLEVEL%.  I just happened to have finished writing a batch script that was getting ready to go into production using the latter that worked simply because of the fall-back nature of the shell.  I’ll have to go back and fix it because the “greater than or equal to” behavior was expected but won’t happen due to my mistake.

    [It’s fine to rely on the fall-back—it’s documented and supported. You just have to understand that it’s a fallback and not an actual variable. -Raymond]
  2. Adam says:

    I feel like have a special shell builtin called ERRORLEVEL that can be confused with an environment variable is just asking for trouble.  Why not just have an environment variable called %ERRORLEVEL% which is automatically updated to the error level whenever a command finishes running?  Then there’s no possibility of confusion, although anything which tries to use that environment variable will not work.  Bash uses the variable $? for exactly this purpose, which no sane program would try to use as its own environment variable.

    [You gave the answer yourself: “Anything which tries to use that environment variable will not work.” But if you don’t have such a variable, then %ERRORLEVEL% does expand to to the current error level. What you can’t do is set the error level via “set ERRORLEVEL=…”. In the same way that bash doesn’t let you “set ?=…”. -Raymond]
  3. Denis Dmitriev says:

    It’s still asking for trouble because it introduces action at a distance:

    H:moo>cat quack.bat

    @echo off

    diff

    echo %ERRORLEVEL%

    H:moo>quack.bat

    diff: missing operand after `diff’

    diff: Try `diff –help’ for more information.

    2

    H:moo>set ERRORLEVEL="meow"

    H:moo>quack.bat

    diff: missing operand after `diff’

    diff: Try `diff –help’ for more information.

    "meow"

    H:moo>

    So if one wants to rely on this fallback, a prudent thing to do would be to clean the ERRORLEVEL variable first (though I don’t know if this will work on all versions of Windows):

    H:moo>set ERRORLEVEL=

    H:moo>quack.bat

    diff: missing operand after `diff’

    diff: Try `diff –help’ for more information.

    2

    H:moo>

  4. Pierre B. says:

    Well, at least bash literally doesn’t allow you to set the $? variable at all. The set and export command fail if you try. CMD.exe allows you to set it but then from that point on the variable is mostly meaningless.

    Maybe cmd.exe builtin set could set its exit value to the value passed in instead of setting the environment variable when the variable being set in is named ERRORLEVEL? That would be a neat trick.

    (I would guess the number of programs that would be broken by the change would be quite near zero.)

    [I would not be surprised if there were batch files that went
    set ERRORLEVEL=fail
    if … set ERRORLEVEL=pass
    echo Test XYZ result: %ERRORLEVEL% >> logfile

    -Raymond
    ]
  5. Igor Levicki says:

    What Raymond wanted to say is that ERRORLEVEL is not %ERRORLEVEL% if you set ERRORLEVEL ;-)

  6. Maurits says:

    Is there, then, a way to set the "error level" from batch?  Or is it necessary to write an exe

    // return-a-number.exe

    int main (int argc, LPCSTR argv[])

    {

       return argc ? atoi(argv[0]) : 0;

    }

    … and then call it from batch?

    if … return-a-number 17

  7. Maurits says:

    Actually reading the post, it appears CMD /C EXIT 17 works.

  8. Peter says:

    I’ve just updated the ExpandEnvironmentStrings MSDN entry (*) to reflect this — the CMD expansion is really different from what the “real” expansion function does.

    Would anyone at Microsoft care to make the official CMD expansion into a useful function?  Seems unfair that the microsoft tool gets fancy environment variable expansion, but the only API exposed does plain and ordinary expansion.

    (*) Really just the “Comments” section, not the entry itself.

    [And which is the “last” process that %ERRORLEVEL% should return the exit code of? What if that process hasn’t exited yet? The kernel and the command processor operate at very different levels. -Raymond]
  9. Andrew from Vancouver says:

    Accessing %ERRORLEVEL% in a batch is useful to capture the errorlevel, e.g.

    set result=0

    find /I "whatever" temp.txt

    set result=%ERRORLEVEL%

    REM Now do a bunch of IF statements based on the error level value, but checking %ERRORLEVEL%, some of which would set a new error level value

    REM … but we do not care because we’re checking the %result% variable, not errorlevel

    In a complex script with lots of tests and shelling to external scripts and executables, this is handier and more robust than a series of:

    if errorlevel 3 (REM whatever3 & goto wherever3)

    if errorlevel 1 (REM whatever1 & goto wherever1)

    if not errorlevel 0 @echo success!

    Andrew 8)

  10. Maurits says:

    The IF ERRORLEVEL n test succeeds if the error level is n or more. This was presumably because…

    The test for inequality is nice to have because the pseudo-environment-variable gives an easy test for equality: IF "%ERRORLEVEL%"=="%N%"

    Mathematically speaking, the two are equivalent, though; given one you can simulate the other.

    rem TASK 1: using only

    rem     if ERRORLEVEL n

    rem simulate

    rem     if "%ERRORLEVEL%"=="%n%"

    rem … specifically, execute command foo under the specific condition

    rem that the error level is equal to 17.

    setlocal

    set dofoo=no

    if ERRORLEVEL 17 set dofoo=yes

    if ERRORLEVEL 18 set dofoo=no

    if "%dofoo%"=="yes" foo

    rem TASK 2: using only

    rem     if "%ERRORLEVEL%"=="%n%"

    rem simulate

    rem     if ERRORLEVEL n

    rem … specifically, execute command foo under the specific condition

    rem that the error level is >= 17.

    rem

    setlocal

    set dofoo=yes

    set i=0

    :STARTLOOP

    if "%i%"=="17" goto EXITLOOP

    if "%ERRORLEVEL%"=="%n%" set dofoo=no

    set /a i = %i% + 1

    goto STARTLOOP

    :EXITLOOP

    if "%dofoo%"=="yes" foo

    But as Andrew says, having both gives you some nice syntactic sugar.

  11. Good post.  This was an issue I fought with a few months ago on an embedded system running DOS (real DOS, not CMD.EXE).  It took me a little while to figure out that ERRORLEVEL wasn’t a normal environment variable.

  12. eddie says:

    you know, Go To Statement Considered Harmful.

    I have written if errorlevel == 3 goto tag3 more times that i would like to. And I still hate it.

  13. Marty says:

    A god safety net is to reset ERRORLEVEL each time you use it, similar to the SetLAstError() function.

    For example:

    Set ERRORLEVEL=1000

    myprogram.exe

    Echo This is not the exit code: %ERRORLEVEL%

    Set ERRORLEVEL=

    myprogram.exe

    Echo This is the exit code: %ERRORLEVEL%

  14. Jay Bazuzi says:

    And the correct way to set ERRORLEVEL?  Use ‘exit’, perhaps as ‘exit /b’.  Use ‘exit /?’ for help.

  15. SRS says:

    if /?

    gives loads of info on this too. Btw if you want to discover all the goodies in cmd.exe, the following commands give good help:

    if /?

    call /?

    set /?

    goto /?

Comments are closed.