1

There are two (2) Windows batch scripts. The first one will CALL the second one. However, SETLOCAL must be used around the call to the second batch script because ENABLEDELAYEDEXPANSION is needed in a FOR loop.

=== batch1.bat

@echo off
echo in %0
set BAT1_VAR=5
SETLOCAL
    call  batch2.bat
    set|find "BAT"
    echo === before endlocal
ENDLOCAL
set|find "BAT"
echo === after endlocal
echo === begin exiting %0
set|find "BAT"
echo === end exiting %0
exit /B 0

=== batch2.bat

@echo off
echo in %0
SET BAT2_VAR=7
echo === begin exiting %0
SET|FIND "BAT"
echo === end exiting %0
EXIT /B 0

=== The problem is that the variable set by batch2.bat needs to still exist after the return to batch1.bat. This can be resolved by using:

ENDLOCAL & set BAT2_VAR=%BAT2_VAR%

HOWEVER, I do not want batch1.bat to need to know the names of variables created by batch2.bat. There might be many and the list may change.

Any ideas to overcome this problem? Thanks.

lit
  • 14,456
  • 10
  • 65
  • 119

1 Answers1

9

If you are in a loop, or any parenthesized block of code, then you cannot use the ENDLOCAL & set BAT2_VAR=%BAT2_VAR% method.

Something like this would fail

REM delayed expansion is disabled to start
for ... %%A in (....) do (
   REM must SET before enabling delayed expansion to preserve ! in %%A value
   set var=%%A
   REM the remainder of the loop needs delayed expansion
   setlocal enableDelayedExpansion
   REM do something with !var!
   call batch2 !var!
   REM maybe the batch2 modifies var some more
   REM Must endlocal and preserve the value. The attempt below fails because %var% will
   REM be the value that existed before the loop was entered
   endlocal & set "var=%var%"
)

A simple fix is to transfer the value via a FOR variable

REM delayed expansion is disabled to start
for ... %%A in (....) do (
   REM must SET before enabling delayed expansion to preserve ! in %%A value
   set var=%%A
   REM the remainder of the loop needs delayed expansion
   setlocal enableDelayedExpansion
   REM do something with !var!
   call batch2 !var!
   REM maybe the batch2 modifies var some more
   REM the code below successfully transports the value across the endlocal barrier
   for /f "delims=" %%B in ("!var!") do endlocal & set "var=%%B"
)

You say your batch might set many values you need to preserve. Normally you pass the names of the return variables into your CALLed routine, and then your routine could do something like:

set "%~2=return value1"
set "%~3=return value2"
... etc.

You would know the names of all the variables, but that might require a lot of code to transport all values accross the endlocal barrier.

You could prefix all the variables with a common prefix, and then use a FOR /F loop to preserve the values. The entire result set of a FOR /F ('command') is cached before any of the iterations begin.

Your CALLed batch could set variables prefix.var1, prefix.var2, ... prefix.varN, and the following code would work

REM delayed expansion is disabled to start
for ... %%A in (....) do (
   REM must SET before enabling delayed expansion to preserve ! in %%A value
   set var=%%A
   REM the remainder of the loop needs delayed expansion
   setlocal enableDelayedExpansion
   REM do something with !var!
   call batch2 !var! prefix.
   REM the batch2 set variables prefixed by prefix.
   REM Must endlocal and preserve all values.
   for /f "delims=" %%B in ('set prefix.') do (
     if "!"=="" endlocal
     set "%%B"
   )
)

The if "!"=="" endlocal line is a nifty trick to only endlocal on the 1st inner loop iteration. It relies on the fact that delayed expansion was disabled before the outer loop, and enabled near the top of each outer loop iteration.

dbenham
  • 127,446
  • 28
  • 251
  • 390
  • @dbenham - The last code example is similar to my situation. I need expansion on at the beginning in order to realize the name of the "batch2" file to call. – lit Dec 07 '12 at 23:18
  • @dbenham - It still seems like the calling batch1 script needs to know the names of the variables that batch2 will create. Now I am trying another tact. If the list of new variables is provided, then could the (endlocal... approach be used? set VAR_LIST=VAR1 VAR2 VAR3 FOR %%v IN (%VAR_LIST%) DO ( SET %%v=%v% # <<< This is the problem ) It seems like it is close to working, but I cannot get the right-hand side of the SET line specified correctly. How can I reference the value of a variable name that is in a variable? – lit Dec 07 '12 at 23:25
  • @dbenham - It still seems like the calling batch1 script needs to know the names of the variables that batch2 will create. Now I am trying another tact. If the list of new variables is provided, then could the (endlocal... approach be used? (endlocal FOR %%v IN (%VAR_LIST%) DO ( SET %%v=%v% # <<< This is the problem ) ) It seems like it is close to working, but I cannot get the right-hand side of the SET line specified correctly. How can I reference the value of a variable name that is in a variable? – lit Dec 07 '12 at 23:37
  • @dbenham - Sorry, I cannot get the formatting correct. I want to set a variable whose name is in a variable. – lit Dec 07 '12 at 23:38
  • [pre][code] FOR %v IN (VAR1 VAR2 VAR3) DO SET %v=%%v% SET VAR1=%VAR1% SET VAR2=%VAR2% SET VAR3=%VAR3% – lit Dec 07 '12 at 23:44
  • @Paul - With my last code example, your batch2 can create as many variables as it needs as long as the name of each variable is something like `prefix.name`, where the prefix is constant and the name changes. Then within batch1 the `set prefix.` command will list all of the variables that batch2 created and the FOR loop will process and save them after the ENDLOCAL has been issued. I am confident this can work well for you. – dbenham Dec 07 '12 at 23:58
  • @dbenham I agree that the prefix method might work well. Unfortunately, it would require renaming some variables after the second script returns. I am thinking to have script 2 write a .bat file into a temp file, then have script 1 do a simple call of that with no SETLOCAL/ENDLOCAL statements. I very much appreciate your skilled help and suggestions. Thanks. – lit Jun 04 '14 at 18:08