12

Using the Windows XP CMD command-line I can expand a variable twice as follows:

set AAA=BBB
set BBB=CCC
for /F "usebackq tokens=*" %i in (`echo %%AAA%%`) do echo %i

will echo CCC. I.e. AAA has been expanded to the string BBB, and then the variable BBB has been expanded to CCC.

This doesn't work from inside a batch script (i.e. a .cmd file). Changing the %%AAA%% to either %%%AAA%%% or %%%%AAA%%%% doesn't work either.

Any idea how i can achieve this from within a script, namely to take expand the variable AAA to the string CCC?

Late Edit

The answers posted work for my reduced example however the non-tortuous answer doesn't work for the real case. Here's an extended example (which doesn't work), which illustrates what I was actually trying to do:

setlocal enabledelayedexpansion

set LIST=BBB CCC DDD
set BBB=111
set CCC=222
set DDD=333

for %%i in (%LIST%) do (

    for /F  %%a in ('echo %%%i%') do  echo !%%a!

)

I would like to see

111
222
333

output.

Stephen Quan
  • 15,118
  • 3
  • 69
  • 63
jon-hanson
  • 7,193
  • 2
  • 32
  • 57
  • dinged because you are still in a batch file and not using powershell. ;) – Cheeso Jul 30 '09 at 05:02
  • 1
    Can't use PowerShell, not yet at least. – jon-hanson Jul 30 '09 at 08:54
  • Isn't Your statement *The answers posted work for my reduced example however the non-tortuous answer doesn't work for the real case* in contradiction with Your comment to *akf*'s post *Thanks, both of those do indeed work.*? – Piotr Dobrogost Oct 23 '11 at 17:23
  • @Piotr. Only if you don't take into account the time at which the comments and statements were made. – jon-hanson Oct 24 '11 at 13:13
  • Ok. If **current** answers do answer Your question it would be nice to reflect this fact by updating Your remark in the question. Current state is confusing. – Piotr Dobrogost Oct 24 '11 at 17:02

7 Answers7

13

Thinking in terms of a less tortuous solution, this, too, produces the CCC you desire.

setlocal enabledelayedexpansion
set AAA=BBB
set BBB=CCC
for /F  %%a in ('echo %AAA%') do  echo !%%a!

edit:

to dissect this answer:

setlocal enabledelayedexpansion - this will allow for any environment variable setting during your bat to be used as modified during the process of your for loop.

set AAA=BBB, set BBB=CCC - your data population set statements

for /F %%a in ('echo %AAA%') do echo !%%a! - This tells the processor to loop, albeit only once, and take out the first token that is returned (default delimiter of space and tab apply) from the running of the command in the parens and put it in the var %%a (outside of a batch, a single % will do). If you specify that var as %%a, you need to use %%a in your do block. Likewise, if you specify %%i, use %%i in your do block. Note that to get your environment variable to be resolved within the do block of the for loop, you need surround it in !'s. (you don't need to in the in block, as I originally posted - I have made that change in my edit).

edit:

You were very close with your updated example. Try it like this:

@echo off
setlocal enabledelayedexpansion
set LIST=BBB CCC DDD
set BBB=111
set CCC=222
set DDD=333

for %%i in (%LIST%) do (
    for /F %%a in ('echo %%i') do echo !%%a!
)

The difference between your update and this is that you were trying to echo the environment variable in the in set with in ('echo %%%i%'), but without the !'s for the delayed expansion of set variables. Were you to use in ('echo !%%i!'), you would see your BBB, CCC, and DDD variables resolved, but then the do block of your inner loop wouldnt have anything to resolve - you dont have any 111 environment variables. With that in mind, you could simplify your loop with the following:

@echo off
setlocal enabledelayedexpansion
set LIST=BBB CCC DDD
set BBB=111
set CCC=222
set DDD=333

for %%i in (%LIST%) do (echo !%%i!)
akf
  • 36,245
  • 8
  • 81
  • 94
  • Although this is indeed less tortuous, the var i'm actually using is part of a for loop, e.g. instead of AAA i have i : for %%i in (%LIST%) do ( :: use i For this case substituting i for AAA in your solution doesn't seem to work. Any ideas? – jon-hanson Jul 29 '09 at 15:12
  • I'm not sure that I follow what you are saying. When using the var of a `for` loop in a bat, the double % should do the trick. I used %%a, which would be the same as %%i in your example. Perhaps you could include your more specific example in the question. – akf Jul 29 '09 at 15:48
  • I've added another example to the question. – jon-hanson Jul 30 '09 at 08:22
  • Thanks, both of those do indeed work. I have to admit, after years of writing fairly advanced UNIX bash scripts, writing Windows cmd scripts has been a painful exercise. The myriad ways of expanding variables (%A, %%A, %A%, !%A%!, etc) is baffling. The enabledelayedexpansion flag appears to improve the situation but it inexplicably renders certain parts of my scripts inoperable. I guess PowerShell is the way forward... – jon-hanson Jul 31 '09 at 10:24
  • Yeah, it certainly is not intuitive. The downvote for not using PowerShell was harsh, though. – akf Jul 31 '09 at 10:55
8

How to expand a shell variable multiple times:

@echo off
setlocal enabledelayedexpansion

set myvar=second
set second=third
set third=fourth
set fourth=fifth

echo Variable value before expansion: !myvar!
call :expand myvar
echo Variable value after expansion: !myvar!
goto :eof


:expand
    set var=%1
    :expand_loop
        if not "!%var%!" == "" (
            set var=!%var%!
            goto :expand_loop
        )
        set %1=!var!
    goto :eof

output:

Variable value before expansion: second
Variable value after expansion: fifth
Piotr Dobrogost
  • 38,049
  • 34
  • 218
  • 341
  • 1
    Follow up: [Why is delayed expansion in a batch file not working in this case?](http://stackoverflow.com/questions/7882395/) – Piotr Dobrogost Oct 24 '11 at 22:45
  • I like this better. I'm also kicking myself because I use a similar technique tor returning strings from `.cmd` and `.bat` files. However I found a wrinkle. The expansion of: "`b:\lang\java\jre\v10.00`" ==> "`\lang\java\jre\v10.00`". I loose the "`b:`" drive designator. Does anyone have an explaination? I have not been able to get a around, It is the colon(`:`). e.g. "`abc:xyz`" ==> "`xyz`" and . "`abc:xyz:1234`" ==> "`1234`". colon is uesd for string substitution. Otherwise works nicely. – will Jul 06 '18 at 01:09
4

This aaa.bat

@echo off
set aaa=bbb
set bbb=ccc
for /F %%i in ('echo %%%aaa%%%') do echo %%i

outputs

c:>ccc

What exactly is the trouble?

wqw
  • 10,921
  • 1
  • 30
  • 39
3

The following (torturous) approach seems to work okay:

    @echo off
:main
    setlocal enableextensions enabledelayedexpansion
    set aaa=bbb
    set bbb=ccc
    call :myset x %%aaa%%
    echo %x%
    endlocal
    goto :eof
:myset
    for /F "usebackq tokens=*" %%i in (`echo %%%2%%`) do set %1=%%i
    goto :eof

It outputs:

ccc

as desired.

I've often used that trick to (for example) format %aaa% into %x% to a certain size (a la sprintf) but this is the first time I've had to do double indirection. It works because you don't find the extra "%%" being sucked up by the current shell level.

paxdiablo
  • 772,407
  • 210
  • 1,477
  • 1,841
1

The OP question almost works on Windows 10. A small correction is required to properly form the ECHO command. Here is the intermediate result:

set LIST=BBB CCC DDD
set BBB=111
set CCC=222
set DDD=333
for %%i in (%LIST%) do echo %%%%i%%

rem // Outputs:
rem // %AAA%
rem // %BBB%
rem // %CCC%

Then we nest the output inside another FOR statement and get the completed result:

set LIST=BBB CCC DDD
set BBB=111
set CCC=222
set DDD=333
for %%i in (%LIST%) do for /f %%a in ('echo %%%%i%%') do echo %%a

rem // Outputs:
rem // 111
rem // 222
rem // 333
Stephen Quan
  • 15,118
  • 3
  • 69
  • 63
0

Double indirection (which is a very apt way pax has put it) reminds of C pointer de-referencing.
I doubt that is supported in the Windows command shell.

Do you think the larger purpose you are targeting can be achieved with something easier;
maybe lesser of a code-golf approach then the one contrived nicely by pax?

nik
  • 12,528
  • 3
  • 36
  • 53
  • Probably, i think that would require a separate question though otherwise the current answers won't make sense. – jon-hanson Jul 29 '09 at 15:24
0

it seems like the easiest thing to do is to create a tiny temp file to set the variable as you wish. something like this should work:

setlocal enabledelayedexpansion

set LIST=BBB CCC DDD
set BBB=111
set CCC=222
set DDD=333

for %%i in (%LIST%) do (
    echo set a=\%%%i\%>tmp.bat
    call tmp.bat
    echo %a
    rm tmp.bat
)
james turner
  • 2,414
  • 1
  • 18
  • 22
  • probably NOT a good idea to create temp files. your never know where the script is running. aside from permission issues, there is always the possibility of unintended consequences. – ShpielMeister Aug 21 '18 at 02:48