EnableDelayedExpansion

Delayed Expansion will cause variables within a batch file to be expanded at execution time rather than at parse time, this option is turned on with the SETLOCAL EnableDelayedExpansion command.

Variable expansion means replacing a variable (e.g. %windir%) with its value C:\WINDOWS

With loop commands like FOR, compound or bracketed expressions, Delayed Expansion will allow you to always read the current value of the variable.

You can write new variable values with or without Delayed Expansion.

Without delayed expansion you can swap the value of two variables in one line:
Set "var1=%var2%" & set "var2=%var1%"

When Delayed Expansion is in effect, variables can be immediately read using !variable_name!
You can set two variables to the same thing in one line:
Set "var3=April" & set "var4=!var3!"
you can still read and use %variable_name% and that will continue to show the initial value (expanded at the beginning of the line).

An alternative to enabling delayed expansion, is to avoid using brackets to group commands and instead call or goto a separate subroutine, see the example below.

Examples

Save this as a batch file e.g. demo.cmd and then execute (demo) to see the result:

@echo off
SETLOCAL
Set "_var=first"
Set "_var=second" & Echo %_var%

This will output: first
The value of %_var% was read into memory BEFORE the Set command which changes it.

Now repeating this with Delayed Expansion:

@echo off
SETLOCAL EnableDelayedExpansion
Set "_var=first"
Set "_var=second" & Echo %_var% !_var!

This will output: first second
The value of the !_var! variable is evaluated as late as possible while the %_var% variable works just as before.

FOR Loops

Delayed variable expansion is often useful when working with FOR Loops, normally an entire FOR loop is evaluated as a single command even if it spans multiple lines of a batch script.
This is the default behaviour of a FOR loop:

@echo off
setlocal
:: count to 5 storing the results in a variable
set _tst=0
FOR /l %%G in (1,1,5) Do (echo [%_tst%] & set /a _tst+=1)
echo Total = %_tst%

C:\> demo_batch.cmd
[0]
[0]
[0]
[0]
[0]
Total = 5

Notice that when the FOR loop finishes we get the correct total, so the variable correctly increments, but during each iteration of the loop the variable will stubbornly display its initial value of 0 even as we set new values.

The same script with EnableDelayedExpansion, gives the same final result but also displays the intermediate values:

@echo off
setlocal EnableDelayedExpansion 
:: count to 5 storing the results in a variable
set _tst=0
FOR /l %%G in (1,1,5) Do (echo [!_tst!] & set /a _tst+=1)
echo Total = %_tst%

C:\> demo_batch.cmd
[0]
[1]
[2]
[3]
[4]
Total = 5

Notice that within the for loop we use !variable! instead of %variable%.

An alternative way to write this is by calling a subroutine, because this breaks out of the loop it does not need Delayed Expansion

@echo off
setlocal
:: count to 5 storing the results in a variable
set _tst=0
FOR /l %%G in (1,1,5) Do (call :sub %%G)
echo Total = %_tst%
goto :eof

:sub
echo [%1] & set /a _tst+=1
goto :eof

C:\> demo_batch.cmd
[1]
[2]
[3]
[4]
[5]
Total = 5

Using %var% and !var!

With delayed expansion a single variable can hold two values:

@echo off
Setlocal EnableDelayedExpansion
Set "_var=Old"
For /L %%G in (1,1,3) Do (
  Set "_var=New"
  Echo [%_var%] is now [!_var!]
)

Will output:

[Old] is now [New]
[Old] is now [New]
[Old] is now [New]

Notice that the variable reverts to the old value in each iteration of the loop, there is no need for Set _var=%_var%
This is because each iteration of the FOR loop will launch a new batch file context along with any specified parameters.

Other effects - Punctuation

Because DelayedExpansion expands variables later, that means that any escape characters (^) and redirection characters in your expressions will be evaluated before the variable expansion and this can be very useful:

@echo off
Setlocal
Set _html=Hello^>World
Echo %_html%

In the above, the Echo command will create a text file called 'world' - not quite what we wanted! This is because the variable is expanded at parse time, so the last line is executing Echo Hello > World and the > character is interpreted as a redirection operator.

If we now try the same thing with EnableDelayedExpansion:

Setlocal EnableDelayedExpansion
Set _html=Hello^>World
Echo !_html!

With delayed expansion, the variable (including the > ) is only expanded at execution time so the > character is never interpreted as a redirection operator.
This makes it possible to work with HTML and XML formatted strings in a variable.

When delayed expansion is enabled AND at least one exclamation mark in a line is present, then any carets will be interpreted as an escape and so will disappear from the output:

Setlocal EnableDelayedExpansion
Echo "Hello^World"
Echo "Hello^World!"

The above will output:

"Hello^World"
"HelloWorld"

Even if you double the carets ^^, which normally would act as an escape, or add an escape just before the exclamation mark, the presence of an exclamation mark anywhere in the line will still have this effect.

More Examples

Set and then Echo the same variable within a FOR command:

Setlocal EnableDelayedExpansion
for /f %%G in ("abc") do ( set _demo=%%G & echo !_demo!)

Replace a variable_name using values from another variable:

@echo off
setlocal EnableDelayedExpansion
Set var1=Hello ABC how are you
Set var2=ABC
Set result=!var1:%var2%=Beautiful!
Echo [!result!]

Another method for replacing a variable named with the content of another is CALL SET

Some unexpected behaviours when using delayed variable expansion

If DelayedExpansion is used in conjunction with a FOR command looping through a set of files, if any file in the set has an exclamation mark '!' in the filename, that will be interpreted like a !variable!.
Although this is not a common character used in filenames, it can cause scripts to fail. This happens because the parameter expansion (%%P) happens just before the delayed expansion phase tries to interpret my!filen!ame.txt

When DelayedExpansion is used inside a code block (one or several commands grouped between parentheses) whose output is Piped, the variable expansion will be skipped.
When you use a pipe, both parts of the pipe will be executed in a new cmd.exe instance and these instances are started by default with disabled delayed expansion.

Why this behaviour?

The SET command was first introduced with MS-DOS 2.0 in March 1983, at that time memory and CPU were very limited and the expansion of variables once per line was enough.
Delayed Expansion was introduced some 16 years later in 1999 by which time millions of batch files had been written using the earlier syntax. Retaining immediate expansion as the default preserved backwards compatibility with existing batch files.

This is not how anyone would design a language if starting from scratch, indeed PowerShell behaves like this:

PS C:\> $demo = "First"
PS C:\> $demo = "Second" ; echo $demo
Second

Default behaviour

EnableDelayedExpansion is Disabled by default.
EnableDelayedExpansion can also be enabled by starting CMD with the /v switch.

After being turned on, Delayed Expansion can be turned off again with SETLOCAL DisableDelayedExpansion

EnableDelayedExpansion can be set as the default in the registry under HKLM or HKCU:

[HKEY_CURRENT_USER\Software\Microsoft\Command Processor]
"DelayedExpansion"= (REG_DWORD)
1=enabled 0=disabled (default)

Changing this default is not recommended.
Unless every batch script you run begins with an appropriate SETLOCAL DisableDelayedExpansion or SETLOCAL EnableDelayedExpansion command, then it is likely that changing the default will break some scripts.

“At times it is folly to hasten at other times, to delay. The wise do everything in its proper time” ~ Ovid

Related commands

Forum discussion - EnableDelayedExpansion (many thanks to Jeb and Aacini for clarifying quite a few points).
OldNewThing - Longer explanation of EnableDelayedExpansion.
SETLOCAL - Start localisation of environment changes in a batch file.


 
Copyright © 1999-2024 SS64.com
Some rights reserved