Windows – String replacement in Windows batch

batchfind and replacestringwindows

I am trying to understand how string replacement in Windows batches actually works and having trouble.

@echo off
set var=wild
set varnew=%var:l=n%
echo var is: %var%
echo varnew is: %varnew%

does work; it generates the expected output:

var is: wild
varnew is: wind

But this doesn't (example directory "Main"):

@echo off
for /D %%G IN (*) do (
    setlocal
    echo G is: %%G
    set _srcp=%%G
    echo _srcp is %_srcp%
    rem set _newp=%_newp:ai=_01_% <-- confused variable
    set _newp=%_srcp:ai=_01_%
    echo._newp is: %_newp%
    endlocal
                     )

It generates this output:

G is: Main
_srcp is Main
_newp is: %_srcp:ai=_01_

I would expect the code to generate _newp is: M_01_n as the last line. I'm really out of ideas here, can somebody please point me into the right direction?

BB

Best Answer

You have a couple problems:

  • %var% expansion occurs when the statement is parsed, and the entire parenthesized code block is parsed in one pass, before any commands are executed. So the value is the value that existed before the loop was started. The solution is delayed expansion, which occurs as each command within the loop is being executed.

  • Your logic is wrong - the assignment of _newp should be based on the value of _srcp

The CMD processor is a complicated beast (and also poorly documented). There are multiple points where various types of variables are expanded, and you must fully understand them if you truly want to make the most of batch programming. It is all explained in the link, but in summary, the order of expansion is:

1) % expansion - Parameter: echo %1 or Environment variable: echo %var%
---- Most parsing has completed by now ----
2) FOR variable expansion: for %%A in (*) do echo %%A
3) Delayed environment variable expansion: echo !var!
4) CALL % expansion - Parameter: call echo %%1 or Environment variable: call echo %%var%%
5) SET /A environment variable expansion: `set /a "value=var+1"

Note that delayed expansion requires delayed expansion to be enabled via SETLOCAL EnableDelayedExpansion

The following code using delayed expansion will give the result you seek:

@echo off
for /D %%G in (*) do (
  setlocal enableDelayedExpansion
  echo G is: %%G
  set "_srcp=%%G"
  echo _srcp is !_srcp!
  set "_newp=!_srcp:ai=_01_!"
  echo _newp is: !_newp!
  endlocal
)

Note that delayed expansion occurs after FOR variable expansion, so the result will be corrupted if %%G constains !. This can be avoided by additional SETLOCAL:

for /D %%G in (*) do (
  setlocal disableDelayedExpansion
  echo G is: %%G
  set "_srcp=%%G"
  setlocal enableDelayedExpansion
  echo _srcp is !_srcp!
  set "_newp=!_srcp:ai=_01_!"
  echo _newp is: !_newp!
  endlocal
  endlocal
)

You could also get the desired result using CALL with double percents, but this is much slower. The speed is not important if executed a few times, but it becomes very significant if executed thousands of times in a loop.

@echo off
for /D %%G in (*) do (
  setlocal
  echo G is: %%G
  set "_srcp=%%G"
  call echo _srcp is %%_srcp%%
  call set "_newp=%%_srcp:ai=_01_%%"
  call echo _newp is: %%_newp%%
  endlocal
)
Related Question