3

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

5

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
)
  • Wow this is complicated. I think I'll stick to powershell. – chue x Aug 5 '15 at 14:25
  • Wow, what an elaborate, helpful and friendly answer, thanks a lot! I'll really have to think this through and test my understanding for a while - will surely get back to you and SU if I get stuck again. – beerbear Aug 8 '15 at 13:27
  • Excuse me, Dave. I would not included the SET /A environment variable expansion: set /a "value=var+1" as the step number 5) in this description because this step is entirely dependant on the specific command executed. I would wrote something like this instead: "Certain commands perform a further expansion of the variable whose name is given as parameter, like is the case of set /P command". IMHO – Aacini Aug 11 '15 at 18:00
  • @Aacini - I see your point, but it is an inherent part of CMD.EXE. I believe SET /A is the only internal command that does its own variable parsing and expansion. – dbenham Aug 11 '15 at 18:45
0

further to davebenham's answer

doing echo %var% within a block like FOR or IF, doesn't work correctly. The %var% variables just don't update. You have to use !var! and to get !var! to work you have to setlocal EnableDelayedExpansion

an explanation of this exists in cmd help, though it's not obvious which command's help explains that! It's set /?

set /?

Finally, support for delayed environment variable expansion has been added. This support is always disabled by default, but may be enabled/disabled via the /V command line switch to CMD.EXE. See CMD /?

Delayed environment variable expansion is useful for getting around the limitations of the current expansion which happens when a line of text is read, not when it is executed. The following example demonstrates the problem with immediate variable expansion:

set VAR=before
if "%VAR%" == "before" (
    set VAR=after
    if "%VAR%" == "after" @echo If you see this, it worked
)

would never display the message, since the %VAR% in BOTH IF statements is substituted when the first IF statement is read, since it logically includes the body of the IF, which is a compound statement. So the IF inside the compound statement is really comparing "before" with "after" which will never be equal. Similarly, the following example will not work as expected:

set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%

in that it will NOT build up a list of files in the current directory, but instead will just set the LIST variable to the last file found. Again, this is because the %LIST% is expanded just once when the FOR statement is read, and at that time the LIST variable is empty. So the actual FOR loop we are executing is:

for %i in (*) do set LIST= %i

which just keeps setting LIST to the last file found.

Delayed environment variable expansion allows you to use a different character (the exclamation mark) to expand environment variables at execution time. If delayed variable expansion is enabled, the above examples could be written as follows to work as intended:

set VAR=before
if "%VAR%" == "before" (
    set VAR=after
    if "!VAR!" == "after" @echo If you see this, it worked
)

set LIST=
for %i in (*) do set LIST=!LIST! %i
echo %LIST%

You can also test the notation from the command line with

cmd /e:on

starting with the default, cmd /v:off then moving to cmd/v:on Most use cmd .v:off even experts, (perhaps because other things can be interpreted differnly with it), so i'm just using this to show you that you can try the !var! notation within cmd with it.

C:\>set a=5

C:\>echo %a%
5

C:\>echo !a!
!a!


C:\>cmd /v:on
Microsoft Wind
Copyright (c)

C:\>echo !a!
5

C:\>

btw, If you had cmd /v:on or in a batch file, EnableDelayedExpansion on, then as Dave shows, you'd have to be aware of ! being a special character so you'd have an issue if the ! was within a %var%. So that'd be a reason why people don't just turn the mode on full time, there may be other reasons.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.