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

359 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
aschipfl 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.