1

Take this simple series of commands executed in a batch environment:

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

It outputs the following:

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

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

1
  • Use % signs. Read set /? starting from Finally, support for delayed environment variable expansion has been added.
    – Mark
    Jul 18, 2020 at 2:59

2 Answers 2

4

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.



enter image description here

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

enter image description here

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 / 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 / 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 / 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 / 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 / file or command line, all the following commands are the same, observing replacing %i with %%i in the case of use in / 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]!

3

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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .