变量延迟详解calltlocal
对于批处理新⼿⽽⾔,“变量延迟”这个概念很可能闻所未闻,但是,它却像⼀堵横亘在你前进道路上的⽆形⾼墙,你感受不到它的存在,但当你试图往前冲时,它会把你狠狠地弹回来,让你⽆法逾越、⽆功⽽返;⽽⼀旦找到了越过它的⽅法,你就会发现,在for的世界⾥,前⾯已经是⼀⽚坦途,⽽你对批处理的理解,⼜上升到了⼀个新的境界。
例如,你编写了这样⼀个代码:
升高中@echo off
t num=0&&echo %num%
pau
你的本意是想对变量num赋值之后,再把这个值显⽰出来,结果,显⽰出来的并不是0,⽽是显⽰:ECHO 处于关闭状态。
之所以会出错,是因为“变量延迟”这个家伙在作怪。
在讲解变量延迟之前,我们需要了解⼀下批处理的执⾏过程,它将有助于我们深⼊理解变量延迟。
批处理的执⾏过程是怎样的呢?
“⾃上⽽下,逐条执⾏”,我想,这个经典的说法⼤家都已经⽿熟能详了,没事的时候倒着念,也还别有⼀番古韵呢^_^,但是,我想问⼤家的是,⼤家真的深刻地理解了这句话的含义了吗?
“⾃上⽽下”,这⼀条和我们本节的讲解关系不⼤,暂时略过不说,后⼀条,“逐条执⾏”和变量延迟有着莫⼤的⼲系,它是我们本节要关注的重点。
很多⼈往往认为⼀⾏代码就是⼀条语句,从⽽把“逐条执⾏”与“逐⾏执⾏”等同起来,这就⼤错特错了。
莫⾮“逐条执⾏”⾥暗藏着⽞机?
正是如此。
“逐条”并不等同于“逐⾏”。这个“条”,是“⼀条完整的语句”的意思,并不是指“⼀⾏代码”。在批处理中,是不是⼀条完整的语句,并不是以⾏来论的,⽽是要看它的作⽤范围。
什么样的语句才算“⼀条完整的语句”呢?
世界主要港口 1、在复合语句中,整个复合语句是⼀条完整的语句,⽽⽆论这个复合语句占⽤了多少⾏的位置。常见的复合语句有:for 语句、if……el语句、⽤连接符&、||和&&连接的语句,⽤管道符号|连接的语句,以及⽤括号括起来的、由多条语句组合⽽成的语句块;
2、在⾮复合语句中,如果该语句占据了⼀⾏的位置,则该⾏代码为⼀条完整的语句。
例如:
@echo off
t num=0
for /f %%i in ('dir /a-d /b *.exe') do (
t /a num+=1
echo num 当前的值是 %num%
)
echo 当前⽬录下共有 %num% 个exe⽂件
dir /a-d /b *.txt|findstr "test">nul&&(
echo 存在含有 test 字符串的⽂本本件
)||echo 不存在含有 test 字符串的⽂本⽂件
if exist test.ini (
echo 存在 test.ini ⽂件
) el echo 不存在 test.ini ⽂件
pau
上⾯的代码共有14⾏,但是只有完整的语句只有7条,它们分别是:
第1条:第1⾏的echo语句;
第2条:第2⾏的t语句;
第3条:第3、4、5、6⾏上的for复合语句;
第4条:第7⾏的echo语句;
第5条:第8、9、10⾏上⽤&&和||连接的复合语句;
第6条:第11、12、13⾏上的if……el复合语句;
第7条:第14⾏上的pau语句。
在这⾥,我之所以要花这么长的篇幅来说明⼀⾏代码并不见得就是⼀条语句,是因为批处理的执⾏特点是“逐条”执⾏⽽不是“逐⾏”执⾏,澄清了这个误解,将会更加理解批处理的预处理机制。
在代码“逐条”执⾏的过程中,这个批处理解释器会对每条语句做⼀些预处理⼯作,这就是批处理中⼤名⿍⿍的“预处理机制”。
预处理的⼤致情形是这样的:⾸先,把⼀条完整的语句读⼊内存中(不管这条语句有多少⾏,它们都会被⼀起读⼊),然后,识别出哪些部分是命令关键字,哪些是开关、哪些是参数,哪些是变量引⽤……如果代码语法有误,则给出错误提⽰或退出批处理环境;如果顺利通过,接下来,就把该条语句中所有被引⽤的变量及变量两边的百分号对,⽤这条语句被读⼊内存之就已经赋予该变量的具体值来替换……当所有的预处理⼯作完成之后,批处理才会执⾏每条完整语句内部每个命令的原有功能。也就是说,如果命令语句中含有变量引⽤(变量及紧邻它左右的百分号对),并且某个变量的值在命
令的执⾏过程中被改变了,即使该条语句内部的其他地⽅也⽤到了这个变量,也不会⽤最新的值去替换它们,因为某条语句在被预处理的时候,所有的变量引⽤都已经被替换成字符串常量了,变量值在复合语句内部被改变,不会影响到语句内部的其他任何地⽅。
顺便说⼀下,运⾏代码[code20]之后,将在屏幕上显⽰当前⽬录下有多少个exe⽂件,是否存在含有 test 字符串的⽂本⽂件,以及是否存在 test.ini 这个⽂件等信息。让很多⼈百思不得其解的是:如果当前⽬录下存在exe⽂件,那么,有多少个exe ⽂件,屏幕上就会提⽰多少次 "num 当前的值是 0" ,⽽不是显⽰1到N(N是exe⽂件的个数)。
结合上⾯两个例⼦,我们再来分析⼀下,为什么这两段代码的执⾏结果和我们的期望有⼀些差距。
微信交易
在[code19]中,t num=0&&echo %num%是⼀条复合语句,它的含义是:把0赋予变量num,成功后,显⽰变量num的值。
虽然是在变量num被赋值成功后才显⽰变量num的值,但是,因为这是⼀条复合语句,在预处理的时候,&&后的%num%只能被t语句之前的语句赋予变量num的具体值来替换,⽽不能被复合语句内部、&&之前的t语句对num所赋予的值来替换,可见,此num⾮彼num。可是,在这条复合语句之前,我们并没有对变量num赋值,所以,&&之后的%num%是空值,相当于在&&之后只执⾏了 echo 这⼀命令,所以,会显⽰ echo 命令的当前状态,⽽不是显⽰变量num的值(虽然该变量的值被t语
句改变了)。
在[code20]中,for语句的含义是:列举当前⽬录下的exe⽂件,每发现⼀个exe⽂件,变量num的值就累加1,并显⽰变量num的值。
看了对[code19]的分析之后,再来分析[code20]就不再那么困难了:第3、4、5⾏上的代码共同构成了⼀条完整的for语句,⽽语句"echo num 当前的值是 %num%"与"t /a num+=1"同处复合语句for的内部,那么,第4⾏上t改变了num的值之后,并不能对第5⾏上的变量num有任何影响,因为在预处理阶段,第5⾏上的变量引⽤%num%已经被在for之前就赋予变量num的具体值替换掉了,它被替换成了0(是被第2⾏上的t语句赋予的)。
如果想让代码[code19]的执⾏结果中显⽰&&之前赋予num的值,让代码[code20]在列举exe⽂件的时候,从1到N地显⽰exe⽂件的数量,那⼜该怎么办呢?
对代码[code19],可以把⽤&&连接复合语句拆分为两条单独的语句,写成:
@echo off
t num=0
echo %num%
pau
但是,这不是我们这次想要的结果。
射手座和双鱼座配不配
对这两段代码都适⽤的办法是:使⽤变量延迟扩展语句,让变量的扩展⾏为延迟⼀下,从⽽获取我们想要的值。
在这⾥,我们先来充下电,看看“变量扩展”有是怎么⼀回事。
既然只要延迟变量的扩展⾏为,就可以获得我们想要的结果,那么,具体的做法⼜是怎样的呢?
⼀般说来,延迟变量的扩展⾏为,可以有如下选择:
23个声母表图片
1、在适当位置使⽤ tlocal enabledelayedexpansion 语句;
2、在适当的位置使⽤ call 语句。
使⽤ tlocal enabledelayedexpansion 语句,那么,[code19]和[code20]可以分别修改为:
@echo off
tlocal enabledelayedexpansion
t num=0&&echo !num!
pau复制内容到剪贴板代码:@echo off
t num=0
tlocal enabledelayedexpansion
for /f %%i in ('dir /a-d /b *.exe') do (
t /a num+=1
echo num 当前的值是 !num!
)
echo 当前⽬录下共有 %num% 个exe⽂件
dir /a-d /b *.txt|findstr "test">nul&&(
echo 存在含有 test 字符串的⽂本本件
)
||echo 不存在含有 test 字符串的⽂本⽂件
if exist test.ini (
echo 存在 test.ini ⽂件
) el echo 不存在 test.ini ⽂件
pau 使⽤第call语句,那么,[code19]和[code20]可以分别修改为:复制内容到剪贴板代码:
@echo off
t num=0&&call echo %%num%%
pau
代码:
@echo off
t num=0
for /f %%i in ('dir /a-d /b *.exe') do (
t /a num+=1
call echo num 当前的值是 %%num%%
)
echo 当前⽬录下共有 %num% 个exe⽂件
dir /a-d /b *.txt|findstr "test">nul&&(
echo 存在含有 test 字符串的⽂本本件
)||echo 不存在含有 test 字符串的⽂本⽂件
if exist test.ini (
echo 存在 test.ini ⽂件
) el 不存在 test.ini ⽂件
pau
由此可见,如果使⽤ tlocal enabledelayedexpansion 语句来延迟变量,就要把原本使⽤百分号对闭合的变量引⽤改为使⽤感叹号对来闭合;如果使⽤call语句,就要在原来命令的前部加上 call 命令,并把变量引⽤的单层百分号对改为双层。其中,因为call语句使⽤的是双层百分号对,容易使⼈犯迷糊,所以⽤得较少,常⽤的是使⽤ tlocal enabledelayedexpansion 语句(t是设置的意思,local是本地的意思,enable是能够的意思,delayed是延迟的意思,expansion是扩展的意思,合起来,就是:让变量成为局部变量,并延迟它的扩展⾏为)。
通过上⾯的分析,我们可以知道:
1、为什么要使⽤变量延迟?因为要让复合语句内部的变量实时感知到变量值的变化。
2、在哪些场合需要使⽤变量延迟语句?在复合语句内部,如果某个变量的值发⽣了改变,并且改变后的值需要在复合语句内部的其他地⽅被⽤到,那么,就需要使⽤变量延迟语句。⽽复合语句有:for语句、if……el语句、⽤连接符&、||和&&连接的语句、⽤管道符号|连接的语句,以及⽤括号括起来的、由多条语句组合⽽成的语句块。最常见的场合,则是for语句和if……el语句。
3、怎样使⽤变量延迟?
⽅法有两种:
①使⽤ tlocal enabledelayedexpansion 语句:在获取变化的变量值语句之前使⽤tlocal enabledelayedexpansion,并把原本使⽤百分号对闭合的变量引⽤改为使⽤感叹号对来闭合;
②使⽤ call 语句:在原来命令的前部加上 call 命令,并把变量引⽤的单层百分号对改为双层。
“变量延迟”是批处理中⼀个⼗分重要的机制,它因预处理机制⽽⽣,⽤于复合语句,特别是⼤量使⽤于强⼤的for语句中。只有熟练地使⽤这⼀机制,才能在for的世界中如鱼得⽔,让⾃⼰的批处理⽔平更上⼀层楼。很多时候,对for的处理机制,我们⼀直是雾⾥看花,即使偶有所得,也只是只可意会难以⾔传。希望⼤家反复揣摩,多加练习,很多细节上的经验,是只有通过⼤量的摸索才能得到的。Good Luck!
变量延迟(tlocal)之浅见
变量延迟,浅见认为就是变量预处理,在事先声明变量,告诉cmd环境哪个先哪个后。默认情况下是停⽤,可以⽤两种⽅法启⽤/停⽤:
⼀、cmd /v:on 和cmd /v:off ,范围在cmd这个环境直⾄exit 出现退出cmd
⼆、tlocal enabledelayedexpansion和tlocal disabledelayedexpansion范围在批处理⽂件范围内,直⾄endlocal出现中⽌.
先看看官⽅帮助t /?后以⼏个批处理代码注释解释。
考虑到读取⼀⾏⽂本时所遇到的⽬前扩充的限制时,延迟环境变量扩充是很有⽤的,⽽不是执⾏的时候。以下例⼦说明直接变量扩充的问题:
t VAR=before
if "%VAR%" == "before" (
t VAR=after
if "%VAR%" == "after" @echo If you e this, it worked
)
不会显⽰消息,因为在读到第⼀个 IF 语句时,BOTH IF 语句中的 %VAR% 会被代替;原因是: 它包含 IF 的⽂体,IF 是⼀个复合语句。所以,复合语句中的 IF 实际上是在⽐较 "before" 和"after",这两者永远不会相等。同样,以下这个例⼦也不会达到预期效果:
t LIST=
for %i in (*) do t LIST=%LIST% %i
echo %LIST%
原因是,它不会在⽬前的⽬录中建⽴⼀个⽂件列表,⽽只是将LIST 变量设成找到的最后⼀个⽂件。这也是因为 %LIST% 在FOR 语句被读取时,只被扩充了⼀次;⽽且,那时的 LIST 变量是空的。因此,我们真正执⾏的 FOR 循环是:
for %i in (*) do t LIST= %i
这个循环继续将 LIST 设成找到的最后⼀个⽂件。延迟环境变量扩充允许您使⽤⼀个不同的字符(惊叹号)在执⾏时间扩充环境变量。如果延迟的变量扩充被启⽤,可以将上⾯例⼦写成以下所⽰,以达到预期效果:
t VAR=before
if "%VAR%" == "before" (
t VAR=after
if "!VAR!" == "after" @echo If you e this, it worked
)
t LIST=
for %i in (*) do t 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&tlocal enabledelayedexpansion
::第⼀⽅演⽰变量延迟,当输出if you ..延迟启动
::var重复赋值,第⼀个%var%取的是before
::第⼆个!var!取的是after,若不开启延迟变量
::不显⽰因!VAR!取的是before
t VAR=before
if "%VAR%" == "before" (
t VAR=after
if "!VAR!" == "after" @echo If you e this, it worked
)
pau>nul
@echo off&tlocal enabledelayedexpansion
:: 演⽰变量延迟,⽤tlocal disabledelayedexpansion
:: 和tlocal enabledelayedexpansion 相互切换
:: echo %var% 取t var=fds 的值,⽽echo !var!
:: 取for复合语句中t var=%%i的值;%var%的值与变量延迟
:: tlocal开关⽆关,但!var!的值与之相关。当变量延迟
:: 处于开状态则取%%i的赋值,反之则取!var!本⾝.
t var=fds
for /l %%i in (1,1,6) do (
t var=%%i
echo %var%
echo !var!
)
pau>nul&exit
@echo off&tlocal enabledelayedexpansion
::变量⾥套变量延迟演⽰
t a=40000
t b=df
t a%b%=70000
t c=!a%b%!
echo %c%
pau>nul
怎么画皮卡丘@echo off&tlocal enabledelayedexpansion
::显⽰2个随机数并截取各数字求和
mode con:cols=40 lines=20 1>nul
color a1
t %random%=%random%
for /f "delims=" %%i in ('t') do t num=%%i&call echo %%num%%&goto be
:be
for /f "tokens=1,2 delims==" %%j in ("%num%") do (
t/a a=%%j
t/a b=%%k
)
for /f %%l in ("%a%%b%") do (
t/a a=%%l
t/a b=%%l
t/a a0=%a:~0,1%
t/a a1=%a:~1,1% 2>nul&t/a a1+=!a0!
t/a a2=%a:~2,1% 2>nul&t/a a2+=!a1!
t/a a3=%a:~3,1% 2>nul&t/a a3+=!a2!
t/a a4=%a:~4,1% 2>nul&t/a a4+=!a3!
t/a b0=%b:~0,1%
t/a b1=%b:~1,1% 2>nul&t/a b1+=!b0!
t/a b2=%b:~2,1% 2>nul&t/a b2+=!b1!
纪检培训t/a b3=%b:~3,1% 2>nul&t/a b3+=!b2!
t/a b4=%b:~4,1% 2>nul&t/a b4+=!b3!
)
t/a b4+=%a4%
echo 和为:%b4%
pau>nul