曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 3621|回复: 0
打印 上一主题 下一主题

[语法] 延迟变量详解

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34397
跳转到指定楼层
楼主
发表于 2012-12-15 20:48:56 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
延迟变量是和预处理有关的东西。

所谓预处理,就是预先处理,这一行为可能会提高程序的执行效率,但同时也容易带来副作用,在这种情况下,预处理就是自作聪明了。

看一个预处理副作用的简单例子:
[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 来调用单条命令也是可以的,比如上面的:
call echo %%VAR%%
其中 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
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2025-6-18 05:42 , Processed in 0.082823 second(s), 23 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表