0

I've got true and false commands on my CMD (as part of Git Bash) for testing purposes.

So here are the basics:

C:\>true

C:\>echo %errorlevel%
0

C:\>false

C:\>echo %errorlevel%
1

The above commands and their exit codes are expected (0 on true, 1 on false).

Now, I'm negating the commands by:

C:\>true && false

C:\>echo %errorlevel%
1

C:\>false || true

C:\>echo %errorlevel%
0

This also works as expected.


Now, how I can combine two conditions at the same time (&& and ||) to negate the command?

Assuming I don't know the exit code of the command, I'd like to do something like:

SOMECMD && false || true

Here is my attempt:

C:\>true && false || true

C:\>echo %errorlevel%
0

C:\>false || true && false

C:\>echo %errorlevel%
1

The above results are not as expected. So true after negation is still true, and false is false.

So what's the easiest way to negate command not knowing its exit code?

Ideally in the same line. Something equivalent to ! in Linux (e.g. ! true and ! false).

1

I did some experimenting and here's the best method I've found so far

  1. Create a not.bat script and put in one of your PATH folders
@echo off
if %ERRORLEVEL% EQU 0 (exit /b 1) else (exit /b 0)
  1. Run this test script to confirm that the not.bat scripts works
@echo off
:: check to see if true / false commands exist or fallback to cmd.exe
:: note 9009 means MSG_DIR_BAD_COMMAND_OR_FILE according to https://stackoverflow.com/questions/23091906/official-ms-reference-for-cmd-exe-errorlevel-9009
false 2>nul
if %ERRORLEVEL% EQU 9009 (set CMDFALSE=cmd /c "exit 1") else (set CMDFALSE=false)
true 2>nul
if %ERRORLEVEL% EQU 9009 (set CMDTRUE=cmd /c "exit 0") else (set CMDTRUE=true)

:: informational
echo using '%CMDFALSE%' for false
echo using '%CMDTRUE%' for true

:: run the test
%CMDFALSE%
echo %ERRORLEVEL% - should be 1
%CMDFALSE% & not
echo %ERRORLEVEL% - should be 0

%CMDTRUE%
echo %ERRORLEVEL% - should be 0
%CMDTRUE% & not
echo %ERRORLEVEL% - should be 1

You should see this output when you run the test script

using 'false' for false
using 'true' for true
1 - should be 1
0 - should be 0
0 - should be 0
1 - should be 1

Now you can use this syntax to invert the ERRORLEVEL.

mycommand & not
echo %ERRORLEVEL%

or if you are using it in a batch file add a call in front

mycommand & call not
echo %ERRORLEVEL%
1

&& and || are not the same as IF / ELSE.

With IF / ELSE, you are guaranteed that only one branch will fire.

But || will always respond to the return code of the last executed command. So given a statement like cmd1 && cmd2 || cmd3, cmd3 will fire if cmd1 fails, or if cmd2 fails.

Given the above, it should be obvious that it is impossible to negate the return code (zero to nonzero, nonzero to zero) using just && and ||.

Obviously it could be done if you throw in a GOTO, or CALL, or... but I doubt that is what you want.

I'm assuming you want an inline, self-contained construct that will perform the negation.

It appears you are comfortable using %ERRORLEVEL%, but bear in mind that %ERRORLEVEL% is not the same as the return code of a command. For example, many internal commands succeed, but they do not set the ERRORLEVEL to 0, but rather leave the value unchanged. The && and || operators respond to the true return code, and do not look at the ERRORLEVEL.

My idea for ERRORLEVEL negation is to use SET /A testing for division by zero. But you must use delayed expansion if you want to examine the ERRORLEVEL on the same line that set the value.

(2>null set /a 1/!ERRORLEVEL! &&call ||call)

The 2>null hides the error message when there is division by 0. The &&call<space> (with a trailing space) sets the ERROLEVEL to 0 when the division succeeds, and ||call (without a trailing space) sets the ERRORLEVEL TO 1 upon divide by 0. The parentheses enable the construct to safely be used inline.

You can save the code in a variable and use it like a simple macro. Below is a complete example that demonstrates full inline usage of the %not% macro.

@echo off
setlocal enableDelayedExpansion

:: Note the delayed expansion requires the quoted ! to be escaped as ^! while defining the macro
set "not=(2>nul set /a 1/^!errorlevel^!&&call ||call)"

for /l %%N in (-2 1 2) do (
 call :setErrorLevel %%N & %not% && echo SUCCESS !errorlevel! || echo FAILURE !errorlevel!
 echo(
)
exit /b

:setErrorLevel
echo Testing %1
exit /b %1

It is important that you use a single & before the %not% so that it always executes.

Obviously you don't have to do everything inline:

@echo off
setlocal enableDelayedExpansion

:: Note the delayed expansion requires the quoted ! to be escaped as ^! while defining the macro
set "not=(2>nul set /a 1/^!errorlevel^!&&call ||call)"

for /l %%N in (-2 1 2) do (
 call :setErrorLevel %%N
 %not% 
 if errorlevel 1 (
   echo FAILURE !errorlevel! 
 ) else (
   echo SUCCESS !errorlevel!
 )
 echo(
)
exit /b

:setErrorLevel
echo Testing %1
exit /b %1

Note that echo( is just a way of printing a blank line that is safer to use than echo..

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.