中国DOS联盟论坛

中国DOS联盟

-- 联合DOS 推动DOS 发展DOS --

联盟域名:www.cn-dos.net  论坛域名:www.cn-dos.net/forum
DOS,代表着自由开放与发展,我们努力起来,学习FreeDOS和Linux的自由开放与GNU精神,共同创造和发展美好的自由与GNU GPL世界吧!

游客:  注册 | 登录 | 命令行 | 会员 | 搜索 | 上传 | 帮助 »
作者:
标题: 整理延迟变量扩展介绍补充 上一主题 | 下一主题
ywwywwjm
初级用户





积分 42
发帖 13
注册 2007-8-3
状态 离线
『楼 主』:  整理延迟变量扩展介绍补充

引用变量的值在批处理中习惯叫变量扩展。
使用一对百分号括起变量名(如:%VAR%)的形式表示变量扩展。

延迟引用变量的值叫延迟变量扩展。
使用一对感叹号括起变量名(如:!VAR!)的形式表示延迟变量扩展。
是指变量扩展这个动作被推迟。

有了变量扩展。为什么又多出一个延迟变量扩展呢?

先看下面程序片段1:
set VAR=before
if "%VAR%" == "before" (
   set VAR=after
   echo %VAR%
)
运行结果是什么?
结果是:before
而正确逻辑结果应是:after

为什么会发生这样的事呢?
原来命令解释程序(cmd)在执行复合语句之前做了预处理,即读入复合语
句时做了变量扩展。对于程序片段1,命令解释程序读入if复合语句,接着
做VAR变量扩展,由于此时set VAR=after语句还没有执行,VAR变量的值
未被改变仍然是before,因此,%VAR%扩展为before ,echo %VAR%语句
变为echo before 。然后,命令解释程序从if语句的开头开始执行,很明显
改变VAR变量的值对echo before语句没有影响(因%VAR%已提前扩展为
before),执行结果是before 。

扩展后的程序片段1:
set VAR=before
if " before " == "before" (
   set VAR=after
   echo before
)

再看一个程序片段2:(给当前目录文件列表的每行加上序号)
set count=0
for %%i in (*) do (
   set /a count += 1
   echo %count% %%i
)
运行结果:所有序号都是0
不是正确的逻辑结果。

原因与程序片段1一样。命令解释程序读入for复合语句,接着做count变
量扩展,由于此时set /a count += 1语句还没有执行,count变量的值未被改
变仍然是0,因此,%count%扩展为0 ,echo %count% %%i语句变为echo 0
%%i 。然后,命令解释程序从for语句的开头开始执行,很明显改变count
变量的值对echo 0 %%i语句没有影响(因%count%已提前扩展为0),执行
结果的序号是0 。

扩展后的程序片段2:
set count=0
for %%i in (*) do (
   set /a count += 1
   echo 0 %%i
)

为了解决以上问题,命令解释程序增加了在运行时才进行变量扩展的功能
--"延迟变量扩展"。即引入延迟变量扩展的表示形式(使用一对感叹号括起
变量名,如:!VAR!),这里简称它为"延迟变量扩展表示式"。命令解释程序
在读入语句时,遇到"延迟变量扩展表示式",对表示式中的变量不做变量扩
展,而推迟到在执行时才做变量扩展。因此,在if、else、for和用"& | &&
||"等连接起来的复合语句中,如果在变量扩展前变量的值发生了变化,就
要将变量扩展改为延迟变量扩展,令变量扩展在执行时才进行,从而得到变
化了的变量值。

因此,要将以上程序片段中变量扩展改为延迟变量扩展,修改后的程序片段:
set VAR=before
if "!VAR!" == "before" (
   set VAR=after
   echo !VAR!
)

set count=0
for %%i in (*) do (
   set /a count += 1
   echo !count! %%i
)

但是,默认情况下,"延迟变量扩展"功能是停用的。
启用这项功能,有几种方法:
1. 运行cmd /v: on 进入命令提示符窗口,然后输入命令。
2. 直接运行cmd /v: on /c "commands"
3. 在批处理文件里使用这项功能之前设置:setlocal enabledelayedexpansion
4. 设置注册表,让cmd默认情况下启用这项功能,可参考命令:cmd/?

总结以上要点,将程序片段2完善后,得到以下批处理程序—myDir.bat:
:: 给当前目录文件列表的每行加上序号
@echo off
setlocal enabledelayedexpansion

set count=0
for %%i in (*) do (
   set /a count += 1
   echo !count! %%i
)

运行程序,结果正确。

如果不想为何时使用延迟变量扩展而烦恼,可以一开始就使用延迟变量扩展,
替代变量扩展。但是,迟变量扩展的自身嵌套或者和变量扩展相互嵌套使用,
也存在问题。

另外,对于"延迟变量扩展"问题,有另一巧妙的解决方法(叫call方法),将
call命令放在需要"延迟变量扩展"的语句前面,同时变量扩展要再用一对百分
号括起。这时call的作用相当于"延迟变量扩展"功能。其实call命令具有对
它后面命令做预处理和执行功能。
再看程序片段1:
set VAR=before
if "%VAR%" == "before" (
   set VAR=after
   echo %VAR%
)
改进后:
set VAR=before
if "%VAR%" == "before" (
   set VAR=after
   call echo %%VAR%%
)
运行结果是:after

为什么运行结果是:after 呢?
命令解释程序读入if复合语句,接着做预处理,即对%VAR%做变量扩展。
而对于%%VAR%%,是这样处理,当遇到连续二个百分号时,会去掉一个,
剩下一个只是字面意思而没有变量扩展功能意思,于是VAR也当作字面意
思。接着遇到后面连续二个百分号时,同样会去掉一个。预处理后,call命
令变为call echo %VAR%,然后,命令解释程序从if语句的开头开始执行,
执行到call命令时,call命令对echo %VAR%再做一次预处理,即对%VAR%
做变量扩展,由于之前VAR变量被赋予after,于是%VAR%扩展为after,接
着,执行echo after。所以,运行结果是:after。

总结:要注意复合语句中的变量扩展。如果在变量扩展前变量的值发生
了变化,就要将变量扩展改为具有"延迟变量扩展"功能的形式。

题外话:"延迟变量扩展"问题,是个害人的陷井,如马路上的沙井被拿去井
盖一样,行人一不小心就陷进去。让新手头大,让老手麻烦。这是cmd的设
计缺陷,将修补缺陷的工作留给用户(自行盖井盖)是极不负责任!

   此帖被 +11 点积分      点击查看详情   
评分人:【 wudixin96 分数: +3  时间:2007-8-8 21:34
评分人:【 26933062 分数: +8  时间:2007-8-8 21:57


2007-8-8 21:11
查看资料  发送邮件  发短消息 网志   编辑帖子  回复  引用回复
26933062
银牌会员





积分 2268
发帖 879
注册 2006-12-19
状态 离线
『第 2 楼』:  

写的很好,学习!
一开始就使用延迟变量扩展,替代变量扩展。
但是,迟变量扩展的自身嵌套或者和变量扩展相互嵌套使用,也存在问题。

请问存在什么问题?可以举一两个列子吗?
谢谢!!


2007-8-8 21:56
查看资料  发短消息 网志   编辑帖子  回复  引用回复
knoppix7
银牌会员





积分 1287
发帖 634
注册 2007-5-2
来自 cmd.exe
状态 离线
『第 3 楼』:  

迟变量扩展,有个问题就是当要作echo !!!!!!!XXXX!!!!!
要出错。
万一要处理的字符离含有!就XX了

2007-8-9 12:47
查看资料  发短消息 网志   编辑帖子  回复  引用回复
ywwywwjm
初级用户





积分 42
发帖 13
注册 2007-8-3
状态 离线
『第 4 楼』:  

"迟变量扩展的自身嵌套或者和变量扩展相互嵌套使用,也存在问题"

其实是想说: 延迟变量扩展做不到二次或多次扩展,只能做一次扩
展。

例:
@echo off
setlocal enabledelayedexpansion

set aaa=111
set bbb=^^!aaa^^!
echo !bbb!
call echo !bbb!

结果都是!aaa!

2007-8-9 21:23
查看资料  发送邮件  发短消息 网志   编辑帖子  回复  引用回复
ywwywwjm
初级用户





积分 42
发帖 13
注册 2007-8-3
状态 离线
『第 5 楼』:  整理延迟变量扩展介绍补充2

引用变量的值在批处理中习惯叫变量扩展。
使用一对百分号括起变量名(如:%VAR%)的形式表示变量扩展。
笔者称用一对百分号括起变量名的表示形式为变量扩展表示式。

延迟引用变量的值叫延迟变量扩展。是指变量扩展这个动作被推迟。
使用一对感叹号括起变量名(如:!VAR!)的形式表示延迟变量扩展。
笔者称用一对感叹号括起变量名的表示形式为延迟变量扩展表示式。

有了变量扩展。为什么又多出一个延迟变量扩展呢?

先看下面程序片段1:
set VAR=before
if "%VAR%" == "before" (
   set VAR=after
   echo %VAR%
)
执行结果是什么?
结果是:before
而正确逻辑结果应是:after

为什么会发生这样的事呢?
原来命令解释程序(cmd)在执行语句之前对语句做了预处理,即读入语句
时,对语句内的变量扩展表示式做了变量扩展。对于程序片段1,命令解释
程序读入if语句(完整一句),接着做VAR变量扩展,由于此时set VAR=after
语句还没有执行,VAR变量的值未被改变仍然是before。因此,%VAR%扩
展为before ,echo %VAR%语句变为echo before 。然后,命令解释程序从
if语句的开头开始执行,很明显改变VAR变量的值对echo before语句没有
影响(因%VAR%已提前扩展为before),执行结果是before 。

扩展后的程序片段1:
set VAR=before
if " before " == "before" (
   set VAR=after
   echo before
)

再看一个程序片段2:(给当前目录文件列表的每行加上序号)
set count=0
for %%i in (*) do (
   set /a count += 1
   echo %count% %%i
)
执行结果:所有行的序号都是0
不是正确的逻辑结果。

与程序片段1一样。命令解释程序读入for语句(完整一句),接着做count
变量扩展,由于此时set /a count += 1语句还没有执行,count变量的值未被
改变仍然是0。因此,%count%扩展为0 ,echo %count% %%i语句变为echo
0 %%i 。然后,命令解释程序从for语句的开头开始执行,很明显改变count
变量的值对echo 0 %%i语句没有影响(因%count%已提前扩展为0),执行
结果的序号是0 。

扩展后的程序片段2:
set count=0
for %%i in (*) do (
   set /a count += 1
   echo 0 %%i
)

再来一个程序片段3:
set VAR=before
set VAR=after & echo %VAR%

命令解释程序读入set VAR=after & echo %VAR%整句,接着做VAR变量
扩展……

大家会发现,问题都是发生在含有多过一条语句的if、for和用"&"连接起
来的语句内。因此,为了更好说明问题,现将含有多过一条语句的if、else、
for和用"& | && ||"等连接起来的语句统称为复合语句。

问题的原因:命令解释程序在执行复合语句之前,读入复合语句时,对复合
语句内的变量扩展表示式做了变量扩展。这是违背了顺序执行程序的原则。

为了解决以上问题,命令解释程序增加了在运行时才进行变量扩展的功能
--"延迟变量扩展"。即引入延迟变量扩展的表示形式(使用一对感叹号括起
变量名,如:!VAR!),笔者称它为"延迟变量扩展表示式"。命令解释程序
在读入语句时,遇到"延迟变量扩展表示式",对表示式中的变量不做变量扩
展,而推迟到在执行时才做变量扩展。因此,在复合语句内,如果在变量扩
展前变量的值发生了变化,就要将变量扩展改为延迟变量扩展,令变量扩展
在执行时才进行,从而得到变化了的变量值。

因此,要将以上程序片段中变量扩展改为延迟变量扩展,修改后的程序片段:
set VAR=before
if "!VAR!" == "before" (
   set VAR=after
   echo !VAR!
)

set count=0
for %%i in (*) do (
   set /a count += 1
   echo !count! %%i
)

set VAR=before
set VAR=after & echo !VAR!

但是,默认情况下,"延迟变量扩展"功能是停用的。
启用这项功能,有几种方法:
1. 运行cmd /v: on 进入命令提示符窗口。
2. 直接运行cmd /v: on /c "commands"
3. 在批处理文件里使用这项功能之前设置:setlocal enabledelayedexpansion
4. 设置注册表,让cmd默认情况下启用这项功能,可参考命令:cmd/?

综合以上要点,将程序片段2完善后,得到以下批处理程序—myDir.bat:
:: 给当前目录文件列表的每行加上序号
@echo off
setlocal enabledelayedexpansion

set count=0
for %%i in (*) do (
   set /a count += 1
   echo !count! %%i
)

执行程序,结果正确。

如果不想为如何使用延迟变量扩展而烦恼,可以一开始就使用延迟变量扩展,
替代变量扩展。但是,延迟变量扩展做不到二次或多次扩展,只能做一次扩
展。
例1:
@echo off
setlocal enabledelayedexpansion

set aaa=111
set bbb=^^!aaa^^!
echo !bbb!
call echo !bbb!

结果都是!aaa!

例2:
@echo off
setlocal enabledelayedexpansion

set bbb=111
echo !bbb!
call echo !!bbb!!

结果都是111

另外,对于"延迟变量扩展"问题,有另一巧妙的解决方法,即使用call命令
对变量进行二次扩展,将call命令放在需要"延迟变量扩展"的语句前面,同
时变量扩展要再用一对百分号括起。这时call的作用相当于"延迟变量扩展"
功能。其实call命令具有对它后面命令做预处理和执行功能。
再看程序片段1:
set VAR=before
if "%VAR%" == "before" (
   set VAR=after
   echo %VAR%
)
改进后:
set VAR=before
if "%VAR%" == "before" (
   set VAR=after
   call echo %%VAR%%
)
执行结果是:after

为什么执行结果是:after 呢?
命令解释程序读入if语句,接着做预处理,即对%VAR%做变量扩展。而对
于%%VAR%%,是这样处理,当遇到连续二个百分号时,会去掉一个,剩下
一个只是字面意思而没有变量扩展功能意思(第一个百分号相当于转义字
符),于是VAR也当作字面意思。接着遇到后面连续二个百分号时,同样会
去掉一个。预处理后,call命令变为call echo %VAR%,然后,命令解释程序
从if语句的开头开始执行,执行到call命令时,call命令对echo %VAR%再
做一次预处理,即对%VAR%做变量扩展,由于之前VAR变量被赋予after,
于是%VAR%扩展为after,接着,执行echo after。所以,执行结果是:after。

总结:要注意在复合语句内的变量扩展。如果在变量扩展前变量的值发生了
变化,就要将变量扩展改为具有"延迟变量扩展"功能的形式。令变量扩展在
执行时才进行,从而得到变化了的变量值。

题外话:"延迟变量扩展"问题,是个害人的陷阱,如马路上的沙井被拿去井
盖一样,行人一不小心就掉下去。让新手头大,让老手麻烦。这是命令解释
程序的设计缺陷,将修补缺陷的工作留给用户(自行盖井盖)是极不负责任!

[ Last edited by ywwywwjm on 2007-8-14 at 08:50 PM ]

2007-8-9 22:52
查看资料  发送邮件  发短消息 网志   编辑帖子  回复  引用回复

请注意:您目前尚未注册或登录,请您注册登录以使用论坛的各项功能,例如发表和回复帖子等。


可打印版本 | 推荐给朋友 | 订阅主题 | 收藏主题



论坛跳转: