System.Cats.Lol
System.Cats.Lol

Reputation: 1770

Windows batch programming: Indirect/nested variable evaluation

We have a text file that lists a bunch of paths, and a batch file that reads the lines from this file.

For instance, TargetFolders.txt might contain the line:

%ProgramFiles%\Acme\FooBar %VersionNumber%

Naturally, when we read this line from the text file (using a FOR command), the variable %%I receives the actual line text, with the % signs rather than replacing the variable values. So,

SET VersionNumber=7.0
FOR /F "eol=; delims=" %%I IN (TargetFolders.txt) DO (
    echo Folder: %%I
)

Prints

Folder: %ProgramFiles%\Acme\FooBar %VersionNumber%

How does one make it replace the actual variable values, so that it prints

Folder: C:\Program Files\Acme\FooBar 7.0

?

Upvotes: 5

Views: 6280

Answers (3)

Irfy
Irfy

Reputation: 9587

SET VersionNumber=7.0
FOR /F "eol=; delims=" %%I IN (TargetFolders.txt) DO (
    for /F "usebackq delims=" %%J in (`echo %%I`) do echo Folder: %%J
)

There you go. (This is what you wanted, right?)

Upvotes: 8

mklement0
mklement0

Reputation: 439487

To complement the existing helpful answers with a more robust variant that also handles the following embedded characters correctly: & | < > ^[1] :

Note that for simplicity I'm using a string to drive the outer loop, whose literal value - to be expanded later by the inner loop - is %ProgramFiles%\Acme\FooBar %VersionNumber%, due to the doubled % instances.

set "VersionNumber=0.7"
for /f "delims=" %%f in ("%%ProgramFiles%%\Acme\FooBar %%VersionNumber%%") do (
  for /f "delims=" %%g in ('echo "%%f"') do echo Folder: [%%~g]
)

This yields something like Folder: [C:\Program Files\Acme\FooBar 0.7]

Note:

  • By default, for /f interprets a single-quoted string ('...') as a command to execute and whose output to capture; delims= tells for to capture the output as a whole in a single variable.
    (While you can use usebackq with a `...`-enclosed command instead, there's no advantage to doing so in this case.)
  • Note how the reference to the outer loop's variable is double-quoted ("%%f"), which is what allows the value to contain said special characters without breaking the echo command.

  • Because the output will then also be double-quoted, the ~ operator is used to strip the enclosing double quotes from the captured value on echoing it (%%~g).


[1] A solution that additionally handles embedded " instances is trickier:

  rem Construct a variable whose value contains all shell metacharacters.
  rem % chars. meant to be treated as literals must be doubled (%%)
  rem Note the extra " at the end, which appends an unbalanced double quote. 
set "var=%%OS%% & %%ba^r | (baz) <mo'>""

  rem Embedded " chars. must be escaped as "" (doubling them).  
for /f "delims=" %%v in ('echo "%var:"=""%"') do set "expandedVar=%%~v"

  rem Note: The resulting variable's value: 
  rem  - can only be echoed *double-quoted*, as the command will otherwise break.
  rem  - still contains the *doubled* embedded double quotes, which, however,
  rem    is the escaping that other Microsoft programs expect.
echo "[%expandedVar%]"

This yields: "[Windows_NT & %ba^r | (baz) <mo'>""]"

Upvotes: 0

dbenham
dbenham

Reputation: 130899

Simply adding a CALL may solve your problem. (also your definition of VersionNumber was wrong)

SET VersionNumber=7.0
FOR /F "eol=; delims=" %%I IN (TargetFolders.txt) DO (
  call echo Folder: %%I
)

But this will fail if your file contains unquoted special characters like &, >, <, |.

For example, the following line would fail:

%ProgramFiles%\This&That\ %VersionNumber%

It will work if it is quoted

"%ProgramFiles%\This&That\" %VersionNumber%

The CALL will also mangle any quoted carets: "^" will become "^^"

The best solution would be to modify your text file and replace each % with !.

!ProgramFiles!\Acme\FooBar !VersionNumber!
!ProgramFiles!\This&That !VersionNumber!

Now you can safely use delayed expansion to expand the variables within the loop.

setlocal enableDelayedExpansion
SET VersionNumber=7.0
FOR /F "eol=; delims=" %%I IN (TargetFolders.txt) DO (
  echo Folder: %%I
)

If your text file already has ! that you want to preserve, then it must be escaped. Also ^ will have to be escaped if it appears on a line with a !.

preserve caret ^^ and exclamation ^! by escaping
caret ^ without exclamation is no problem

Alternatively you can substitute variables for caret and exclamation literals

alternate method to preserve caret !c! and exclamation !x!
caret ^ without exclamation still no problem

And then define the variables in your batch

setlocal enableDelayedExpansion
set "x=^!"
set "c=^"
SET VersionNumber=7.0
FOR /F "eol=; delims=" %%I IN (TargetFolders.txt) DO (
  echo Folder: %%I
)

Upvotes: 6

Related Questions