变量延迟详解,什么情况下该使用变量延迟?
今天在网上的中国dos联盟论坛上看到关于变量延迟的说明,感觉写的比较好,下面是部分摘抄!
---------------------------------------------------------------------------------------
namejm『楼 主』: 什么情况下该使用变量延迟?
请问在什么情况下应该使用变量延迟?
格式是什么样的?
变量延迟又有什么作用呢?
---------------------------------------------------------------------------------------
willsort『第 2 楼』:
Re namejm:
关于环境变量延迟扩展,使用set /?可以查看到部分说明,不过考虑到其粗劣的翻译水平,建议在查看之前,首先chcp 437切换为英文查看原英文说明。鉴于文中已说得十分详尽,而且有数个代码示例,应该不难理解。在此仅略作一些补充。
在许多可见的官方文档中,均将使用一对百分号闭合环境变量以完成对其值的替换行为称之为“扩展(expansion)”,这其实是一个第一方的概念,是从命令解释器的角度进行称谓的,而从我们使用者的角度来看,则可以将它看作是引用(Reference)、调用(Call)或者获取(Get)。
而命令解释器是扩展环境变量的行为大致如下:首先读取命令行的一条完整语句,在进行一些先期的预处理之后,命令被解释执行之前,会对其中用百分号闭合的字符串进行匹配,如果在环境空间中找到了与字符串相匹配的环境变量,则用其值替换掉原字符串及百分号本身,如果未得到匹配,则用一个空串替换,这个过程就是环境变量的“扩展”,它仍然属于命令行的预处理范畴。
而一条“完整的语句”,在NT的命令解释器CMD中被解释为“for if else”等含有语句块的语句和用“& | && ||”等连接起来的复合语句。
因此,当CMD读取for语句时,其后用一对圆扩号闭合的所有语句将一同读取,并完成必要的预处理工作,这其中就包括环境变量的扩展,所以在for中的所有语句执行之前,所有的环境变量都已经被替换为for之前所设定的值,从而成为一个字符串常量,而不再是变量。无论在for中将那些环境变量如何修改,真正受到影响的只是环境变量空间,而非for语句内部。
而为了能够在for语句内部感知环境变量的动态变化,CMD设计了延迟的环境变量扩展特性,也就是说,当CMD读取了一条完整的语句之后,它不会立即执行变量的扩展行为,而会在某个单条语句执行之前再进行扩展,也就是说,这个扩展行为被“延迟”了。
延迟环境变量扩展特性在CMD中缺省是关闭的,开启它的方法目前有两个:一是CMD /v:off(此处说法有误,应为 CMD /v:on——namejm 注),它会打开一个新的命令行外壳,在使用exit退出这个外壳之前,扩展特性始终有效,常用于命令行环境中;二是setlocal EnableDelayedExpansion,它会使环境变量的修改限制到局部空间中,在endlocal之后,扩展特性和之前对环境变量的修改将一同消失,常用于批处理语句中。
出处:https://www.cn-dos.net/forum/viewthread.php?tid=20733
=======================================================================================
变量延迟详解(新手推荐)
因工作关系一直没有太多时间泡在论坛上,每次上本论坛都很匆忙,注册几个月以来第一次发贴(因为本人太菜,不敢发帖),真有点不好意思!希望大家多多支持!本帖只“照顾”新手,老鸟略过,哈!
以下是偶根据论坛内的帖子得出的结论,如有错漏敬请指正!
首先要特别感谢willsort老大写的这帖子,偶是从中得到启发的!
http://www.cn-dos.net/forum/viewthread.php?tid=20733
下面在讨论过程中,偶会插入一些“废话”,如果你不喜欢看我写的“废话”,可以跳过。它与本文讨论的中心完全无关!不过偶还是建议大家看看,哈!
这段可跳过:
同学们上课啦,第一天来这里任教,必须先做个自我介绍。偶叫金城武,啊啊~~~~~~不,不是,一时口快说错了, 偶姓贾,不好意思!由于近日忙于研究“鬼武者”,所以忘了自己的姓氏!什么,说我现在才玩鬼武者,哎!没办法啊,偶穷啊,口袋里总是掏不起这几块钱买啊。“几块钱?”众人议论纷纷。什么,又说我买盗版,啊~~啊~~~这~~这不在本文的讨论范围啊~~~~~说完台下众人举起砖头……^_^
这是正文不可跳过:
willsort老大上面的帖子,对于新手来说比较难理解。不过没关系,我们先分析一个例子,同样是引用willsort老大的。本例启用了变量延迟,是个正确的例子!
例1:
for /f "tokens=* delims=" %%i in ("Hello world.") do (
set n=%%i
set n=!n:ld.=t!
set n=!n:o w= S!
set n=!n:He=Wi!
echo !n!
)
pause
1.@echo off & setlocal EnableDelayedExpansion
关闭命令回显,并启用变量延迟
2.for /f "tokens=* delims=" %%i in ("Hello world.") do (
for命令及其参数的使用,请大家在论坛里搜索相关字眼。限于篇幅问题,这里不作讨论。如果此时你不明白它的意思,那么你就当它的作用是把字符串“Hello world.”赋值给%%i好了,当然这只是权宜之计,以后一定要学习for的使用!
3.set n=%%i
把%%i的值(即Hello world.)赋予给变量n,这个大家都知道吧
4.set n=!n:ld.=t!
这里要讲讲set替换字符的功能了。这个语句的意思是,先获取变量n的值(此时n的值是“Hello world.”),然后将字符“t”替换字符“ld.”,然后再将替换后的结果再次赋值给变量n(此时n的值变为“Hello wort”)。至于set替换字符的编写格式,大家可以在CMD键入“set/?”找到“%PATH:str1=str2%”这段有说明
5.set n=!n:o w= S!
意思和上句一样,只是替换和被替换的内容不同。它是将“ S”替换“o w”(注意S前面和w前面都有个空格),其实willsort老大是想证明set替换字符是支持句点和空格的(第4句“ld”后面有个.)。此时n的值为“Hell Sort”
6.set n=!n:He=Wi!
这句不用说了吧,执行完这句后n的值为“Will Sort”
7.echo !n!
显示变量n的值
需要注意的是,一旦启用了变量延迟,就要用!号把变量括起来,而不能用%号。
好了,每句的意思已经说完了,下面要讲本帖真正要讨论的变量延迟的问题。
这里又要引用Will Sort老大的说明:当CMD读取for语句时,其后用一对圆括号闭合的所有语句将一同读取,并完成必要的预处理工作,这其中就包括环境变量的扩展,所以在for中的所有语句执行之前,所有的环境变量都已经被替换为for之前所设定的值,从而成为一个字符串常量,而不再是变量。
而为了能够在for语句内部感知环境变量的动态变化,CMD设计了延迟的环境变量扩展特性,也就是说,当CMD读取了一条完整的语句之后,它不会立即执行变量的扩展行为,而会在某个单条语句执行之前再进行扩展,也就是说,这个扩展行为被“延迟”了。
总的来说是,在没有启用变量延迟的情况下,凡是在括号内(即do里面)的变量,在执行for语句之前,就已经被替换成for语句之前其它命令对该变量所赋予的值。这句话不懂没关系,下面再看一个例子,看完你就会明白。
例2:
for /f "tokens=* delims=" %%i in ("Hello world.") do (
set n=%%i
set n=%n:ld.=t%
set n=%n:o w= S%
set n=%n:He=Wi%
echo %n%
)
pause
为什么会这样呢?原因是,在没有启用变量延迟的情况下,凡是在括号内(即do里面)的变量,在执行for语句之前,就已经被替换成for语句之前其它命令对该变量所赋予的值。
则是说在本例中的以下几句
set n=%%i
set n=%n:ld.=t%
set n=%n:o w= S%
set n=%n:He=Wi%
echo %n%
第一句能正常执行并达到它的目的,因为它只是单纯地将%%i的值赋予给变量n,所以没有任何问题。其它几句属这样情况:早在for语句执行前,CMD就急不切待地将这几句里面的所有变量n一同执行替换行为,替换为for之前,其它命令对n所设置的值,从而使n变成一个常量。但在本例中,for语句之前只有@echo off这句,并没有其它命令对n作过任何赋值行为,所以在for之前,变量n的值为空值。即是说,set n=%n:ld.=t% 这句里面的变量n,在CMD读取(注意是读取不是执行)完整个for语句后(这时还未轮到set执行自己的任务),就立刻被替换为一个空值,一个空值里面没有任何东西,所以就不存在一字符替换另一字符这种说法(没有东西怎么替换?)。最终到执行set n=%n:ld.=t%语句时,它只是获取一个空值,再给变量n赋予空值而已。其它几句也是一样原理。
所以,最后echo %n%的时候变量n还是个空值,而echo命令没有东西可以显示,就只有显示“ECHO 处于关闭状态”这句来说明自己的状态
通过这个例子的说明,相信大家已经知道变量延迟的作用吧!我们再回头来看看例1。
启用变量延迟后,在执行
set n=!n:ld.=t!
set n=!n:o w= S!
set n=!n:He=Wi!
echo !n!
这些语句前,它们里面的变量n不会马上被CMD替换(启用延迟后,CMD变得有耐性啦^_^),而未被替换的话,那么n就还是变量,而不是常量。等到执行set n=!n:ld.=t!等这几句时,变量n才被替换。这样每个set命令都能感知变量n的任何变化,从而作出正确的替换行为。这就是变量延迟啦!
可跳过:
什么,说我讲得不好?没办法啊,因为偶太菜啊,只知道这些。偶只是淘两顿饭吃而已,望大家谅解啊,不要再拿砖头砸偶。。。不然偶就~~~~~~~~~~叫救命!^_^
这是正文不可跳过:
不要以为只有for才要用变量延迟,下面这个例子同样需要
例3:这是个错误的例子
set mm=girl&echo %mm%
pause
原因是没有启用延迟,而且在set mm=girl&echo %mm%语句前没有其它命令对mm进行赋值。这时当CMD执行set mm=girl&echo %mm%语句前,就已经急不切待地把变量mm的值替换了,而又因为前面没给mm赋值,所以mm被替换为空值,变成常量。等到echo命令执行时,它其实是echo一个不会变化的常量,本例中即是空值。
有人会问,echo前面不是给mm赋值了吗?
这个就要关系到CMD解释命令的步骤,大家可以参详本帖开头willsort的帖子。
总的来说是,如果不启用变量延迟,在本例中,echo是不会理会也不会知道,它前面(指同一行语句)是否有其它命令给mm赋值。它只会从set mm=girl&echo %mm%这句以上的语句中获取它所要显示的变量的内容,也就是说,上一行或上几行的命令将mm设置成什么值,echo命令就显示什么值。
大家这样做就明白了:
set mm=boy
set mm=girl&echo %mm%
pause
这样编写例3才正确:
set mm=girl&echo !mm!
pause
好了全篇完了,下课!
突然,门外传来几只“恐龙”的嚎叫声:把那小子抓回去,胆敢趁着节日咱们游山玩水的时候偷走!!
2分钟后,“恐龙战队”押着这小子来到一个美丽壮观的“城堡”面前,正门上方写着“XX疯人院”,哈哈!
愿天下美女妇女节快乐,一天比一天美(包括在浏览本贴的你)!!---汗,这里有女同胞吗??有的请举手!呵呵!39们也一起感受节日的气氛吧!
这帖本来是想上午发的,但因工作关系,到现在才有空,无奈啊!
以上这些“废话”只是想令大家阅读这贴时能增添几分气氛,增加大家的阅读兴趣,令大家在学习过程中轻松轻松而已。如有得罪,敬请批评指正!
出处:http://www.cn-dos.net/forum/viewthread.php?tid=28273
=======================================================================================
变量延迟(setlocal)之浅见
变量延迟,浅见认为就是变量预处理,在事先声明变量,告诉cmd环境哪个先哪个后。默认情况下是停用,可以用两种方法启用/停用:
一、cmd /v:on 和cmd /v:off ,范围在cmd这个环境直至exit 出现退出cmd
二、setlocal enabledelayedexpansion和setlocal disabledelayedexpansion范围在批处理文件范围内,直至endlocal出现中止.
先看看官方帮助set /?后以几个批处理代码注释解释。
=======================================================================================
考虑到读取一行文本时所遇到的目前扩充的限制时,延迟环境变量扩充是很有用的,而不是执行的时候。以下例子说明直接变量扩充的问题:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "%VAR%" == "after" @echo If you see this, it worked
)
不会显示消息,因为在读到第一个 IF 语句时,BOTH IF 语句中的 %VAR% 会被代替;原因是: 它包含 IF 的文体,IF 是一个复合语句。所以,复合语句中的 IF 实际上是在比较 "before" 和"after",这两者永远不会相等。同样,以下这个例子也不会达到预期效果:
set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%
原因是,它不会在目前的目录中建立一个文件列表,而只是将LIST 变量设成找到的最后一个文件。这也是因为 %LIST% 在FOR 语句被读取时,只被扩充了一次;而且,那时的 LIST 变量是空的。因此,我们真正执行的 FOR 循环是:
for %i in (*) do set LIST= %i
这个循环继续将 LIST 设成找到的最后一个文件。延迟环境变量扩充允许您使用一个不同的字符(惊叹号)在执行时间扩充环境变量。如果延迟的变量扩充被启用,可以将上面例子写成以下所示,以达到预期效果:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "!VAR!" == "after" @echo If you see this, it worked
)
set LIST=
for %i in (*) do set LIST=!LIST! %i
echo %LIST%
如果命令扩展名被启用,有几个动态环境变量可以被扩展,但不会出现在 SET 显示的变量列表中。每次变量数值被扩展时,这些变量数值都会被动态计算。如果用户用这些名称中任何一个定义变量,那个定义会替代下面描述的动态定义:
%CD% - 扩展到当前目录字符串。
%DATE% - 用跟 DATE 命令同样的格式扩展到当前日期。
%TIME% - 用跟 TIME 命令同样的格式扩展到当前时间。
%RANDOM% - 扩展到 0 和 32767 之间的任意十进制数字。
%ERRORLEVEL% - 扩展到当前 ERRORLEVEL 数值。
%CMDEXTVERSION% - 扩展到当前命令处理器扩展名版本号。
%CMDCMDLINE% - 扩展到调用命令处理器的原始命令行。
---------------------------------------------------------------------------------------
开始批处理文件中环境改动的本地化操作。在执行 SETLOCAL 之后所做的环境改动只限于批处理文件。要还原原先的设置,必须执行 ENDLOCAL。达到批处理文件结尾时,对于该批处理文件的每个尚未执行的 SETLOCAL 命令,都会有一个隐含的 ENDLOCAL 被执行。
SETLOCAL
如果命令扩展名被启用,SETLOCAL 会如下改变:
SETLOCAL 批命令现在可以接受可选参数:
ENABLEEXTENSIONS / DISABLEEXTENSIONS
启动或停用命令处理器扩展名。详细信息,请参阅 CMD /?。
ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION
启动或停用延缓环境变量扩展名。详细信息,请
参阅 SET /? 。
无论在 SETLOCAL 命令之前它们的设置是什么,这些修改会一直保留到匹配的 ENDLOCAL 命令。如果有一个参数,SETLOCAL 命令将设置 ERRORLEVEL 的值。如果有两个有效参数中的一个,该值则为零。用下列技巧,您可以在批脚本中使用这个来决定扩展名是否可用:
VERIFY OTHER 2>nul
SETLOCAL ENABLEEXTENSIONS
IF ERRORLEVEL 1 echo Unable to enable extensions
这个方法之所以有效,是因为在 CMD.EXE 的旧版本上,SETLOCAL不设置 ERRORLEVEL 值。具有不正确参数的 VERIFY 命令将ERRORLEVEL 值初始化成非零值。
=======================================================================================
官方解释有些让人犯迷糊,以下几个代码注释部分来解释变量延迟,纰漏之处,请高手斧正.
=======================================================================================
@echo off&setlocal enabledelayedexpansion
::第一方演示变量延迟,当输出if you ..延迟启动
::var重复赋值,第一个%var%取的是before
::第二个!var!取的是after,若不开启延迟变量
::不显示因!VAR!取的是before
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "!VAR!" == "after" @echo If you see this, it worked
)
pause>nul
---------------------------------------------------------------------------------------
@echo off&setlocal enabledelayedexpansion
:: 演示变量延迟,用setlocal disabledelayedexpansion
:: 和setlocal enabledelayedexpansion 相互切换
:: echo %var% 取set var=fds 的值,而echo !var!
:: 取for复合语句中set var=%%i的值;%var%的值与变量延迟
:: setlocal开关无关,但!var!的值与之相关。当变量延迟
:: 处于开状态则取%%i的赋值,反之则取!var!本身.
set var=fds
for /l %%i in (1,1,6) do (
set var=%%i
echo %var%
echo !var!
)
pause>nul&exit
---------------------------------------------------------------------------------------
@echo off&setlocal enabledelayedexpansion
::变量里套变量延迟演示
set a=40000
set b=df
set a%b%=70000
set c=!a%b%!
echo %c%
pause>nul
---------------------------------------------------------------------------------------
@echo off&setlocal enabledelayedexpansion
::显示2个随机数并截取各数字求和
mode con:cols=40 lines=20 1>nul
color a1
set %random%=%random%
for /f "delims=" %%i in ('set') do set num=%%i&call echo %%num%%&goto be
:be
for /f "tokens=1,2 delims==" %%j in ("%num%") do (
set/a a=%%j
set/a b=%%k
)
for /f %%l in ("%a%%b%") do (
set/a a=%%l
set/a b=%%l
set/a a0=%a:~0,1%
set/a a1=%a:~1,1% 2>nul&set/a a1+=!a0!
set/a a2=%a:~2,1% 2>nul&set/a a2+=!a1!
set/a a3=%a:~3,1% 2>nul&set/a a3+=!a2!
set/a a4=%a:~4,1% 2>nul&set/a a4+=!a3!
set/a b0=%b:~0,1%
set/a b1=%b:~1,1% 2>nul&set/a b1+=!b0!
set/a b2=%b:~2,1% 2>nul&set/a b2+=!b1!
set/a b3=%b:~3,1% 2>nul&set/a b3+=!b2!
set/a b4=%b:~4,1% 2>nul&set/a b4+=!b3!
)
set/a b4+=%a4%
echo 和为:%b4%
pause>nul
出处:http://hi.baidu.com/jadych/item/f6b022a5048b30268819d330
=======================================================================================
以下内容为mq0036个人补充:
先来看个变量延迟的例子:
1 @echo off 2 3 setlocal 4 set aa=aaa:bbb 5 call :ck %aa% 6 echo return=%str% 7 call :ddd 8 goto :eof 9 10 11 12 :ck 13 setlocal EnableDelayedExpansion 14 if "a"=="a" (set str2=ErrorOperation) else ( 15 echo else 16 for /f "tokens=1,* delims=:" %%m in ("%~1") do ( 17 set str1=%%~m 18 if /I "!str1!"=="aaa" set str2=%%~n 19 ) 20 ) 21 endlocal & set str=%str2% 22 goto :eof 23 24 25 :ddd 26 echo %zzz% 27 set zzz=old 28 goto :eof
相信各位对上面的程序都能看懂,不过我这里还要写点废话解释解释,高手请跳过。
1.使用setlocal设置变量为局部变量,结束标志是endlocal,如果不是有endlocal则作用范围表示到当前文件结束.
2.变量延迟使用setlocal EnableDelayedExpansion和setlocal DisableDelayedExpansion,如果不使用setlocal DisableDelayedExpansion则作用范围表示到当前文件结束.
说明:DisableDelayedExpansion只是关闭变量延迟,也就是在这个语句后面的无法使用变量延迟的功能(后面还有讲解)
3.if语句后如果要执行多条语句需要把多条语句使用括号括起来,当使用else时,即使if后只有单条语句,也要括起来。
BAT语法认为括号中的语句就是一条语句。
程序说明:
说变量延迟就要说到局部变量,如果他们嵌套使用,各个变量的取值会怎么样?
第三行,设置局部变量
:ck子程序的功能,是利用延迟把aa变量中根据冒号(:)分割提取字符串,并且带回分割后的字符串
:ddd子程序则是为了验证程序前面的setlocal的嵌套的作用域
程序运行方式:把程序保存为bat文件,运行cmd并切换到程序所在目录,运行程序,如果修改了程序则关闭当前cmd窗口重新打开cmd。
验证一:
把第三行的setlocal去掉,然后多次运行程序,可以看出第一次执行时,echo %zzz%输出的是ECHO is off.后面再次执行的都输出了old字符串。
程序运行方式:把程序保存为bat文件,运行cmd并切换到程序所在目录,输入程序文件名运行
程序运行方式:把程序保存为bat文件,运行cmd并切换到程序所在目录,运行程序,如果修改了程序则关闭当前cmd窗口重新打开cmd。
---------------------------------------------------------------
验证二:
使用endlocal & set str=%str2%是为了把局部变量返回主调函数中
多次调用执行该文件,变量echo %zzz%中的值都是空
程序运行方式:把程序保存为bat文件,运行cmd并切换到程序所在目录,运行程序,如果修改了程序则关闭当前cmd窗口重新打开cmd。
---------------------------------------------------------------
验证三:
在BAT文件中的一部分是局部变量,一部分是全局变量。修改程序代码如下:
setlocal
echo %aa%
set aa=aaa:bbb
::endlocal
call :ddd
endlocal
goto :eof
:ddd
echo %zzz%
set zzz=old
goto :eof
通过改变endlocal的位置可以看到,在调用语句的后面,但在子程序的前面使用,则会起到局部变量的效果,当放到调用语句的前面则是全局变量的效果。这里测试的是在主程序中调用setlocal的功能,所以endlocal也必须在主程序中使用。
而且setlocal跟子程序有关,如果在子程序中使用setlocal,则在该子程序结束前的endlocal有效,如果不写endlocal则默认作用域到子程序结束,也就是在子程序中使用setlocal则作用域不会超出子程序。看下面的示例:
@echo off
echo %aa%
set aa=aaa:bbb
call :bbb
call :ddd
goto :eof
:bbb
setlocal
echo %zzz%
set zzz=old
::endlocal
echo %xxx%
set xxx=xxx
goto :eof
:ddd
echo %y%
set y=yyyy
运行以上程序,把:bbb子程序中的endlocal启用和禁用,可以查看变量%zzz%和%xxx%的值,
程序运行方式:把程序保存为bat文件,运行cmd并切换到程序所在目录,运行程序,如果修改了程序则关闭当前cmd窗口重新打开cmd。
---------------------------------------------------------------
验证四:
把if "a"=="a" (set str2=ErrorOperation)...... 这个语句中的括号拿掉,不管条件是否成立,"a"=="a"或"a"=="b",则都会执行else里的语句
程序运行方式:把程序保存为bat文件,运行cmd并切换到程序所在目录,运行程序,如果修改了程序则关闭当前cmd窗口重新打开cmd。
---------------------------------------------------------------
验证五:
延迟的启用与禁用,修改ck子程序,如下:
:ck
setlocal EnableDelayedExpansion
if "a"=="b" (set str2=ErrorOperation) else (
echo else
for /f "tokens=1,* delims=:" %%m in ("%~1") do (
set str1=%%~m
if /I "!str1!"=="aaa" set str2=%%~n
)
)
::setlocal DisableDelayedExpansion
for /l %%i in (1,1,3) do (set num=%%i
echo !num!)
setlocal DisableDelayedExpansion
goto :eof
把setlocal DisableDelayedExpansion放到for语句之前和之后的区别,你自己可以测试看看效果,从这里个程序可以看到启用和禁用的区别。
程序运行方式:把程序保存为bat文件,运行cmd并切换到程序所在目录,运行程序,如果修改了程序则关闭当前cmd窗口重新打开cmd。
---------------------------------------------------------------
关注我】。(●'◡'●)
如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的【因为,我的写作热情也离不开您的肯定与支持,感谢您的阅读,我是【Jack_孟】!
本文来自博客园,作者:jack_Meng,转载请注明原文链接:https://www.cnblogs.com/mq0036/p/3478108.html
【免责声明】本文来自源于网络,如涉及版权或侵权问题,请及时联系我们,我们将第一时间删除或更改!
posted on 2013-12-17 11:30 jack_Meng 阅读(6541) 评论(0) 编辑 收藏 举报