How does delayed expansion works in batch script?

Having this simple cmd:

>set ar[0]=orange
>set ar[1]=apple
>set ar[2]=banana
>for %i in (0,1,2) do echo !ar[%i]!

outputs:

>echo !ar[0]!
!ar[0]!
>!ar[1]!
!ar[1]!
>!ar[2]!
!ar[2]!

So it obviously do not expand to its values. How to do so?


Delayed expansion is mainly used to expand variable values more than one time, especially in For loops.

Quoting from https://ss64.com/nt/delayedexpansion.html

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

By default expansion will happen just once, before each line is executed. The !delayed! expansion is performed each time the line is executed, or for each loop in a FOR looping command. First we look at this example:

set i=0
for /l %%a in (0,1,10) do (
  set /a i=%i%+1
  echo %i%
)

Here we expect this will output numbers 1 to 10. However, everytime it will output only 0. Because every time the variables are expanded one time, so values don't change.

This time we will try with delayed expansion:

setlocal enabledelayedexpansion 
set i=0
for /l %%a in (0,1,10) do (
  set /a i=!i!+1
  echo !i!
)

This time we will get the expected output, because each time we expand the values and the value gets changed.

Your code does not work because you have to add setlocal enabledelayedexpansion at start of your code.

Hope that helps


Sources:
Phase 5 in How does the Windows Command Interpreter (CMD.EXE) parse scripts?

Delayed Expansion: Only if delayed expansion is on, the command is not in a parenthesized block on either side of a pipe, and the command is not a "naked" batch script (script name without parentheses, CALL, command concatenation, or pipe).

  • Each token for a command is parsed for delayed expansion independently.
    • Most commands parse two or more tokens - the command token, the arguments token, and each redirection destination token.
    • The FOR command parses the IN clause token only.
    • The IF command parses the comparison values only - either one or two, depending on the comparison operator.
  • For each parsed token, first check if it contains any !. If not, then the token is not parsed - important for ^ characters. If the token does contain !, then scan each character from left to right:
    • If it is a caret (^) the next character has no special meaning, the caret itself is removed
    • If it is an exclamation mark, search for the next exclamation mark (carets are not observed anymore), expand to the value of the variable.
      • Consecutive opening ! are collapsed into a single !
      • Any remaining unpaired ! is removed
    • Expanding vars at this stage is "safe", because special characters are not detected anymore (even <CR> or <LF>)
    • For a more complete explanation, read the 2nd half of this from dbenham same thread - Exclamation Point Phase

Phase 5.3) Pipe processing: Only if commands are on either side of a pipe
Each side of the pipe is processed independently and asynchronously.

  • If command is internal to cmd.exe, or it is a batch file, or if it is a parenthesized command block, then it is executed in a new cmd.exe thread via %comspec% /S /D /c" commandBlock", so the command block gets a phase restart, but this time in command line mode.
    • If a parenthesized command block, then all <LF> with a command before and after are converted to <space>&. Other <LF> are stripped.
  • This is the end of processing for the pipe commands.
  • See https://stackoverflow.com/q/8192318/1012053 for more about pipe parsing and processing

Phase 5.5) Execute Redirection: Any redirection that was discovered in phase 2 is now executed.

  • The results of phases 4 and 5 can impact the redirection that was discovered in phase 2.
  • If the redirection fails, then the remainder of the command is aborted. Note that failed redirection does not set ERRORLEVEL to 1 unless || is used.


enter image description here

  • You can also use the Delayed Expansion by cmd.exe with flag [/v:on | /v], on the command-line or in the bat/cmd files.

enter image description here

  • In your command-line using cmd.exe /v:on /c
set ar[0]=orange
set ar[1]=apple
set ar[2]=banana
for %i in (0,1,2) do cmd.exe /v:on /C"echo !ar[%i]!

  • In your bat/cmd file without declaring setlocal enabledelayedexpansion, you can also use cmd.exe /v:on /c "command & command | command || command..."
@echo off 

set "ar[0]=orange" 
set "ar[1]=apple"
set "ar[2]=banana" 
for %%i in (0,1,2)do cmd /v /c "echo\ !ar[%%i]!"

  • In your bat/cmd declaring setlocal enabledelayedexpansion:
@echo off 

set "ar[0]=orange"
set "ar[1]=apple"
set "ar[2]=banana" 

setlocal enabledelayedexpansion
for %%i in (0,1,2)do echo\ !ar[%%i]!

enndlocal


  • Some layout variances for this bat/cmd code:
@echo off 
set "ar[0]=orange" & set "ar[1]=apple" & set "ar[2]=banana" 
for %%i in (0,1,2)do %ComSpec% /v:on /c"echo !ar[%%i]!"
%__APPDIR__%timeout.exe /t -1 & endlocal & goto :EOF
@echo off 
set "ar[0]=orange" & set "ar[1]=apple" & set "ar[2]=banana" 
setlocal enabledelayedexpansion && for %%i in (0,1,2)do echo\ !ar[%%i]!
%__APPDIR__%timeout.exe /t -1 & endlocal & goto :EOF
@echo off 
set "ar[0]=orange" && set "ar[1]=apple" && set "ar[2]=banana" 
for %%i in (0,1,2)do <con: %ComSpec% /v:on /c"echo !ar[%%i]!"
call <con: rem./ && %__APPDIR__%timeout.exe /t -1 && endlocal
  • You also can use call in bat/cmd or in your command-line to update this value:
set "ar[0]=orange"
set "ar[1]=apple"
set "ar[2]=banana" 
for %i in (0,1,2)do for %i in (0,1,2)do <con: call echo %ar[%i]%


  • In your bat/cmd file or command line, all the following commands are the same, observing replacing %i with %%i in the case of use in bat/cmd files
for %i in (0,1,2) do %ComSpec% /v:on /r "echo !ar[%i]!"
for %i in (0,1,2) do %ComSpec% /v:on /c "echo !ar[%i]!"

for %i in (0,1,2) do cmd.exe /v:on /r "echo !ar[%i]!"
for %i in (0,1,2) do cmd.exe /v:on /c "echo !ar[%i]!"

for %i in (0,1,2) do cmd /v:on /r "echo !ar[%i]!"
for %i in (0,1,2) do cmd /v:on /c "echo !ar[%i]!"


for %i in (0,1,2) do %ComSpec% /v /r "echo !ar[%i]!"
for %i in (0,1,2) do %ComSpec% /v /c "echo !ar[%i]!"

for %i in (0,1,2) do cmd.exe /v /r "echo !ar[%i]!"
for %i in (0,1,2) do cmd.exe /v /c "echo !ar[%i]!"

for %i in (0,1,2) do cmd /v /r "echo !ar[%i]!"
for %i in (0,1,2) do cmd /v /c "echo !ar[%i]!"


for %i in (0,1,2) do %ComSpec%/v:on/r "echo !ar[%i]!"
for %i in (0,1,2) do %ComSpec%/v:on/c "echo !ar[%i]!"

for %i in (0,1,2) do cmd.exe/v:on/r "echo !ar[%i]!"
for %i in (0,1,2) do cmd.exe/v:on/c "echo !ar[%i]!"

for %i in (0,1,2) do cmd/v:on/r "echo !ar[%i]!"
for %i in (0,1,2) do cmd/v:on/c "echo !ar[%i]!"

for %i in (0,1,2) do %ComSpec%/v/r "echo !ar[%i]!"
for %i in (0,1,2) do %ComSpec%/v/c "echo !ar[%i]!"

for %i in (0,1,2) do cmd.exe/v/r "echo !ar[%i]!"
for %i in (0,1,2) do cmd.exe/v/c "echo !ar[%i]!"

for %i in (0,1,2) do cmd/v/r "echo !ar[%i]!"
for %i in (0,1,2) do cmd/v/c "echo !ar[%i]!"


for %i in (0,1,2) do %ComSpec%/v:on/r"echo !ar[%i]!"
for %i in (0,1,2) do %ComSpec%/v:on/c"echo !ar[%i]!"

for %i in (0,1,2) do cmd.exe/v:on/r"echo !ar[%i]!"
for %i in (0,1,2) do cmd.exe/v:on/c"echo !ar[%i]!"

for %i in (0,1,2) do cmd/v:on/r"echo !ar[%i]!"
for %i in (0,1,2) do cmd/v:on/c"echo !ar[%i]!"


for %i in (0,1,2) do %ComSpec%/v/r"echo !ar[%i]!"
for %i in (0,1,2) do %ComSpec%/v/c"echo !ar[%i]!"

for %i in (0,1,2) do cmd.exe/v/r"echo !ar[%i]!"
for %i in (0,1,2) do cmd.exe/v/c"echo !ar[%i]!"

for %i in (0,1,2) do cmd/v/r"echo !ar[%i]!"
for %i in (0,1,2) do cmd/v/c"echo !ar[%i]!"


for %i in (0,1,2) do %ComSpec%/v/recho !ar[%i]!
for %i in (0,1,2) do %ComSpec%/v/cecho !ar[%i]!

for %i in (0,1,2) do cmd.exe/v/r"echo !ar[%i]!
for %i in (0,1,2) do cmd.exe/v/c"echo !ar[%i]!

for %i in (0,1,2) do cmd/v/recho !ar[%i]!
for %i in (0,1,2) do cmd/v/cecho !ar[%i]!

  • Some further reading:

    [√] set /?

    [√] CMD /?

    [√] For Loop

    [√] For /F Loop

    [√] Conditional Execution || && ...

    [√] Why does call set work differently

    [√] Understanding start, 2>nul, cmd, and other symbols in a batch file

    [√] Use parentheses/brackets to group expressions in a Windows batch file