Difference between "|| exit /b" and "|| exit /b !errorlevel!"
Let's look at the three possible scenarios:
cmd /c "exit 99" || exit /b
returns 0
because cmd /c "exit 99"
executed correctly
cmd /c "exit 99" || exit /b !errorlevel!
returns 99
because cmd /c "exit 99"
set errorlevel
to 99 and we are returning the errorlevel which results from executing cmd /c "exit 99"
cmd /c "exit 99" || exit /b %errorlevel%
returns ?
- errorlevel as it was when the cmd /c "exit 99"
line was parsed as it was
at that time that `%errorlevel% was evaluated.
If delayedexpansion
was not set, then the only difference is that the !errorlevel!
scenario attempts to assign a string to the error level, which most probably won't work very well.
As for Powershell - it's a corner case on a road less travelled. A scenario that was not tested thoroughly as the designers expected to execute .exe
s, etc. using this facility. No doubt even if it is reported, it would not be fixed as there's a workaround, even if it's not well-exposed.
This is, I believe, the fail-to-fail scenario - a facility that's assumed to work because the right conditions to cause it to fail are rarely met.
In the same way,
echo %%%%b
is ambiguous. Does it mean to echo
the literal %%b
or to echo
%
prefixed to the contents of metavariable b
? (Answer : the latter). Not exactly encountered every day. What chance that the ambiguity will be resolved - ever? We can't even get /u
implemented on the date
command to have it deliver the date in a universal format which would solve the vast majority of date-oriented questions posted on the batch
tag. As for the possibility of a switch to allow date
to deliver days-since-some-epoch-date - I haven't even bothered to suggest it since, despite inviting suggestions for cmd
modifications, absolutely nothing has been done about facilities offered, just user-interface changes. Off playing with all the shiny things while ol' faithful languishes in the bottom of a locked filing cabinet stuck in a disused lavatory with a sign on the door saying ‘Beware of the Leopard.”
There is a difference between exit /b
and exit /b <code>
.
As mklement0 states, the difference becomes visible when calling a batch file with or without CALL
In my tests, I used (call)
to force the errorlevel to 1.
test1.bat
@echo off
(call)
exit /b
test2.bat
@echo off
(call)
exit /b %errorlevel%
Testing with test-all.bat:
cmd /c "test1.bat" & call echo Test1 %%errorlevel%%
cmd /c "call test1.bat" & call echo call Test1 %%errorlevel%%
cmd /c "test2.bat" & call echo Test2 %%errorlevel%%
cmd /c "call test2.bat" & call echo call Test2 %%errorlevel%%
Output:
Test1 0 call Test1 1 Test2 1 call Test2 1
To get an always reliable errorlevel, you should use the explicit form of exit /b <code>
.
In case of using the construct <command> || exit /b !errorlevel!
the delayed expansion is necessary or the form
<command> || call exit /b %%errorlevel%%
Another solution
<command> || call :exit
...
:exit
(
(goto) 2>nul
exit /b
)
This uses the batch exception handling
Does Windows batch support exception handling?
Workarounds:
Call your batch file via
cmd /c "<batch-file> ... & exit"
, in which case the|| exit /b
solution without an explicit exit code works as expected:cmd /c ".\test.bat & exit"
- If needed, escape any embedded
"
characters as`"
, such as around batch-file paths and pass-through arguments that contain spaces:cmd /c ".\test.bat `"quoted argument`" & exit"
- Alternatively, if you don't need PowerShell's string interpolation to embed variable values in the call, you can use
'...'
quoting, in which case embedded"
can be used as-is:cmd /c '.\test.bat "quoted argument" & exit'
- If needed, escape any embedded
Using
cmd /c "<batch-file> ... & exit"
routinely to call batch files from outsidecmd.exe
is advisable, as even batch files without explicitexit /b
(orexit
) calls can otherwise behave unexpectedly - see this answer.
Alternatively - but only if your batch file never needs to be called from another batch file to which control should be returned and if it never needs to be part of a
cmd /c
multi-command command line where it isn't the last command[1] - you can use|| exit
instead of|| exit /b
- this exits the executingcmd.exe
process as a whole, instantly, but the exit code (error level) is then reliably reported (at least in the context of a<command> || exit
statement) also with direct invocation from outsidecmd.exe
, such as& .\test.bat
(or, in this simple case, just.\test.bat
) from PowerShell.
While combining setlocal EnableDelayedExpansion
with exit /b !ERRORLEVEL!
works too (except inside (...)
- see this post) - due to using an explicit exit code - it is obviously more cumbersome and can have side effects, notably quietly removing !
characters from commands such as echo hi!
(while it's possible to minimize that problem by placing the setlocal EnableDelayedExpansion
call on the line just before an exit /b
call, that would require duplication if there are multiple exit points).
cmd.exe
's behavior is unfortunate in this case, but can't be avoided.
When calling a batch file from outside cmd.exe
:
exit /b
- without an exit-code (error-level) argument - only sets thecmd.exe
process exit code as expected - namely to the exit code of the most recently executed command in the batch file - if you follow the batch-file call with& exit
, i.e. ascmd /c <batch-file> ... `& exit
Without the
& exit
workaround, an argument-lessexit /b
call from a batch file is reflected in the%ERRORLEVEL%
variable intra-cmd.exe
-session, but that doesn't translate tocmd.exe
's process exit code, which then defaults to0
.[1]With the
& exit
workaround, intra-batch-file argument-lessexit /b
does properly setcmd.exe
's exit code, even in a<command> || exit /b
statement, in which case<command>
's exit code is relayed, as intended.
exit /b <code>
, i.e. passing an exit code<code>
explicitly, always works[2], i.e. the& exit
workaround is then not needed.This distinction is an obscure inconsistency that could justifiably be called a bug; Jeb's helpful answer has sample code that demonstrates the behavior (using the less comprehensive
cmd /c call ...
workaround as of this writing, but it applies equally tocmd /c "... & exit"
).
[1] With cmd /c
, you can pass multiple statements for execution, and it is the last statement that determines the cmd.exe
process' exit code. E.g, cmd /c "ver & dir nosuch"
reports exit code 1
, because the non-existent file-system item nosuch
caused dir
to set the error level to 1
, irrespective of whether or not the preceding command (ver
) succeeded. The inconsistency is that, for a batch file named test.bat
which exits with exit /b
without an explicit exit-code argument, cmd /c test.bat
always reports 0
, whereas cmd /c test.bat `& exit
properly reports the exit code of the last statement executed before the batch file exited.
[2] The exit code may be specified literally or via a variable, but the pitfall is that - due to cmd.exe
's up-front variable expansion - <command> || exit /b %ERRORLEVEL%
does not work as intended, because %ERRORLEVEL%
at that point expands to the error level prior to this statement, not to the one set by <command>
; this is why delayed expansion, via having run setlocal enabledelayedexpansion
or having invoked the cmd.exe
with the /V
option, is necessary in this case: <command> || exit /b !ERRORLEVEL!