Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does cmd run my second command when I use && even though the first one failed?

Tags:

windows

cmd

I'm experimenting with Rust. I want to compile a program, and only if it succeeds, run it. So I'm trying:

rustc hello.rs && hello

But hello.exe always runs, even if compilation fails.

If I try

rustc hello.rs
echo Exit Code is %errorlevel% 

I get "Exit Code is 101".

As I understand it, the only truthy value is 0 in cmd, which 101 is clearly not, and && is lazily evaluated, so why does it run hello?


rustc.bat looks like this:

@echo off
SET DIR=%~dp0%
cmd /c "%DIR%..\lib\rust.0.11.20140519\bin\rustc.exe %*"
exit /b %ERRORLEVEL%
like image 871
mpen Avatar asked Jul 05 '14 07:07

mpen


Video Answer


2 Answers

Very curious this. Put a CALL in front and all should be fine.

call rustc hello.rs && hello

I don't totally understand the mechanism. I know that && and || do not read the dynamic %errorlevel% value directly, but operate at some lower level. They conditionally fire based on the outcome of the most recently executed command, regardless of the current %errorlevel% value. The || can even fire for a failure that does not set the %errorlevel%! See File redirection in Windows and %errorlevel% and batch: Exit code for "rd" is 0 on error as well for examples.

Your rustc is a batch file, and the behavior changes depending on if CALL was used or not. Without CALL, the && and || operators respond only to whether the command ran or not - they ignore the exit code of the script. With CALL, they properly respond to the exit code of the script, in addition to responding if the script failed to run (perhaps the script doesn't exist).

Put another way, batch scripts only notify && and || operators about the exit code if they were launched via CALL.

UPDATE

Upon reading foxidrive's (now deleted) answer more carefully, I realize the situation is more complicated.

If CALL is used, then everything works as expected - && and || respond to the ERRORLEVEL returned by the script. ERRORLEVEL may be set to 1 early on in the script, and as long as no subsequent script command clears the error, the returned ERRORLEVEL of 1 will be properly reported to && and ||.

If CALL is not used, then && and || respond to the errorcode of the last executed command in the script. An early command in the script might set ERRORLEVEL to 1. But if the last command is an ECHO statement that executes properly, then && and || respond to the success of the ECHO command instead of the ERRORLEVEL of 1 returned by the script.

The real killer is that EXIT /B 1 does not report the ERRORLEVEL to && or || unless the script was invoked via CALL. The conditional operators detect that the EXIT command executed successfully, and ignore the returned ERRORLEVEL!

The expected behavior can be achieved if the last command executed by the script is:

cmd /c exit %errorlevel%

This will properly report the returned ERRORLEVEL to && and ||, regardless whether the script was invoked by CALL or not.

Here are some test scripts that demonstrate what I mean.

test1.bat

@echo off
:: This gives the correct result regardless if CALL is used or not
:: First clear the ERRORLEVEL
(call )
:: Now set ERRORLEVEL to 1
(call)

test2.bat

@echo off
:: This only gives the correct result if CALL is used
:: First clear the ERRORLEVEL
(call )
:: Now set ERRORLEVEL to 1
(call)
rem This command interferes with && or || seeing the returned errorlevel if no CALL

test3.bat

@echo off
:: This only gives the correct result if CALL is used
:: First clear the ERRORLEVEL
(call )
:: Now set ERRORLEVEL to 1
(call)
rem Ending with EXIT /B does not help
exit /b %errorlevel%

test4.bat

@echo off
:: This gives the correct result regardless if CALL is used or not
:: First clear the ERRORLEVEL
(call )
:: Now set ERRORLEVEL to 1
(call)
rem The command below solves the problem if it is the last command in script
cmd /c exit %errorlevel%

Now test with and without CALL:

>cmd /v:on
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

>test1&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Failure with errorlevel=1

>test2&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Success, yet errorlevel=1

>test3&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Success, yet errorlevel=1

>test4&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Failure with errorlevel=1

>call test1&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Failure with errorlevel=1

>call test2&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Failure with errorlevel=1

>call test3&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Failure with errorlevel=1

>call test4&&echo Success, yet errorlevel=!errorlevel!||echo Failure with errorlevel=!errorlevel!
Failure with errorlevel=1

>
like image 56
dbenham Avatar answered Nov 05 '22 11:11

dbenham


Quick demo to prove the point:

@ECHO OFF
SETLOCAL
CALL q24983584s 0&&ECHO "part one"
ECHO done one
CALL q24983584s 101&&ECHO "part two"
ECHO done two

GOTO :EOF

where q24983584s.bat is

@ECHO OFF
SETLOCAL
EXIT /b %1

GOTO :EOF

works as expected...

like image 43
Magoo Avatar answered Nov 05 '22 11:11

Magoo