In most circumstances, ||
is the most reliable way to detect an error. But you have stumbled on one of the rare cases where ERRORLEVEL works but ||
does not.
The problem stems from the fact that your error is raised within a batch script, and ||
responds to the return code of the most recently executed command. You are thinking of test.bat as a single "command", but actually it is a sequence of commands. The last command executed within the script is GOTO :EOF
, and that executed successfully. So your test.bat||echo 99
is responding to the success of the GOTO :EOF
.
When you remove the ||GOTO :EOF
from within the script, then your test.bat||echo99
sees the result of the failed mkdir
. But if you were to add a REM
command to the end of test.bat, then test.bat||echo 99
would respond to the success of the REM
, and the error would be masked again.
The ERRORLEVEL is still non-zero after test.bat||echo 99
because commands like GOTO
and REM
do not clear any prior non-zero ERRORLEVEL upon success. This is one of many pieces of evidence that ERRORLEVEL and the return code are not quite the same thing. It definitely gets confusing.
You can treat test.bat as a unit command and get the behavior you want by using CALL.
C:est>call test.bat && echo OK || echo FAIL
FAIL
C:est>if ERRORLEVEL 1 (echo FAIL2) else echo OK2
FAIL2
This works because the CALL
command temporarily transfers control to the called script. When the script terminates, control is returned to the CALL
command, and it returns the current ERRORLEVEL. So ||echo 99
is responding to the error returned by the CALL command itself, not the last command within the script.
Now for the CMD /C
issue.
The return code returned by CMD /C
is the return code of the last command executed.
This works:
C:est>cmd /c call test.bat && echo OK || echo FAIL
FAIL
C:est>if ERRORLEVEL 1 (echo FAIL2) else echo OK2
FAIL2
because CMD /C
returns the ERRORLEVEL returned by the CALL
statement
But this fails entirely:
C:est>cmd /c test.bat && echo OK || echo FAIL
OK
C:est>if ERRORLEVEL 1 (echo FAIL2) else echo OK2
OK2
Without the CALL
, the CMD /C
returns the return code of the last executed command, which is the GOTO :EOF
. The CMD /C
also sets the ERRORLEVEL to the same return code, so now there is no evidence that there ever was an error within the script.
And down the rabbit hole we go
R.L.H., in his answer and in his comments to my answer, is concerned that ||
sometimes clears the ERRORLEVEL. He provides evidence that appears to back up his conclusion. But the situation isn't that simple, and it turns out that ||
is the most reliable (but still not perfect) way to detect errors.
As I stated earlier, the return code that all external commands return upon exit is not the same thing as the cmd.exe ERRORLEVEL.
The ERRORLEVEL is a state maintained within the cmd.exe session itself, wholly distinct from return codes.
This is even documented in the definition of the exitCode within the EXIT help
(help exit
or exit /?
)
EXIT [/B] [exitCode]
/B specifies to exit the current batch script instead of
CMD.EXE. If executed from outside a batch script, it
will quit CMD.EXE
exitCode specifies a numeric number. if /B is specified, sets
ERRORLEVEL that number. If quitting CMD.EXE, sets the process
exit code with that number.
When an external command is run by CMD.EXE, it detects the executeable's return code and sets the ERRORLEVEL to match. Note that it is only a convention that 0 means success, and non-zero means error. Some external commands may not follow that convention. For example, the HELP command (help.exe) does not follow the convention - it returns 0 if you specify an invalid command as in help bogus
, but returns 1 if you ask for help on a valid command, as in help rem
.
The ||
operator never clears the ERRORLEVEL when an external command is executed. The process exit code is detected and fires ||
if it is non-zero, and the ERRORLEVEL will still match the exit code. That being said, the commands that appear after &&
and/or ||
may modify the ERRORLEVEL, so one has to be careful.
But there are many other situations besides external commands where we developers care about success/failure and return codes/ERRORLEVELs.
- execution of internal commands
- redirection operators
<
, >
, and >>
- execution of batch scripts
- failed execution of invalid commands
Unfortunately, CMD.EXE is not at all consistent in how it handles error conditions for these situations. CMD.EXE has multiple internal points where it must detect errors, presumably through some form of internal return code that is not necessarily the ERRORLEVEL, and at each of these points CMD.EXE is in a position to set the ERRORLEVEL depending on what it finds.
For my test cases below, note that (call )
, with a space, is arcane syntax that clears the ERRORLEVEL to 0 before each test. Later on, I will also use (call)
, without a space, to set the ERRORLEVEL to 1
Also note that delayed expansion has been enabled within my command session by using
cmd /v: on
prior to running my tests
The vast majority of internal commands set the ERRORLEVEL to a non-zero value upon failure, and the error condition also fires ||
. The ||
never clears or modifies the ERRORLEVEL in these cases.
Here are a couple examples:
C:est>(call ) & set /a 1/0
Divide by zero error.
C:est>echo !errorlevel!
1073750993
C:est>(call ) & type notExists
The system cannot find the file specified.
C:est>echo !errorlevel!
1
C:est>(call ) & set /a 1/0 && echo OK || echo ERROR !errorlevel!
Divide by zero error.
ERROR 1073750993
C:est>(call ) & type notExists.txt && echo OK || echo ERROR !errorlevel!
The system cannot find the file specified.
ERROR 1
Then there is at least one command, RD, (possibly more), as well as the redirection operators that fire ||
upon error, but do not set ERRORLEVEL unless ||
is used.
C:est>(call ) & rd notExists
The system cannot find the file specified.
C:est>echo !errorlevel!
0
C:est>(call ) & echo x >adPathout.txt
The system cannot find the path specified.
C:est>echo !errorlevel!
0
C:est>(call ) & rd notExists && echo OK || echo ERROR !errorlevel!
The system cannot find the file specified.
ERROR 2
C:est>(call ) & echo x >adPathout.txt && echo OK || echo ERROR !errorlevel!
The system cannot find the path specified.
ERROR 1
See "rd" exits with errorlevel set to 0 on error when deletion fails, etc and File redirection in Windows and %errorlevel% for more information.
I know of one internal command (there may be others) plus basic failed I/O operations that can issue error messages to stderr, yet they don't fire ||
nor do they set a non-zero ERRORLEVEL.
The DEL command can print an error if the file is read only, or does not exist, but it does not fire ||
or set ERRORLEVEL to non-zero
C:est>(call ) & del readOnlyFile
C:est
eadOnlyFile
Access is denied.
C:est>echo !errorlevel!
0
C:est>(call ) & del readOnlyFile & echo OK || echo ERROR !errorlevel!
C:est
eadOnlyFile
Access is denied.
OK
See https://stackoverflow.com/a/32068760/1012053 for a bit more information related to DEL errors.
In much the same way, when stdout has been successfully redirected to a file on a USB device, but then the device is removed before a command such as ECHO tries to write to the device, then the ECHO will fail with an error message to stderr, yet ||
does not fire, and ERRORLEVEL is not set to non-zero. See http://www.dostips.com/forum/viewtopic.php?f=3&t=6881 for more info.
Then we have the case where a batch script is executed - the actual subject of the OP's question. Without CALL
, The ||
operator responds to the last command executed within the script. With CALL
, the ||
operator responds to the value returned by the CALL
command, which is the final ERRORLEVEL that exists upon batch termination.
Finally, we have the case that R.L.H. reports, where an invalid command is reported as ERRORLEVEL 9009 normally, but as ERRORLEVEL 1 if ||
is used.
C:est>(call ) & InvalidCommand
'InvalidCommand' is not recognized as an internal or external command,
operable program or batch file.
C:est>echo !errorlevel!
9009
C:est>(call ) & InvalidCommand && echo OK || echo ERROR !errorlevel!
'InvalidCommand' is not recognized as an internal or external command,
operable program or batch file.
ERROR 1
I can't prove this, but I suspect that the detection of command failure and setting of ERRORLEVEL to 9009 occurs very late in the command execution process. I'm guessing that ||
intercepts the error detection before the 9009 is set, at which point it sets it to 1 instead. So I don't think ||
is clearing the 9009 error, but rather it is an alternate pathway by which the error is handled and set.
An alternate mechanism for this behavior is that the invalid command could always set the ERRORLEVEL to 9009, yet have a different return code of 1. The ||
could subsequently detect the 1 return code and set the ERRORLEVEL to match, thus overwriting the 9009.
Regardless, I am not aware of any other situation where a non-zero ERRORLEVEL result is different depending on whether ||
was used or not.
So that takes care of what happens when a command fails. But what about when an internal command succeeds? Unfortunately, CMD.EXE is even less consistent than it was with errors. It varies by command, and may also depend on whether it is executed from the command prompt, from a batch script with a .bat
extension, or from a batch script with a .cmd
extension.
I am basing all of the discussion below on Windows 10 behavior. I doubt there are differences with earlier Windows versions that use cmd.exe, but it is possible.
The following commands always clear the ERRORLEVEL to 0 upon success, regardless of context:
- CALL : Clears ERRORLEVEL if the CALL