Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't Windows batch file `exit` work with `||`?

The usual advice in batch / command scripting is to use exit /b to exit while setting ERRORLEVEL to indicate an error. However this doesn't play well with CMD's || and && operators. If I run these at the CMD command line:

C:\>echo @exit /b 1 > foo.cmd

C:\>foo.cmd && echo success || echo fail
success

(Expected output is fail).

According to ss64.com this is because "exit code" and ERRORLEVEL aren't always the same, and that exit can return a success (0) exit code when exiting with a non-0 ERRORLEVEL, and exit code is what ||/&&` are paying attention to.

Interestingly, adding call makes it work as I'd expect:

C:\>call foo.cmd && echo success || echo fail
fail

But requiring every use of ||/&& to also use call puts the burden on the consumer, rather than the implementation, of the script.

One example of when this might be useful is if I have a build.cmd script and a test.cmd script, I might want to run build && test.

https://ss64.com/nt/syntax-conditional.html and https://ss64.com/nt/exit.html don't mention this behavior, even though that site is usually very thorough about batch weirdness.

Why is CMD like this? What options exist to avoid this problem?

like image 714
Jay Bazuzi Avatar asked Nov 17 '25 11:11

Jay Bazuzi


2 Answers

Three examples using CMD:

C:\> Echo @exit /b 3 > throw_err.cmd
C:\> CMD /c throw_err.cmd && echo Success || echo Error: %errorlevel%
1

C:\> CMD /K throw_err.cmd && echo Success || echo Error: %errorlevel%
C:\> exit
3

C:\> Echo @exit 3 > throw_err.cmd
C:\> CMD /c throw_err.cmd && echo Success || echo Error: %errorlevel%
3

From PowerShell (with or without /b):

PS C:\> ./throw_err.cmd
PS C:\> $lastExitCode
3

Having to call a new CMD instance when you are already at the CMD prompt, just to get vaguely sensible error handling, does seem a little long winded, I think I prefer calling via PowerShell.

like image 170
SS64 Avatar answered Nov 20 '25 10:11

SS64


When you run a child script from inside of a CMD environment, flow control stays with the child script unless you use call to return to the parent environment. This is more obvious if instead of using a one-liner, you had used

@echo off
foo.cmd
if "%errorlevel%"=="1" (
    echo success
) else (
    echo fail
)

which doesn't return anything at all (since flow ended with the child script).

However, since you have everything on one line like that, the interpreter just goes "hang on; I have a block of other commands to run" and moves on to the next command (see How does the Windows Command Interpreter (CMD.EXE) parse scripts? for much more detail).

As you discovered, using call to call child scripts makes the parent environment behave correctly, since calls are executed before regular commands.

like image 22
SomethingDark Avatar answered Nov 20 '25 09:11

SomethingDark



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!