5

Suppose i have variable a with contents "123" and variable b123 with some text in it. For some reason i want to use variable a as part of second var name. Something like this:

SET a=123
SET b123=some_text_in_it
rem i want next line to output  "some_text_in_it"
echo %b%a%%

so i want to concatenate text with var contents and use resulting string as variable name to echo that contents. Sample above doesn't work and i see why, probably i need to add some kind of groupping. How to do it, prefferably in one line?

Aleksandr Kravets
  • 5,750
  • 7
  • 53
  • 72

3 Answers3

8

There are two common ways for this
CALL or DelayedExpansion

setlocal EnableDelayedExpansion
SET a=123
SET b123=some_text_in_it
rem i want next line to output  "some_text_in_it"
call echo %%b%a%%%
echo !b%a%!

The CALL variant uses the fact, that a call will reparse the line a second time, first time only the %a% will be expanded and the double %% will be reduced to a single %
call echo %b123%
And in the second step the %b123% will be expanded.
But the CALL technic is slow and not very safe, so the DelayedExpansion should be prefered.

The DelayedExpansion works as the exclamation marks are expanded in a later parser phase than the expansion of the percents.
And that's also the cause, why delayed expansion is much safer.

Edit: Method for arrays containing numbers
If you are operating with arrays which contains only numbers you could also use set /a to access them.
That's much easier than the FOR or CALL technic and it works also in blocks.

setlocal EnableDelayedExpansion
set arr[1]=17
set arr[2]=35
set arr[3]=77
(
  set idx=2
  set /a val=arr[!idx!]
  echo !var!
)
jeb
  • 78,592
  • 17
  • 171
  • 225
  • @jeb: Although `set /a` is useful to access numeric array elements, it can not use an index modified in the same `set /a` command for the same reasons you explained me below. For example: `set /a idx+=1, var=arr[!idx!]` does not get the right element, so this management must be achieved in two separated `set /a` commands. – Aacini Mar 18 '12 at 14:07
2

The best reason to "use a variable as part of second var name" is to access array (vector and matrix) elements via a subscript variable. Although this is exactly the same than the example above, it looks much clearer:

setlocal EnableDelayedExpansion
set i=123
set v[123]=Value of element 123
call echo %%v[%i%]%%
echo !v[%i%]!

Although CALL is slower than Delayed Expansion, as jeb said above, it may be nested several times allowing to use a second (or deeper) indirection to access array elements. This feature may be used in complex data structures, like linked lists. For example:

set i=123
set v[123]=456
set w[456]=Value of element 456
call call echo %%%%w[%%v[%i%]%%]%%%%

EDIT: For some reason I don't understand, the Delayed Expansion version of the last command doesn't work properly:

call echo !w[%%v[%i%]%%]!
Aacini
  • 65,180
  • 12
  • 72
  • 108
  • 1
    The `call echo !w[%%v[%i%]%%]!` will expand in the _standard_ order. 1.Percent expansion to `call echo !w[%v[123]%]!`, 2. delayed expansion to `call echo ` as the `w[%v[123]%]` isn't a defined variable, 3. the call will be execute and start a new percent expansion. Your sample should look like `call echo %%w[!v[%i%]!]%%` Btw. After a _CALL_ delayed expansion will never be executed, explained at [SO:How does CMD.EXE parse scripts](http://stackoverflow.com/a/4095133/463115) – jeb Mar 16 '12 at 07:25
  • @Aacini: well, i had simillar reason to use this feature. Had a number of strings localized in several languages in constants with names identical in all except postfix (e.g. 'my_text_1_de','my_text_1_it'...). So my goal was to assign proper i18n strings to variables after language was selected by user. – Aleksandr Kravets Mar 16 '12 at 07:37
  • @jeb: Of course!: the delayed expansion is done _before_ executing the command, that, in this case (`call`), is responsible for the second expansion of percent signs. Thanks a lot :) – Aacini Mar 16 '12 at 14:40
  • @AleksandrKravets: May I suggest you to enclose the changing part of the variable name in brackets like standard arrays in other languages? 'my_text_1[de]','my_text_1[it]' Although this is the same for Batch processing, it is clearer for programmers! – Aacini Mar 16 '12 at 14:48
  • @Aacini: I don't see any reason to do that in my case. Of cource it is good trick if you are skilled Batch-scripter, but anyone not too skilled, like me, reading somebody's script could make wrong assumption that bat-files support arrays. And he will be very dissapointed later =) – Aleksandr Kravets Mar 16 '12 at 15:25
  • @AleksandrKravets: Excuse me? Of course that .bat files DO support arrays! The objective of my answer was that you realize that in first place. Precisely my point is this: if you use arrays in Batch it is clearer to write them in the standard way instead of disguise them of something different. Your 'my_text_1...' variables are just an array that have the subscript separated from the array name by an underscore, instead of be enclosed in braquets like in standard languages. – Aacini Mar 17 '12 at 05:10
  • 1
    @Aacini: Excuse me too, but solution you proposed is a workaround, not an array support which you can find in Python or Perl or any normal programming language. You can even think of it as a Hash-table( indexes are strings, remember). But It's not a REAL hash-table, it's Q&D solution, useful, intuitive, but still a workaround. Maybe i'm carping, but it's because i'm not a real BAT-lover, i just have to work with Bat-file. – Aleksandr Kravets Mar 17 '12 at 12:32
  • @AleksandrKravets: Not a problem here! I just tried to give you an advice to write clearer Batch files. About comparisons, I hope you think Pascal, C, Ada, C# and others are "normal programming languages" although they support arrays with `de it ...` indexes (enumerated type). In Batch you may store values in several same-named variables and select one of them via an index, but you may call this a workaround if you wish. Many Batch files are unnecessarily complex because don't use such workaround, so it is a good thing that you can solve your problem via the "Q&D solution" Batch feature! ;) – Aacini Mar 18 '12 at 13:39
  • @Aacini: Well, as i said above, it is useful =) – Aleksandr Kravets Mar 19 '12 at 08:00
2

There is yet one more way to accomplish the task that is very useful if both variables have been assigned within the same parenthesized block that is expanding them. The solution is similar to jeb's delayed expansion answer, except it uses a FOR variable instead of normal expansion.

@echo off
setlocal enableDelayedExpansion

:: Define a linefeed value - only needed for last variant
set LF=^


:: The two blank lines above are critical to the definition of LF

(
  set A=123
  set B123=some_text_in_it

  rem ECHO !B%A%! will fail because A was assigned within the same block

  rem This works as long as A does not contain * or ?
  for %%A in ("!A!") do echo !B%%~A!

  rem This works as long as A does not start with EOL character (; by default)
  for /f "delims=" %%A in ("!A!") do echo !B%%A!

  rem This will never fail because it disables EOL by setting it to a linefeed
  for /f eol^=^%LF%%LF%^ delims^= %%A in ("!A!") do echo !B%%A!
)

The CALL trick could also be used, but comparatively it is very slow, and it is unreliable - see CALL me, or better avoid call.

The definition and use of a linefeed variable is discussed at Explain how dos-batch newline variable hack works.

Setting EOL to a linefeed is described in detail at HOW TO: FOR /F Disabling EOL or using a quote as delim. Using an LF variable instead of embedding linefeeds within the FOR statement is just an extension of the technique. It makes the code more readable.

EDIT
A simpler way to safely use the FOR technique for any valid value is to set EOL to an equal sign, since no user defined variable can contain an = in the name.

for /f "eol== delims=" %%A in ("!A!") do echo !B%%A!
Community
  • 1
  • 1
dbenham
  • 127,446
  • 28
  • 251
  • 390