1

Suppose I have batch file one.cmd:

@two.cmd && echo success || echo fail

and two.cmd:

@exit /b 1

If I run this at an interactive CMD prompt it behaves one way:

D:\>one.cmd
success

But if I run it under cmd /c it behaves a different way:

D:\>cmd /c one.cmd
fail

Why is this?

Some other cases

PowerShell and Python's subprocess.run both give the same result as cmd /c.

cmd /k gives the same result as running interactively. Which I guess makes sense, given that it's a new interactive shell.

Why this matters

I know how to fix this. Either (or both) of:

  1. any use of || and && should be paired with call.
  2. always exit batch files with cmd.exe /c exit %ERRORLEVEL%.

See more here: Why doesn't Windows batch file `exit` work with `||`?).

My real goal is to write a unit test to ensure that my batch file correctly propagates error on failure, but because the unit test is not using an interactive shell, it doesn't reproduce the problem.

Jay Bazuzi
  • 45,157
  • 15
  • 111
  • 168
  • 1
    Should you not be using `@Call two.cmd ...` – Compo Apr 06 '23 at 14:58
  • 1
    A batch file must be __called__ from within a batch file using the command `call`. I recommend to read my answer on [How to call a batch file that is one level up from the current batch file directory?](https://stackoverflow.com/a/24725044/3074564) It describes the four possible methods to run a batch file from within a batch file and what are the differences in execution behavior. – Mofi Apr 07 '23 at 05:54
  • I think I already said that. – Jay Bazuzi Apr 08 '23 at 06:02
  • `@two.cmd && echo success || echo fail` in batch file `one.cmd` results in an __undefined behavior__. The processing of batch file `one.cmd` after parsing the single command line and putting all the commands into the command execution queue of the Windows *Command Processor* __ends__ already with the continuation of batch file processing on batch file `cmd.exe`. The entire batch file processing ends with the execution of `@exit /B 1` in `two.cmd`. But there is in command execution queue from `one.cmd` still `&& echo success || echo fail` with the __conditional__ command operators `&&` and `||` – Mofi Apr 08 '23 at 10:46
  • But the batch file processing is already exited. So, how should the conditional command operators `&&` and `||` be processed now being in the command execution queue although the batch processing already exited? Well, it does something by comparing the value of the __dynamic__ variable (integer variable in memory of `cmd.exe`, __not__ an environment variable) with integer value 0 and runs `echo success` and `errorlevel` being `0` otherwise on any non-zero value `echo success` is just removed from command execution queue. – Mofi Apr 08 '23 at 10:51
  • Then after execution of `echo success` or removal from command execution queue is processed `||` with comparing once again the current value of `errorlevel` with integer value 0 and `echo fail` is executed on value is not equal 0. `echo success` on execution on `errorlevel` having value `0` on previous check does not change the value of `errorlevel` variable and therefore this combined usage of `&&` and `||` works at all in general. Even if it looks like the execution behavior is always the same on running `one.cmd` either directly or with `cmd /c one.cmd`, the wrong syntax in `one.cmd` ... – Mofi Apr 08 '23 at 10:56
  • 1
    ... results in an __undefined behavior__ on execution of `one.cmd` regarding to the two conditional command operators `&&` and `||`. It is simply wrong to write in a batch file like `one.cmd` a command line like that. That is similar to writing in a C source code file `int errorlevel; if (errorlevel == 0) printf("success\n"); if (errorlevel != 0) printf("fail\n");` after a line which calls another procedure. The execution behavior is undefined by wrong code. That is a fact which must not be analyzed further. The code in `one.cmd` must be corrected to get a defined behavior. – Mofi Apr 08 '23 at 11:02
  • That's a lot of detail, @Mofi. Seems like a good candidate for an Answer? – Jay Bazuzi Apr 08 '23 at 21:54

1 Answers1

0

Mofi already mentioned it in his comment: A batch/cmd file have to be called in a batch file.
The cause is, a CALL put a return point to the call stack, but without CALL there is no entry on the stack!
In your case, at first glance, there seems to be no difference, but try to add one more line to one.bat

one.bat

@two.cmd && echo success || echo fail
echo This line will never be reached

Or add a call to a label

one.bat

@two.cmd && echo success || echo fail & call :myLabel
echo This line will never be reached
:myLabel
echo This line will never be reached

Invalid attempt to call a jump target outside a batch file.

The cause for seeing the success or fail messages is the fact, that a line or block will always be cached.
It isn't a return to the code, it's simply the execution of the cache.

jeb
  • 78,592
  • 17
  • 171
  • 225
  • I don't understand how this answers my question. – Jay Bazuzi Apr 08 '23 at 06:04
  • @JayBazuzi Short: If your code is broken, your results are unreliable. Execute `two.cmd` without CALL breaks the code. Why you want results for syntactic incorrect code? – jeb Apr 08 '23 at 15:58
  • Thanks for taking the time, @jeb. It's a common practice at the interactive command line to run `build && test` or `gradlew test && git commit || git reset` without wondering if `build` or `gradlew` are batch files. That means either A) everyone must learn to use `call` at the interactive command line if they are going to use `||` or B) every batch file should end with `cmd /c exit %ERRORLEVEL%` (or similar). It sounds like you recommend option A? – Jay Bazuzi Apr 09 '23 at 17:19