0

This is what i've written so far:

for /l %%x in (Veronica, Nils, Mike, Tom) do (
set word=%%x
set str="My name is Name"
call set str=%%str:Name=%word%%%
echo %str% >> names.txt
)

Unfortunately the only output is:

ECHO is on.
ECHO is on.
ECHO is on.

in an infinite loop.

Any ideas on where I've gone wrong/

Hamish Mckie
  • 17
  • 1
  • 1
  • 5
  • It's a standard `For` loop you need not a `For /L` and doublequotes are your friend, don't be afraid to use them. You've got the string `name` in there twice too and you can `Call Echo` to bypass an unneeded, `Set`. And you do know that you can use `For %%A In (Veronica, Nils, Mike, Tom) Do Echo My name is %%A`, don't you? – Compo Sep 06 '18 at 05:39

3 Answers3

0

Windows command processor replaces all occurrences of %variable% within a command block starting with ( and ending with matching ) by current value of referenced environment variable before the command is executed using the command block. See How does the Windows Command Interpreter (CMD.EXE) parse scripts? This command block handling by cmd.exe can be seen by running the batch file from within a command prompt window instead of double clicking on the batch file with @echo off removed or modified to @echo ON or commented out temporarily until the batch file is working as expected.

The help output on running set /? in a command prompt window explains on an IF and a FOR example when and how to use delayed expansion which is the preferred method to access an environment variable value within a command block being defined or modified in same command block.

The other solution is referencing the environment variable using the syntax %%variable%% and using command CALL to force a double parsing of this command line by cmd.exe. In this case %% is modified on parsing entire command block to % on both sides of the referenced environment variable and later on execution of command CALL the remaining %variable% is parsed once again and substituted by current value of the referenced variable.

In posted batch code the environment variable str is not defined above FOR command line with the command block. For that reason echo %str% >> names.txt is on parsing the entire command block modified by cmd.exe to echo >> names.txt before FOR is executed at all and so ECHO just outputs three times the current status.

Also the command line call set str=%%str:Name=%word%%% does not work because of word is not defined before FOR command line with the command block and therefore %word% is substituted already by nothing resulting in the command line call set str=%str:Name=% being finally executed three times on loop execution.

The next error is usage of FOR option /L for this loop. The help output on running for /? in a command prompt window explains for for /L:

FOR /L %variable IN (start,step,end) DO command [command-parameters]

    The set is a sequence of numbers from start to end, by step amount.
    So (1,1,5) would generate the sequence 1 2 3 4 5 and (5,-1,1) would
    generate the sequence (5 4 3 2 1)

It should be clear after reading this help that /L is not the right option for this task.

The next mistake is that string substitutions done with command SET or directly by cmd.exe on parsing the command line are always applied case-insensitive. The string assigned to environment variable str contains name and Name which are both replaced on string substitution. That is not really wanted here.

Then there is a big difference on using set variable="value" or on using set "variable=value". The latter is the better as explained in detail in answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line?

Finally the space character left to redirection operator >> is also output by command ECHO resulting in a trailing space in the file. Therefore the redirection operator >> should not be separated by a space from string to write into the file.

First working code for the example uses delayed environment variable expansion:

@echo off
setlocal EnableExtensions EnableDelayedExpansion
del names.txt 2>nul
set "str=My name is #name#"
for %%I in (Veronica, Nils, Mike, Tom) do echo !str:#name#=%%~I!>>names.txt
endlocal

Second working code for the example uses the CALL trick:

@echo off
del names.txt 2>nul
set "str=My name is #name#"
for %%I in (Veronica, Nils, Mike, Tom) do call echo %%str:#name#=%%~I%%>>names.txt

For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.

  • call /?
  • del /?
  • echo /?
  • endlocal /?
  • for /?
  • setlocal /?
  • set /?

See also the Microsoft article about Using command redirection operators.

Mofi
  • 46,139
  • 17
  • 80
  • 143
0

You might want to look at delayedexpansion run from cmdline setlocal /? to see how it is enabled and set /? to see the explanation of it:

@echo off
setlocal enabledelayedexpansion
for %%x in (Veronica, Nils, Mike, Tom) do (
  set "word=%%x"
  set "str=My name is repl"
  call set "str=%%str:repl=!word!%%"
  echo !str!
)

To make the script cleaner looking, you might even want to either add the names to a variable which can be updated, without having to extend your for loop, by calling the variable:

@echo off
set "names=Veronica,Nils,Mike,Tom"
setlocal enabledelayedexpansion
for %%x in (%names%) do (
  set "word=%%x"
  set "str=My name is repl"
  call set "str=%%str:repl=!word!%%"
  echo !str!
)

Note that we replace the % with ! where we need variables expanded. Also not that I set my double quotes starting before the variable name in each set

Gerhard
  • 22,678
  • 7
  • 27
  • 43
0

Some point in your code:

Errors:

  • The FOR /L %var IN (start,step,end) syntax is used is to iterate through a sequence of numbers from start to end incrementing by the amount which is specified by step So the loop variable (%var) represents the current number in sequence.
    But you want to iterate through a set of strings, for that purpose you have to use the naked variant of the FOR command (FOR without any switches). The documentation says that it is for iterating through a set of files, but as long as the strings does not contain the wildcard characters *? it can be safely used to iterate through set of strings.

    So as the first step to correct the code, the /L switch must be removed the FOR command:
    for %%x in (Veronica, Nils, Mike, Tom) do ...

  • Normal variable expansions; Also known as percent variable expansion, occurs before the code actually get executed, So in a block of code, you can only reliably use percent expansion for the variables which were defined before entering that block of code, Any modification to variables in the same block will not be visible by percent expansion (%var%) because it has been expanded to whatever it was before entering the block.

    To overcome this limitation you can either double up the percents around the variable and prefix the command with CALL: call echo %%var%% or use delayed variable expansion:echo !var!

    So the next step would be to replace echo %str% by call echo %%str%%.

  • The variable substitutions are not case sensitive, So in the substitution %str:Name=Vernica%, because the str variable contains two occurrence the the substring "name" ("My name is Name") it will replace both and the result will be My Veronica is Veronica. For the replacement of the Name part you have to use a substring which is unique within the main string:
    set "str=My name is $Name"
    call set str=%%str:$Name=%%x%%

    Also note the direct use of FOR variable %%x in the substitution. There is no need to assign %%x to another variable like what you did with set word=%%x. And even if you, it will not work because in call set str=%%str:Name=%word%%% the variable %word% has already being replaced by batch parser with whichever value it had before entering the FOR block.

Summing up the above points, a working code can written like this

for %%x in (Veronica, Nils, Mike, Tom) do (
    set "str=My name is $Name"
    call set str=%%str:$Name=%%x%%
    call echo %%str%% >>"names.txt"
)

The above works correctly and produces the desired result, but it is extremely inefficient and can be optimized further to produce a more efficient, faster, shorter and cleaner code which will be described in the following section.


Inefficiencies:

Continuing with the corrected code in the last section

  • The str variable is a static variable, that is on each iteration of the loop you initialize it with the same string My name is $Name So it can be moved outside of the loop and initialized just once.

  • Since you are using the substituted string only once to write it to output device, the set and echo can be combined to single command doing both at once: call echo %%str:$Name=%%x%%

  • The redirection used inside the loop (>>"names.txt"), So on each iteration of the loop, the file names.txt will be opened, written to, and closed. This is very inefficient, think of the loop that does this tens of thousands of time. The whole FOR loop can be placed inside a redirected block, So the file names.txt will be opened only once on when the loop starts and closed when the loop in finished.

  • It is better to enclose the name items between quotes, should they contain any spaces or special characters the code will not affected. Actually it is not an inefficiency but rather a best practice recommendation.


Final result

Without delayed expansion:

set "str=My name is $Name"
(for %%x in ("Veronica", "Nils", "Mike", "Tom") do call echo %%str:$Name=%%~x%%)>"names.txt"

With delayed expansion:

set "str=My name is $Name"
setlocal EnableDelayedExpansion
(for %%x in ("Veronica", "Nils", "Mike", "Tom") do echo !str:$Name=%%~x!)>"names.txt"
endlocal

In comparison between using CALL or DelayedExpansion

  • CALL is very slow, DelayedExpansion is much faster.

  • DelayedExpansion will fail if the names contain !

  • CALL will fail if the names contain any one the &|<> characters, unless you enclose them in quotes when using echo. e.g. call echo "%%str:$Name=%%~x%%".
    Of course this can't happen with human names, but with generic strings.

SachaDee
  • 9,245
  • 3
  • 23
  • 33
sst
  • 1,443
  • 1
  • 12
  • 15