How to call one Windows batch script from another with delayed expansion on in both

332 Views Asked by At

I have two .bat files, both use delayed expansion, so that I can set variables within for loops.

The example below is greatly simplified just to show the problem

Script one.bat

@echo off

setlocal enableextensions
setlocal enabledelayedexpansion

set j=0
for /L %%i in (1,1,2) do (
  set j=%%i
  set /A j=!j! + 1
  echo %%i !j!
  two.bat
  echo %%i !j!
)

Script two.bat

@echo off

setlocal enableextensions
setlocal enabledelayedexpansion

echo Hello World

exit /B 0

On return from two.bat variable !j! is lost and echo is turned back on.

J:\>one
1 2
Hello World
1 !j!

J:\>(
set j=2
 set /A j=!j! + 1
 echo 2 !j!
 two.bat
 echo 2 !j!
)
Missing operator.
2 !j!
Hello World
2 !j!

Yes I could make two.bat a sub-routine in one.bat but its hundred of lines long and I dont want to have to maintain two copies of the same logic

What am I missing here ?

1

There are 1 best solutions below

0
On

Your assumption is wrong that execution returns from file two.bat, because that is only the case when you are using the call command1.

The batch file one.bat runs two.bat within a parenthesised block of code, which is already in a command stack2, so the block is (kind of) finished before terminating execution.


Your output perfectly illustrates what happens (therefore I commented it here):

J:\>one              
1 2                  /* first loop iteration, the first `echo %%i !j!` in the block is
                        executed as expected; */
Hello World          /* `two.bat` is run; execution does NOT return to `one.bat`,
                        because there is NO `call`, which would put a return point onto
                        the stack! the only thing remembered and thus accomplished is
                        the current command line or parenthesised block; */
1 !j!                /* the second `echo %%i !j!` in the block is executed as it is
                        still buffered, but `two.bat` is already quit, hence implicit
                        `endlocal` commands have been executed, so all the nested
                        `setlocal` commands in your scripts are cancelled and delayed
                        expansion is disabled (default state); moreover, variable `j` is
                        no longer defined here; */
                     /* at this point, the parenthesised block, which is the loop body,
                        has been executed, hence batch file context is left and Command
                        Prompt context applies, so `@echo off` from `one.bat` does no
                        longer apply here; */
J:\>(                // this block is nothing but the echo of the second loop iteration,
set j=2              // which is still buffered;
 set /A j=!j! + 1    
 echo 2 !j!          
 two.bat             
 echo 2 !j!          
)                    
Missing operator.    /* this error message is caused by the attempt to execute
                        `set /A j=!j! + 1` (remember that delayed expansion is no longer
                        enabled and `j` is undefined); */
2 !j!                // first `echo %%i !j!` in the buffered second loop iteration;
Hello World          /* `two.bat` is run again; afterwards, batch file context is left
                        again and Command Prompt context applies; */
2 !j!                // second `echo %%i !j!` in the buffered second loop iteration;

To prove whether execution happens under batch file or Command Prompt context, just place set /A j in the loop body in one.bat as the last command, so you will get an additional output 0 after the output 1 !j! and the second 2 !j!, because set /A does not return anything in batch file context, but it outputs the (last) result (without a trailing line-break) in Command Prompt context; the value of 0 shows that j is no longer set.


1) There are a few exceptions: the called batch file is involved in a pipe, or it is run and parsed by a for /F loop, because the batch file runs in a new cmd.exe instance in such cases.
2) The same would be true if the called batch file was involved in a line with concatenated commands, hence something like two.bat & echo Fine would echo Fine upon execution of two.bat.