aschipfl
aschipfl

Reputation: 34909

Why does FOR fail with delayed sub-string expansion after IN?

Sub-string expansion works within the set of a for loop (that is the parenthesised part after in) when immediate expansion is used (write %%I instead of %I in a batch file):

set "X=123"
for %I in (%X:~1,1%) do @echo %I

However, it fails when delayed expansion is applied:

for %I in (!X:~1,1!) do @echo %I

I would expect the output to be:

2

But instead it is:

!X:~1
1!

Why is this and how can I prevent that?

I know I could work around that by quoting the set and using ~, but this is not what I want here:

for %I in ("!X:~1,1!") do @echo %~I

The following command line fails too:

for %I in (!X:*2=!) do @echo %I

The unexpected output is:

!

Also for command lines using the /R, /L and /R switches fail with such sub-string syntax.

It surely has got something to do with the fact that , and = are token separators for cmd, just like SPACE, TAB, ;, etc.

Upvotes: 3

Views: 120

Answers (1)

aschipfl
aschipfl

Reputation: 34909

According to the thread How does the Windows Command Interpreter (CMD.EXE) parse scripts? and also numerous comments here, the answer lies in the special way for loops are parsed.

The key is the following excerpt of this answer (see the italic text in particular):

Phase 2) Process special characters, tokenize, and build a cached command block:
[...]

  • Three commands get special handling - IF, FOR, and REM
    [...]
    • FOR is split in two after the DO. A syntax error in the FOR construction will result in a fatal syntax error.
    • The portion through DO is the actual FOR iteration command that flows all the way through phase 7
      • All FOR options are fully parsed in phase 2.
      • The IN parenthesized clause treats <LF> as <space>. After the IN clause is parsed, all tokens are concatenated together to form a single token.
      • Consecutive token delimiters collapse into a single space throughout the FOR command through DO.

Due to the fact that delayed expansion happens after parsing of for and the described behaviour, token separators like SPACE, TAB, ,, ;, =, etc. become converted to a single SPACE, hence a sub-string expansion expression like !X:~1,1! is changed to !X:~1 1!, and a sub-string substitution expression like !X:*2=! is changed to !X:*2 !, which both are invalid syntax.

Therefore to solve the issue you need to escape the token separators by ^ like:

for %I in (!X:~1^,1!) do @echo %I

and:

for %I in (!X:*2^=!) do @echo %I

(By the way, there is a very similar problem with if statements.)

Upvotes: 1

Related Questions