Rado
Rado

Reputation: 8963

Assign variables past endlocal in a loop

How do I preserve a number of variables (with a certain prefix) past endlocal in a loop?

Variables are visible in the loop declaration, but not body.

Here is an example with echo instead of variable assignment to illustrate that variables are not visible in the for body. Normally a variable assignment would go in place of the echo:

@echo off

setlocal ENABLEDELAYEDEXPANSION
set TB_1=test1
set TB_2=test2

set TB_ALL_VARS=
for /F "tokens=1 delims==" %%x in ('set TB_') do (
  set TB_ALL_VARS=%%x !TB_ALL_VARS!
)

for %%x in (%TB_ALL_VARS%) do ( echo %%x = !%%x! ) 
echo END LOCAL
endlocal & ( for %%x in (%TB_ALL_VARS%) do ( echo %%x = !%%x! ) )

Output:

TB_2 = test2
TB_1 = test1
END LOCAL
TB_2 = !TB_2!
TB_1 = !TB_1!

As you can see, the variables are printed ok before endlocal, but not printed after endlocal.

Is there a way to save variables like this past endlocal?

Upvotes: 2

Views: 328

Answers (2)

rojo
rojo

Reputation: 24466

Given your example code, I think what you are asking is, "How do I set a dynamic number of variables after endlocal?" What you ask is not terribly intuitive, but it is possible. You can't use delayed expansion when compounding set with endlocal. A workaround can often be employed to use a for loop to endlocal & set "var=%%A", which only works if the number of variables and values are static. Unfortunately, endlocal & for doesn't work the same as for... in... do ( endlocal & set ), as you've no doubt discovered in your own testing.

My solution is to use a macro to do the setting after endlocal -- basically putting commands rather than simple string values into a variable, then evaluating that variable as a set of set commands.

@echo off
setlocal

:: // call ":set" subroutine to do the setting
call :set

:: // display results
set subv

:: // end main runtime
goto :EOF


:: // :set subroutine
:set
setlocal enabledelayedexpansion

:: // any number of variables prefixed by "subv"
set "subv1=1"
set "subv2=2"
set "subv3=3"

:: // init %compound%
set compound=

:: // combine all %subvX% variables into a macro of set var1=val1 & set var2=val2, etc
for /f "delims=" %%I in ('set subv') do set "compound=!compound! & set "%%~I""

:: // evaluate set commands as a macro
endlocal & %compound:~3%
goto :EOF

Another solutions is to go back to the first workaround I mentioned, in the format of endlocal & set "var=%%A". Typically this is only used when you know you'll only loop once, something like

for %%I in ("!var!") do endlocal & set "return=%%~I"

... because you don't want to endlocal too many times. But you can do a multiple value loop with a single endlocal by employing if not defined like this:

@echo off
setlocal

call :set
set subv

goto :EOF

:set
setlocal enabledelayedexpansion
set "subv1=1"
set "subv2=2"
set "subv3=3"
set end=
for /f "delims=" %%I in ('set subv') do (
    if not defined end endlocal & set end=1
    set "%%~I"
)
goto :EOF

... and because endlocal and set "%%~I" are contained within a parenthetical code block, the variables are re-set and your goal is achieved.

Upvotes: 4

npocmaka
npocmaka

Reputation: 57252

You're attempting to use tunneling , but after endlocal you cannot use ! to expand variables.Try this:

@echo off

setlocal ENABLEDELAYEDEXPANSION
set TB_1=test1
set TB_2=test2

set TB_ALL_VARS=
for /F "tokens=1 delims==" %%x in ('set TB_') do (
  set TB_ALL_VARS=%%x !TB_ALL_VARS!
)

for %%x in (%TB_ALL_VARS%) do ( echo %%x = !%%x! ) 
echo END LOCAL
endlocal & ( for %%x in (%TB_ALL_VARS%) do ( call echo %%x = %%%%x%% ) )

OR

@echo off

setlocal ENABLEDELAYEDEXPANSION
set TB_1=test1
set TB_2=test2

set TB_ALL_VARS=
for /F "tokens=1 delims==" %%x in ('set TB_') do (
  set TB_ALL_VARS=%%x !TB_ALL_VARS!
)

for %%x in (%TB_ALL_VARS%) do ( echo %%x = !%%x! ) 
echo END LOCAL
endlocal & ( for %%x in (%TB_ALL_VARS%) do (
 setlocal enableDelayedExpansion
 call echo %%x = !%%x! )
 endlocal 
 )

Upvotes: 3

Related Questions