Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Double delayed expansion in batch?

Tags:

batch-file

I am trying to double my delayed expansion if that makes any sense. Here is what I want.

set var1=hello
set var2=var1
set var3=var2
echo %!%var3%!%

and then have hello be displayed. This is not my actual code but an example of how I need it to work.

like image 852
C Tommy Avatar asked Nov 24 '15 16:11

C Tommy


3 Answers

even possible without delayed expansion (although even uglier than rojo's answer) Just a matter of the number of "layers" of parsing and correct escaping the %:

@echo off
set var1=hello
set var2=var1
set var3=var2
call call echo %%%%%%%var3%%%%%%%
like image 142
Stephan Avatar answered Oct 05 '22 23:10

Stephan


Another one without call (faster):

@echo off
setlocal enabledelayedexpansion

set "var1=hello"
set "var2=var1"
set "var3=var2"

for %%v in (!%var3%!) do echo !%%v!

EDIT: Reply to rojo's challenge

Your code have an error in the creation of the variables; all variables contain the string: "var!X!" and the final result is "var!X!X". Below is your code with the variables creation part fixed:

@echo off
setlocal EnableDelayedExpansion

set "var1=hello"
for /L %%I in (2,1,1000) do (
    set /a X = %%I - 1
    set "var%%I=var!x!"
)

call :follow var1000
goto :EOF

:follow <varname>
setlocal enabledelayedexpansion
set "var=!%~1!"
:follow_loop
if defined !%var%! (
    set "var=!%var%!" & goto follow_loop
) else (
    echo !%var%!
)

This program correctly show "hello" at end; it takes about 2.51 seconds when run in my computer.

The method is pretty short, so there is not too much chance to improve it; the obvious modification is to change the goto loop by a for (while) one. Here it is:

@echo off
setlocal EnableDelayedExpansion

set "var1=hello"
for /L %%I in (2,1,1000) do (
    set /a X = %%I - 1
    set "var%%I=var!x!"
)

call :follow var1000
goto :EOF


:follow <varname>
set "var=%~1"
cmd /V:ON /C for /L %%? in () do @for %%v in (^^!var^^!) do @if defined %%v (set "var=^!%%v^!") else echo %%v ^& exit

This code takes about 1.22 seconds to run, that is, just the 48.6% of your method (2 times faster) ;)

like image 43
Aacini Avatar answered Oct 06 '22 00:10

Aacini


You could add a call and surround your first delayed expansion with double percents like this.

@echo off
setlocal enabledelayedexpansion

set "var1=hello"
set "var2=var1"
set "var3=var2"

call echo %%!%var3%!%%

Seems horribly convoluted to me, though. I'd probably rewrite the script to make such trickery not needed if I were me.


Edit: Since there are so many solutions being added here, I'll propose another one. Here's a subroutine that will follow the line of variables from beginning to end, even if it's 1000 levels deep. Just as an academic exercise.

@echo off
setlocal

set "var1=hello"
for /L %%I in (2,1,1000) do (
    set /a X = %%I - 1
    setlocal enabledelayedexpansion
    for %%x in (!X!) do endlocal & set "var%%I=var%%x"
)

call :follow var1000
goto :EOF

:follow <varname>
setlocal enabledelayedexpansion
set "var=!%~1!"
:follow_loop
if defined !%var%! (
    set "var=!%var%!" & goto follow_loop
) else (
    echo !%var%!
)

And here's another using a batch + JScript hybrid (because the JScript while loop is faster than a batch goto loop).

@if (@CodeSection == @Batch) @then

@echo off
setlocal

set "var1=hello"
for /L %%I in (2,1,1000) do (
    set /a X = %%I - 1
    setlocal enabledelayedexpansion
    for %%x in (!X!) do endlocal & set "var%%I=var%%x"
)

cscript /nologo /e:JScript "%~f0" "var1000"
goto :EOF

@end // end batch / begin JScript hybrid chimera
var env = WSH.CreateObject('Wscript.Shell').Environment('Process'),
    itm = WSH.Arguments(0);

while (env(itm)) itm = env(itm);

WSH.Echo(itm);

Your move, Aacini.

like image 44
rojo Avatar answered Oct 06 '22 01:10

rojo