How CALL SET Expands Variables in Batch Script
In advanced batch scripting, you may encounter a situation where a variable doesn't hold a direct value, but instead holds the name of another variable. To get the final value, you need to perform a "double expansion" or "indirect expansion." This is a tricky problem because the standard %VAR% syntax is evaluated only once.
The classic, powerful technique to solve this is the CALL SET command. It leverages a special behavior of the CALL command to force a second round of parsing on a line, allowing nested variables to be resolved. This guide will break down how this works, why it's necessary, and compare it to the modern alternative, delayed expansion.
The Core Problem: Indirect or Nested Variable Expansion
Let's say you have two variables. One is a "pointer" to the other.
@ECHO OFF
SET "MyValue=Hello World"
SET "Pointer=MyValue"
Your goal is to get "Hello World" by starting with the Pointer variable. A standard ECHO fails:
ECHO %Pointer%
Output:
MyValue
You need the script to first evaluate %Pointer% to get MyValue, and then evaluate %MyValue% to get Hello World. This is a two-step expansion.
The Magic of CALL: Forcing a Second Parsing Pass
The CALL command is typically used to run another batch script or a subroutine. However, it has a special property: it forces the command processor to evaluate the command line in two passes.
- Parsing Pass: Before
CALLexecutes, the command processor parses the line and expands all%VAR%variables. - Execution Pass:
CALLthen takes the result of the first pass and executes it as a new command, which triggers a second round of parsing and expansion.
Let's see this with ECHO:
@ECHO OFF
SET "MyValue=Hello World"
SET "Pointer=MyValue"
CALL ECHO %%%Pointer%%%
Output:
Hello World
How it works:
- Parsing Pass:
cmd.exeseesCALL ECHO %%%Pointer%%%. It expands%Pointer%toMyValueand resolves the paired percent signs%%into a single%. The command becomes:CALL ECHO %MyValue%. - Execution Pass:
CALLnow executes the commandECHO %MyValue%. This new command is parsed,%MyValue%is expanded toHello World, and theECHOcommand prints the final result.
The CALL SET Trick Explained
While CALL ECHO works for displaying, you often need to store the result in a variable. This is where CALL SET comes in. The logic is exactly the same.
Syntax: CALL SET "ResultVar=%%%PointerVar%%%"
@ECHO OFF
SET "MyValue=Hello World"
SET "Pointer=MyValue"
SET "Result="
ECHO Before: Result is "%Result%"
CALL SET "Result=%%%Pointer%%%"
ECHO After: Result is "%Result%"
How it works:
- Parsing Pass: The line
CALL SET "Result=%%%Pointer%%%"becomesCALL SET "Result=%MyValue%". - Execution Pass:
CALLnow executes the commandSET "Result=%MyValue%". This is parsed,%MyValue%becomesHello World, and theSETcommand assigns this value to theResultvariable.
Basic Example: Resolving a Pointer
This script puts the full CALL SET logic into a clear demonstration.
@ECHO OFF
SETLOCAL
SET "ConfigValue_A=Production"
SET "ActiveConfig=ConfigValue_A"
SET "FinalValue="
ECHO The pointer 'ActiveConfig' points to the variable named: %ActiveConfig%
ECHO.
ECHO --- Using CALL SET to resolve the pointer ---
CALL SET "FinalValue=%%%ActiveConfig%%%"
ECHO The final resolved value is: %FinalValue%
ENDLOCAL
Output:
The pointer 'ActiveConfig' points to the variable named: ConfigValue_A
--- Using CALL SET to resolve the pointer ---
The final resolved value is: Production
CALL SET vs. Delayed Expansion (The Modern Way)
For this same problem, the modern solution is Delayed Expansion. It's often easier to read and understand.
The Logic: SETLOCAL ENABLEDELAYEDEXPANSION allows you to use !VAR! for dynamic expansion. When the parser sees !%Pointer%!, it first expands the %PERCENT% variable (static pass) and then the !EXCLAMATION! variable (dynamic pass).
An example of script using delayed expansion:
@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "MyValue=Hello World"
SET "Pointer=MyValue"
SET "Result=!%Pointer%!"
ECHO The result with Delayed Expansion is: !Result!
How it works:
- The parser sees
!%Pointer%!. - It expands the "inner" percent variable first:
%Pointer%becomesMyValue. - The string is now
!MyValue!. - Delayed expansion then expands
!MyValue!toHello World.
For most new scripts, delayed expansion is the preferred method as its syntax is cleaner. However, CALL SET is a powerful technique that works without needing to enable delayed expansion.
Common Pitfalls and How to Solve Them
- Confusing Percent Signs: The syntax
%%%Pointer%%%is cryptic. It's easy to get the number of percent signs wrong. Just remember that it's%%(to escape a%) followed by%Pointer%followed by another%%. - Special Characters: If your final value contains special characters like
&,|, or>, usingCALL ECHOwill fail.CALL SET "Result=..."is safer because theSET "Var=Value"syntax is robust and treats the value as a literal string.
Practical Example: Accessing Dynamic Variables in a Loop
This is where CALL SET shines. The script creates several variables (VAR1, VAR2, VAR3) and then uses a FOR loop to access them dynamically by number.
@ECHO OFF
SETLOCAL
SET "VAR1=Apple"
SET "VAR2=Banana"
SET "VAR3=Cherry"
ECHO --- Looping through dynamic variables ---
FOR /L %%i IN (1,1,3) DO (
CALL SET "CurrentValue=%%VAR%%i%%"
REM We need delayed expansion here to ECHO the changing 'CurrentValue'
SETLOCAL ENABLEDELAYEDEXPANSION
ECHO The value of VAR%%i is: !CurrentValue!
ENDLOCAL
)
Output:
--- Looping through dynamic variables ---
The value of VAR1 is: Apple
The value of VAR2 is: Banana
The value of VAR3 is: Cherry
Conclusion
CALL SET is an advanced batch scripting technique that solves the problem of nested or indirect variable expansion by forcing the command processor to parse a line twice.
- It is the classic method for resolving a variable whose name is stored in another variable.
- The key syntax is
CALL SET "Result=%%%Pointer%%%". - While powerful, it is often considered less readable than its modern alternative, Delayed Expansion (
!%Pointer%!).
Understanding CALL SET is a sign of mastering the intricacies of the cmd.exe parser and is an invaluable tool for certain complex scripting challenges.