延迟变量是和预处理有关的东西。
所谓预处理,就是预先处理,这一行为可能会提高程序的执行效率,但同时也容易带来副作用,在这种情况下,预处理就是自作聪明了。
看一个预处理副作用的简单例子:
[Plain Text] 纯文本查看 复制代码 @echo off
set VAR=www.groad.net & echo %VAR%
pause
在没有运行输出的情况下,一般都会觉得输出 groad 这个字串。但事实却是输出:ECHO 处于关闭状态。
当用 echo 命令尝试输出一个空值时,它就会提示 “ECHO 处于关闭状态”。因此,echo %VAR% 这条语句在执行时,VAR 这个变量必定为空,为什么会这样?这就是预处理所造成的副作用。如果将上面的代码改成:
[Plain Text] 纯文本查看 复制代码 @echo off
set VAR=www.groad.net
echo %VAR%
pause
这时就会输出:www.goad.net ,结果是符合预想的。
可见,产生预处理行为的是 & 符号。对于上面的例子,预处理的过程是这样的:
解析器不会从左到右先去执行语句,如果是这样,那就不叫预处理了;而是一次性将整条 set VAR=www.groad.net & echo %VAR% 语句读入,并对变量 VAR 做相应的替换。由于 VAR 变量在该条语句之前,并没有被设置过,因此它为空。所以解析器也就用这个空值去替换 & 符号后的 echo 语句里的 %VAR% (这是一个变量被替换成常量的一个过程),这样在真正执行 echo 语句时,就会看到“ECHO 处于关闭状态”的提示。
由此可见,预处理的自作聪明所带来的副作用让人比较不爽。然而并不仅仅是 & 符号可以引发预处理血案,像 if 和 for 这种复合语句也是预处理会光顾的场所。
if 预处理副作用例子:
[Plain Text] 纯文本查看 复制代码 @echo off
set VAR=www.groad.net
if "%VAR%" == "www.groad.net" (
set VAR=www.google.com
echo %VAR%
)
pause
输出结果为 www.groad.net 而不是 www.google.com 。
for 预处理副作用例子:
[Plain Text] 纯文本查看 复制代码 @echo off
for /f "tokens=* delims=" %%i in ("hello windows bat") do (
set VAR=%%i
echo %VAR%
)
pause
输出结果也是 “ECHO 处于关闭状态”。
对于上面所举的 if 例子,预处理机制会将 echo %VAR% 变成 echo www.groad.net ,即变量 VAR 被置换成了常量 www.groad.net ,因此在程序真正执行时,即使重新设置了 VAR 的值,那也已经毫无作用了。对于 for 的情况也是类似如此。
既然命令行解析器的处理机制已是如此,那么为了避免这种情况,MS 也提供了弥补机制,即“延迟变量”。延迟变量,其意思是,将变量的扩展延迟,换句话来说就是通过某种方式告诉解析器,我的这个变量不要进行预处理替换,请保留它的原貌。
延迟变量的表示法是将变量括在两个感叹号中间(如 !VAR!),这和括在两个百分号中间的普通变量只是形式上的区别,并且在程序的开头加上 setlocal enabledelayedexpansion 这么一句,其意使能延迟变量扩展功能。
按照上述方法,修改上面的几个程序如下:
修正一:
[Plain Text] 纯文本查看 复制代码 @echo off
setlocal enabledelayedexpansion
for /f "tokens=* delims=" %%i in ("hello windows bat") do (
set VAR=%%i
echo !VAR!
)
pause
运行输出:hello windows bat
请按任意键继续. . .
修正二:
[Plain Text] 纯文本查看 复制代码 @echo off
setlocal enabledelayedexpansion
set VAR=www.groad.net
if "%VAR%" == "www.groad.net" (
set VAR=www.google.com
echo !VAR!
)
pause
运行输出:www.google.com
请按任意键继续. . .
修正三:
[Plain Text] 纯文本查看 复制代码 @echo off
setlocal enabledelayedexpansion
set VAR=www.groad.net & echo !VAR!
pause
运行输出:www.groad.net
请按任意键继续. . .
小结:
可引发预处理行为的地方是在:if, else, for, &, &&, |, || 等命令或符号所连接起来的复合语句中。阻止预处理副作用的方法是使用延迟变量。需要注意的是,延迟变量扩展功能在默认情况下是非使能的,在批处理脚本里使用 setlocal enabledelayedexpansion 语句也只能本脚本里的延迟变量扩展功能。想默认启动该功能,需要修改注册表,至于如何修改,可以运行 cmd /? 帮组命令查看,那里有详细介绍。
除了上面使用的标准延迟变量方法外,还可以使用 call 命令的特性,使预处理机制触发两次,这样也能达到延迟变量的目的,下面举例说明。
我们再次用 call 命令来改变上面的程序:
call 方法修正一:
[Plain Text] 纯文本查看 复制代码 @echo off
set VAR=www.groad.net & call echo %%VAR%%
pause
call 方法修正二:
[Plain Text] 纯文本查看 复制代码 @echo off
set VAR=www.groad.net
if "%VAR%" == "www.groad.net" (
set VAR=www.google.com
call echo %%VAR%%
)
pause
call 方法修正三:
[Plain Text] 纯文本查看 复制代码 @echo off
for /f "tokens=* delims=" %%i in ("hello windows bat") do (
set VAR=%%i
call echo %%VAR%%
)
pause
使用 call 命令来达到变量延迟的目的需要注意的是,变量名两边分别用两个百分号括起来的,而不是一个百分号。
在一般应用中,call 是用来调用标号(子函数),和其它的批处理程序的。不管是标号下面的指令,还是其它批处理中的程序,实际上都是一条条的指令。因此,直接用 call 来调用单条命令也是可以的,比如上面的:其中 echo 是一个命令,而 %%VAR%% 则是 echo 的一个参数 --- 在程序设计中,编译器往往喜欢对函数的某些参数(如该参数是个宏)进行预处理的。同理,这里 call 所调用命令的情况类似于此,它同样会对参数进行预处理。如果参数加了两个百分符号,那么就会被预处理两次,这样也会产生变量延迟的效果。
以“call 方法修正二”里的程序片段为例分析一下这个二次预处理的过程:
首先是解析器的“全局笼统性”的预处理,它的做法是先去掉 VAR 左右最外围的两个百分号,这时剩下 %VAR% ,此时解析器暂时“不再认识”%VAR% 这种形式。因为它的本意是想去掉两个百分号后,可以看到 VAR 的真容,然后去找个值来将其替换,怎料到此时这个 VAR 竟然还穿了一层马甲(还有两个百分号括着),解析器就就没法继续深究下去,然而它并不会认为这样是一个错误;因为如果语法正确的话,最后大不了输出一个空值。
经过解析器这么处理一遍后,程序开始真正执行,也就是说,程序中的 call 语句变成了 "call echo %VAR%" 。这样一来,当执行到 call 时,call 会对 %VAR% 再一次进行预处理,目的就是让 echo 得到一个明确的参数,call 的做法是就近拿一个被设置了的 VAR 值替换 %VAR%,因此就变成了 call echo www.google.com 。可以在 set VAR=www.google.com 这句底下加一条 set VAR=www.baidu.com,那么程序最后输出的是 www.baidu.com 。 |