中国DOS联盟论坛

中国DOS联盟

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

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

游客:  注册 | 登录 | 命令行 | 会员 | 搜索 | 上传 | 帮助 »
中国DOS联盟论坛 » DOS批处理 & 脚本技术(批处理室) » [原创]变量扩展及call命令的“变量延迟”原理详析
作者:
标题: [原创]变量扩展及call命令的“变量延迟”原理详析 上一主题 | 下一主题
q8249014
初级用户





积分 175
发帖 45
注册 2007-8-4
状态 离线
『楼 主』:  [原创]变量扩展及call命令的“变量延迟”原理详析

批处理学习帖① —— 变量扩展及call命令的“变量延迟”原理详析

前言:

本系列教程适合有一定批处理基础的同学学习,如果我下文的内容你看不懂,说明你的基

础较差,建议你在论坛潜水一段时间,翻看老贴并仔细实践,回来后相信你一定会看懂的。

下面我们就一起来系统的学习一下批处理的变量扩展和call命令的“变量延迟”

注意:如果没有特别说明,批处理学习帖中讲解的内容都是在批处理环境中的

一、变量扩展

变量是怎样进行扩展的呢?
@echo off
:: 代码1
set "var=test"
echo.%var%
echo.%%var%
echo.%%var%%
echo.%%%var%%%
echo.%%%var%%%%
pause


  Quote:
[代码1 执行结果]
test
%var
%var%
%test%
%test%
请按任意键继续. . .

请你先执行一下代码1

  Quote:
(变量扩展的“替换步骤”)

  1.变量扩展

    (1)当cmd读取到使用“%”引用的变量语句后,首先检查“var”左边“%”的数量是否为奇数,
       是则替换“%var%”为变量的值,偶数则不进行替换;如果变量未被定义则用空串替换。
      

  2.脱百分号

    (1)当“%var%”被替换为变量的值后,那么两边的百分号自然都会减少一个
    (2)如果单侧百分号的数量是偶数,那么直接将百分号(%)脱掉一半
       如果单侧百分号的数量是奇数,那么会脱掉百分号数量的一半+0.5[或者可以说是(百分号数量+1)÷2]
       如果没有百分号就不脱啦

下面就用代码1中的最后一句代码来给同学们分析一下(echo.%%%var%%%%)

    (1)变量扩展:因为“var”的左边有三个百分号,3为奇数,所以将“%var%”替换为“test”
       那么这时“%%%var%%%%”变为“%%test%%%”
    (2)脱百分号:因为这时“test”左侧有两个百分号,所以直接脱掉一半,
       而右侧有三个百分号所以脱掉(3÷2+0.5)2个百分号,所以最后结果就变为“%test%”


(变量扩展的“替换步骤”)中叙述的可能有些抽象,但是大家实践下再结合“(echo.%%%var%%%%)”的分析应该就明白了


看到这里同学们应该能明白环境变量扩展的过程了,但是有一点必须向同学们说明白,以上所说的“替换步骤”

只是为了让新同学能够更好的理解环境变量扩展的过程,而在批处理中命令解释器是不会按照上面的那一套规

则来扩展变量的,但是同学们也不用担心,虽然处理的方式不同但是结果都是一样的。

下面将从命令解释器解释“%”“变量标识符”的角度来给同学们讲解一下变量扩展的过程

    “%”字符在批处理中被解释为“变量标识符”,那么在批处理中“%”就属于一个特殊符号了,从预处理的

    角度来讲,命令解释器在读取完毕一条完整的批处理语句后(“%var%”)首先对变量进行扩展,那么在这

    个时候语句中所有的“%”都被当作“变量标识符”解释了,变量扩展完毕后所有的“%”也就不存在了,然

    而在这个时候命令解释器还没有对“^”转义字符进行解析,那么我要输出“%”怎么办?我们只想输出“%var%”

    而不想让它被解释怎么办?那就要想办法取消“%”的特殊性,让命令解释器不对它进行处理,那么在这

    个时候自然就需要一个字符来对“%”进行转义处理,这个字符是什么呢?其实它还是“%”本身,所以

    ,“%”字符在批处理中的第二个身份就是“转义符”。注意:“%”只会对“%”进行转义,有别与“^”。

下面还是用代码1中的“echo.%%%var%%%%”来分析一下

    1.echo.后面的第一个“%”对第二个“%”进行转义处理,这时第二个“%”变为普通字符,命令解释
      器不在对它进行解释,而第一个“%”被命令解释器解释为“转义符”,所以不存在了。 [%]
    2.echo.后面的第三个“%”和var还有第四个“%”组成一个完整的变量引用,所以“var”被替换为
      “test”,对“var”进行引用的两个“%”被命令解释器解释为“变量标识符”,所以不存在了。 [test]
    3.同[1.]echo.后面的第五个“%”对第六个“%”进行转义处理,这时第六个“%”变为普通字符,命
      令解器不在对它进行解释,这时第五个“%”就不存在了。 [%]
    4.最后一个也就是第七个“%”,命令解释器将它解释为“变量标识符”,所以不存在了
    5.这时候对“%”的处理全部进行完毕,我们将前四步的结果组合起来,[1.]% [2.]test [3.]%,最后
      的结果就变为:%test%

根据以上的分析过程,可以看出当两个“%”在一起的时候,前一个“%”被命令解释器解释为“转义符”,后一个

“%”被前一个“%”转义成普通字符了,并遵循从左向右的原则。

不知道上面解释大家能不能看懂,即使看不懂也没关系,只要 (变量扩展的“替换步骤”) 中的内容能明白就可以了,

因为 (变量扩展的“替换步骤”) 容易理解一些,所以下文及以后的学习帖中任然采用“替换步骤”来进行讲解。

(变量扩展的“替换步骤”)中的步骤是通过变量扩展的结果而总结出来的规律,新同学更容易理解一些,但要从本质

上理解原因,还是要理解上面的分析,不然你就会问,为什么奇数扩展而偶数不扩展,呵呵。

注:由于“%”的双重特性,预处理后自然有一些“%”不存在了,所以也有人叫它“逃逸字符”或者“脱字符”。


“原创文章,转帖请注明出处cn-dos&q8249014哦”


二、call命令的“变量延迟”

我们知道命令在执行之前 命令解释器会对命令语句进行一些必要的解释处理,这个过程我们称

之为“预处理工作”,第一段中所讲的“变量扩展”就属于预处理工作的一部分,下面先看一下

和预处理有关的一些知识。

__________________________________________________________________________
在这里首先给同学们简要的说明一下什么是“预处理工作”

就我个人理解预处理就是命令解释器对命令进行解释、匹配处理的一个过程,它进行于命令执行前

那么“预处理工作”到底做了些什么呢?
  
  (1)首先对使用“%”引用的变量进行扩展
  (2)对命令中的字符进行匹配解释,如把“&”匹配为命令连接符
  Rem (3)如果包含if(for)命令,则对if(for)命令的格式进行检查
  Rem (4)如果包含if(for)命令,待if(for)命令语句中的所有变量都扩展完毕后进行条件处理,
         然后把满足条件的命令语句提交给该命令主体执行
  (3)…………等待你的挖掘
  (4)进行延迟变量的扩展(会处理转义字符“^”)
  (5)预处理工作进行完毕后,把命令语句提交给命令主体执行

注:以上使用Rem注释的内容,不要求新同学们完全理解  批处理经验丰富的朋友应该能明白__________________________________________________________________________

为了便于同学们更好的理解下文中的两段代码,这里引用本论坛中willsort的一篇文章

  Quote:
文中仅做了一些补充修改,使解释更为准确,方便大家参阅

      关于环境变量延迟扩展,使用set /?可以查看到部分说明,不过考虑到其粗劣的翻译水平,建议在查看之
前,首先使用chcp 437切换为英文显示状态,在查看原英文说明。鉴于文中已说得十分详尽,并且有数个代码示
例,应该不难理解。在此仅略作一些补充。

      在许多可见的官方文档中,都使用一对百分号闭合环境变量以完成对其值的替换,这个过程称之为“扩展
(expansion)”,这其实是一个第一方的概念,是从命令解释器的角度进行称谓的,而从我们使用者的角度来
看,则可以将它看作是引用(Reference)、调用(Call)或者获取(Get)。

      命令解释器扩展环境变量的过程大致如下:首先读取命令行的一条完整语句,(命令被解释执行之前,会
进行一些预处理工作。)然后对其中使用百分号闭合的字符串进行匹配,如果在环境空间中找到了与字符串相匹
配的环境变量,则用其值替换掉原字符串及百分号本身,如果未得到匹配,则用一个空串替换。这个过程就是环
境变量的“扩展”,它属于命令行的预处理范畴。

      一条“完整的语句”,在NT的命令解释器CMD中被解释为“for、if else、()”等含有语句块的语句和用
“&、|、&&、||”等连接起来的复合语句。

      因此,当CMD读取for语句时,其后用一对圆扩号闭合的所有语句将一同被读取,并完成必要的预处理工作
,这其中就包括环境变量的扩展,所以在for中的所有语句执行之前,所有的环境变量都已经被替换为for之前所
设定的值了,从而成为一个字符串常量,而不再是变量。无论在for中将那些环境变量如何修改,真正受到影响
的只是环境变量空间,而非for语句内部。

      为了能够在for语句内部感知环境变量的动态变化,CMD设计了延迟环境变量扩展的特性,而为了扩展时与
使用百分号闭合的环境变量进行区分,开启延迟环境变量扩展后要使变量延迟扩展应使用英文感叹号闭合环境变
量。也就是说,当CMD读取了一条完整的语句之后,它不会立即扩展使用英文感叹号闭合的环境变量,而是在某
个单条语句执行之前再进行扩展,也就是说,这个扩展行为被“延迟”了。但使用百分号闭合的环境变量任会在
读取完毕一条完整语句后立即被扩展。

      延迟环境变量扩展特性在CMD中缺省是关闭的,开启它的方法目前有两个:一是CMD /v:on,它会打开一个
新的命令行外壳,在使用exit退出这个外壳之前,延迟扩展特性始终有效,常用于命令行环境中;二是
setlocal EnableDelayedExpansion,它会使环境变量的修改限制到局部空间中,在endlocal之后,延迟扩展特
性和之前对环境变量的修改将一同消失,常用于批处理语句中。


@echo off
::代码2
set "var=test"
for /l %%i in (1 1 1) do (
    set "var=cn-dos"
    echo.%var%
)
pause


  Quote:
[代码2 执行结果]
test
请按任意键继续. . .

请你先执行一下代码2

为什么代码2执行后的结果是“test”,而不是“cn-dos” 呢?

分析:
________________________________________________________________________
这是因为cmd读取for这一完整语句后就进行必要的预处理工作,所有能被扩展的变量都已被替换

为之前设定的值了,所以在代码2中执行到“echo.%var%”时实际执行的命令是“echo.test”。

(换句话说,在还没有执行“set "var=cn-dos"”的时候“%var%”就等于“test”了)
________________________________________________________________________
@echo off
::代码3
set "var=test"
for /l %%i in (1 1 1) do (
    set "var=cn-dos"
    call echo.%%var%%
)
pause


  Quote:
[代码3 执行结果]
cn-dos
请按任意键继续. . .

请你先执行一下代码3

用了call命令后变量扩展和不用call有什么区别呢?  (test&cn-dos)

代码3仅多了一个call,为什么结果就和代码2不一样呢?代码3为什么可以显示“cn-dos” 呢?

用call命令是如何实现“变量延迟”效果的呢?

分析:
_______________________________________________________________________________
因为call命令在处理命令语句时默认也会对变量进行扩展,所以用了call命令后便会多一次“替换步骤”

(一):读取完毕一条完整语句后,命令解释器进行预处理时对环境变量进行扩展的步骤

      1.变量扩展:“call echo.%%var%%”,因为“var”左边有两个百分号,所以变量不被替换
      2.脱百分号:因为“var”两侧的百分号都是偶数个,所以两侧的百分号都脱掉一半变为“%var%”

(二):命令语句提交给call命令后,call命令内部对环境变量进行扩展的步骤

      1.变量扩展:这时已经执行“set "var=cn-dos"”,var的值已经被定义为“cn-dos”,那么“%var%”将被
        扩展为“cn-dos”
      2.脱百分号:没有百分号所以不拖啦,所以结果就是“cn-dos”

注:以上的“替换步骤”和第一段中的 (变量扩展的“替换步骤”) 是一样的
_______________________________________________________________________________


以上使用call来进行变量延迟扩展的主要思路是:

    (1)在变量左侧使用偶数个百分号对变量进行引用,以便在预处理时变量不被扩展
    (2)命令语句提交给call命令后,call命令在处理命令语句时会再次对变量进行扩展,那么就达到了延迟扩展的目的

这里请新同学结合上面的分析 比较一下代码2和3的结果并弄懂原因 体会一下call命令的“变量延迟”的运用
这样有助于第三段实例演示的学习

“原创文章,转帖请注明出处cn-dos&q8249014哦”


三、实例演示

代码4是论坛中“terse”写的一段10进制转为16进制的代码,代码5是我进行“修整”后的一段代码

这段代码也是我所见过最短的一段10进制转为16进制的代码
@echo off&setlocal enabledelayedexpansion
:: 代码4
:: Code by CN-DOS terse
set "hx=0123456789ABCDEF"
for /l %%a in (1 1 10000) do (
    set /a str=%%a
    for /l %%i in (1 1 8) do (
        if !str! gtr 0 (
        set/a "n=str&15,str>>=4"
        for %%i in (!n!) do set h=!hx:~%%i,1!!h!
      ))
        echo %%a 的十六进制为:0x!h!&set "h="
)
pause

@echo off&setlocal enabledelayedexpansion
:: 代码5
:: Code by CN-DOS terse
set "hx=0123456789ABCDEF"
set/p str=:
(for /l %%i in (1 1 8) do (
     if !str! GTR 0 (
        set/a "n=str&15,str>>=4"
        call set h=%%hx:~!n!,1%%!h!
     )
)
if not defined h set "h=0"
echo.%str% 的十六进制为:0x!h!)
pause
代码4中变量截取那一段[for %%i in (!n!) do set h=!hx:~%%i,1!!h!]因为不能直接使用

“set h=!hx:~!n!,1!!h!”或“set h=%hx:~!n!,1%!h!”进行截取,所以他使用for把变量

“!n!”传递给%%i,然后在进行变量替换“set h=!hx:~%%i,1!!h!”。

而我“修整”的代码5中则充分利用了call命令的“变量延迟”,即:“call set h=%%hx:~!n!,1%%!h!”

这里只是讨论一下批处理技巧没有其他的意识,“terse”的代码还是很好的哟,呵呵


以上代码4和5同学们进行一下比较,再体会一下call“变量延迟”的运用,最好能完全读懂每一句代码的意识。

作业:弄懂代码5“echo.%str% 的十六进制为:0x!h!”中“str”的值为什么是你输入的数字而不是0

      “call set h=%%hx:~!n!,1%%!h!”自行分析一下这一句的执行过程


四、总结

    这里给同学们补充一下开启延迟环境变量后使用“!”引用的变量的“替换步骤”

      (1)读取完命令语句后,cmd不会立即扩展使用“!”引用的变量,
         使用“%”引用的变量任然使用第一段(变量扩展的“替换步骤”)中的规则进行扩展
      (2)进行预处理匹配工作 [进行预处理工作中的 (2) (3) …………]
      (3)在命令执行前对使用“!”引用的变量进行扩展[注意:这里只进行扩展,但是会处理转义字符]
         也就是说开启变量延迟后使用“!”引用的变量将在读取到该句后执行该句前的最后一步才进行扩展
         扩展的规则是:先检查变量是否被定义,是则扩展为其值,否则用空串替换,另外所有多余的“!”都
         会用空串进行替换(被解释为“变量标识符”)。如果是在cmd命令行中执行多余的“!”则不会被替换。

    在批处理学习帖③中给同学们总结一下开启变量延迟后使用“!”引用的变量扩展时怎样处理转义“^”字符


   “%”和“!”引用的变量扩展的区别:

      在读取完毕一个单条语句或复合语句后,语句中所有使用“%”引用的变量将会被扩展
      在读取完毕一个单条语句或复合语句后,语句中所有使用“!”引用的变量不会被扩展,
      而是在读取完该语句后执行该语句前的最后一步在进行扩展

    以上所说的“变量扩展”其实就是“变量替换”的意识,这样解释同学们可能容易理解一些


好这一课到这里就结束了


如果文中有什么不妥之处,敬请坛友们斧正哦

“原创文章,转帖请注明出处cn-dos&q8249014哦”

2009年12月1日 修订

[ Last edited by q8249014 on 2010-2-4 at 16:52 ]

   此帖被 +14 点积分      点击查看详情   
评分人:【 HAT 分数: +12  时间:2009-10-14 20:52
评分人:【 mqw624 分数: +1  时间:2010-4-17 15:08
评分人:【 btpg 分数: +1  时间:2010-5-29 23:54


2009-10-14 17:22
查看资料  发送邮件  发短消息 网志   编辑帖子  回复  引用回复
hgj9999
新手上路





积分 2
发帖 2
注册 2008-11-13
状态 离线
『第 2 楼』:  

这么好的帖子怎么没人回呢
对call的理解我还很初级啊,要继续学习

2009-11-30 10:26
查看资料  发送邮件  发短消息 网志   编辑帖子  回复  引用回复
heng520
初级用户





积分 29
发帖 16
注册 2005-11-8
状态 离线
『第 3 楼』:  

好祥细,很不错
还得继续学习啊。。

2009-11-30 20:08
查看资料  发送邮件  发短消息 网志   编辑帖子  回复  引用回复
wzc6063724
新手上路





积分 11
发帖 8
注册 2010-7-9
状态 离线
『第 4 楼』:  

学习了,这方面我一直不懂,今天受教了

2010-8-17 12:24
查看资料  发送邮件  发短消息 网志   编辑帖子  回复  引用回复
491321720xxw
新手上路





积分 3
发帖 2
注册 2010-8-27
来自 四川
状态 离线
『第 5 楼』:  

好好,有多懂老!!!!!!!!

2010-10-3 18:18
查看资料  发送邮件  发短消息 网志  OICQ (491321720)  编辑帖子  回复  引用回复
hfg1977
新手上路





积分 1
发帖 1
注册 2010-3-23
状态 离线
『第 6 楼』:  

1.学习了call 及"!"延时
2.获得了更好的D2H方法.

谢谢楼主.

2010-10-5 10:41
查看资料  发送邮件  发短消息 网志   编辑帖子  回复  引用回复

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


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



论坛跳转: