这是我迄今为止所写的内容:
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
)
不幸的是,唯一的输出是:
ECHO is on.
ECHO is on.
ECHO is on.
在一个无限循环中。
关于我哪里出错的想法/
Windows命令处理器在使用命令块执行命令之前,用引用的环境变量的当前值替换命令块中以(
开始、以匹配的)
结束的所有%variable%
。请参阅Windows命令解释器(CMD.EXE)如何解析脚本?通过在命令提示窗口中运行批处理文件,而不是双击删除了@echo off
或将其修改为@echo ON
或暂时注释掉的批处理文件直到批处理文件按预期工作,可以看到cmd.exe
对该命令块的处理。
在命令提示符窗口中运行set /?
的帮助输出在IF和FOR示例中解释了何时以及如何使用延迟扩展,延迟扩展是访问在同一命令块中定义或修改的命令块中的环境变量值的首选方法。
另一种解决方案是使用语法%%variable%%
引用环境变量,并使用命令CALL强制cmd.exe
对此命令行进行双重解析。在这种情况下,%%
在将整个命令块解析为引用环境变量两侧的%
时被修改,随后在执行命令CALL时,剩余的%variable%
被再次解析并被引用变量的当前值替换。
在发布的批处理代码中,环境变量str
没有在带有命令块的FOR命令行上定义。因此,在执行For之前,echo %str% >> names.txt
正在解析由cmd.exe
修改为echo >> names.txt
的整个命令块,因此ECHO仅输出当前状态的三倍。
此外,由于word
没有在带有命令块的FOR命令行之前定义,因此命令行call set str=%%str:Name=%word%%%
不工作,因此%word%
已经被什么都没有取代,导致命令行call set str=%str:Name=%
最终被执行三次循环执行。
下一个错误是对该循环使用FOR选项/L
。在命令提示符窗口中运行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)
阅读本帮助后应该清楚,/L
不是此任务的正确选项。
下一个错误是,在解析命令行时,使用命令SET或直接由cmd.exe
执行的字符串替换总是不区分大小写。分配给环境变量str
的字符串包含name
和Name
,它们都在字符串替换时被替换。这在这里并不是真正需要的。
使用CCD_ 29与使用CCD_。后者更好,正如在命令行上使用"set var=text"后,为什么没有带"echo%var%"的字符串输出的答案中详细解释的那样?
最后,留给重定向运算符>>
的空格字符也由命令ECHO输出,从而在文件中产生尾随空格。因此,重定向运算符>>
不应与要写入文件的字符串相隔一个空格。
示例的第一个工作代码使用延迟环境变量扩展:
@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
示例的第二个工作代码使用CALL技巧:
@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
为了理解所使用的命令及其工作方式,请打开命令提示符窗口,在那里执行以下命令,并非常仔细地阅读为每个命令显示的所有帮助页面。
call /?
del /?
echo /?
endlocal /?
for /?
setlocal /?
set /?
另请参阅Microsoft关于使用命令重定向运算符的文章。
您可能需要查看从cmdlinesetlocal /?
运行的delayedexpansion
以了解它是如何启用的,以及set /?
以查看它的解释:
@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!
)
为了使脚本看起来更干净,您甚至可能希望将名称添加到可以更新的变量中,而不必扩展for循环,方法是调用变量:
@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!
)
请注意,我们将%
替换为!
,其中我们需要扩展变量。也不是说我在每个set
中设置了从变量名之前开始的双引号
代码中的某些点:
错误:
-
FOR /L %var IN (start,step,end)
语法用于在从start
到end
的一系列数字中迭代,增量由step
指定。因此循环变量(%var
)表示序列中的当前数字
但是,如果要遍历一组字符串,则必须使用FOR
命令的裸变体(不带任何开关的FOR
)。文档中说,它用于遍历一组文件,但只要字符串不包含通配符*?
,就可以安全地使用它来遍历一组字符串
因此,作为纠正代码的第一步,必须删除/L
开关的FOR
命令:for %%x in (Veronica, Nils, Mike, Tom) do ...
-
正态变量展开式;也称为变量百分比展开,发生在代码实际执行之前。因此,在一个代码块中,您只能可靠地对输入该代码块之前定义的变量使用百分比展开,通过百分比展开(
%var%
),对同一块中变量的任何修改都将不可见,因为它已被展开到进入块之前的任何状态
要克服这一限制,您可以将变量周围的百分比加倍,并在命令前面加上CALL
:call echo %%var%%
,或者使用延迟变量扩展:echo !var!
因此,下一步将用call echo %%str%%
替换echo %str%
-
变量替换不区分大小写,因此在替换
%str:Name=Vernica%
中,由于str
变量包含两个出现的子字符串"name"("My name is Name"
),它将替换这两个,结果将为My Veronica is Veronica
。为了替换Name部分,您必须使用在主字符串中唯一的子字符串:set "str=My name is $Name"
call set str=%%str:$Name=%%x%%
还要注意在替换中直接使用了
FOR
变量%%x
。不需要像使用set word=%%x
那样将%%x
分配给另一个变量。即使您这样做,它也不会起作用,因为在call set str=%%str:Name=%word%%%
中,变量%word%
已经被批处理解析器替换为它在进入FOR
块之前的值
总结以上几点,工作代码可以写成这样的
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"
)
以上操作正确,并产生了所需的结果,但效率极低,可以进一步优化,以产生更高效、更快、更短、更干净的代码,这将在下一节中进行描述。
效率低下:
继续最后一节中的校正代码
str
变量是一个静态变量,也就是说,在循环的每次迭代中,您都可以使用相同的字符串My name is $Name
对其进行初始化。因此,它可以移动到循环之外,只初始化一次。由于您只使用一次替换字符串将其写入输出设备,因此
set
和echo
可以组合为一个命令,同时执行这两个命令:call echo %%str:$Name=%%x%%
循环内部使用的重定向(
>>"names.txt"
),因此在循环的每次迭代中,文件names.txt
将被打开、写入和关闭。这是非常低效的,想想成千上万次这样做的循环。整个FOR
循环可以放在重定向块内,因此当循环开始时,文件names.txt
将只打开一次,当循环结束时关闭。最好将名称项括在引号之间,如果它们包含任何空格或特殊字符,则代码不会受到影响。事实上,这并不是效率低下,而是一个最佳实践建议。
最终结果
无延迟扩展:
set "str=My name is $Name"
(for %%x in ("Veronica", "Nils", "Mike", "Tom") do call echo %%str:$Name=%%~x%%)>"names.txt"
延迟扩展:
set "str=My name is $Name"
setlocal EnableDelayedExpansion
(for %%x in ("Veronica", "Nils", "Mike", "Tom") do echo !str:$Name=%%~x!)>"names.txt"
endlocal
使用CALL
和DelayedExpansion
的比较
CALL
非常慢,DelayedExpansion
快得多。如果名称包含
!
,则DelayedExpansion
将失败如果名称包含任何一个
&|<>
字符,则CALL
将失败,除非在使用echo
时用引号将其括起来。例如CCD_ 94
当然,这不会发生在人名上,但发生在通用字符串上。