神坑
原地址: http://imlane.top/post/batchpi-chu-li-zhong-de-enabledelayedexpansion
虽然距离发现这个坑已经很久了, 但是现在就是特别想记载一下这个"神坑".
只要对批处理有所了解, 就应该很清楚, 批处理中的变量是用百分号%
包裹起来的, 比如这样: %var%
.
如果有一个变量, 你想改变它的值, 比如使之自加1, 那么可以这样:
@echo off
set var=0
set /a var=%var% + 1
echo %var%
直到这里都很好理解, 所有的编程语言都是这么写的嘛. 这只是因为你还没有遇到批处理中的for
命令. 遇到这个命令以后, 一切都不那么美好了.
做个实验:
@echo off
set var=0
for %%i in (h,e,l,l,o) do (
echo %%i
set /a var=%var%+1
)
echo.
echo var is: %var%
猜猜最后那句echo var is: %var%
会打印出什么? var is 5
吗, 因为"h,e,l,l,o"一共5个字符?
显然一切不会那么尽如人意, 正确的答案是1
.
WTF?
为毛啊, 每个字符统计一次, 自加1, 结果为什么不是5?
这就是我说的那个"神坑"了. 刚入门不清楚的时候可能看上一天也不知道怎么回事.
解决办法也很简单, 试试这段代码:
@echo off
setlocal enabledelayedexpansion
set var=0
for %%i in (h,e,l,l,o) do (
echo %%i
set /a var=!var!+1
)
echo.
echo var is: %var%
这一次就能正确的输出5
了吧? 知道奥秘在哪里吗?
注意到开头的setlocal enabledelayedexpansion
和for
命令中的!var!
了吗? 这个就是问题的关键所在.
想要理解setlocal enabledelayedexpansion
和!var!
, 我们要先理解两个概念: parse time
和execution time
. 直译过来就是解析时
和运行时
.
大家应该都知道C语言中的宏定义, 就是#define
这玩意儿, 用#define
定义的宏在执行之前, 预编译的时候, 就会被自动替换成用户定义的内容. 类似的, 批处理中也有这个概念, 就是所谓的parse time
. 而execution time
就是真正执行的时候, 就跟C语言中真正执行的时候是一样的.
批出里中在parse time
的时候, 就会对所有的变量进行替换, 比如你看到的echo %var%
这条命令, 如果在parse time
的时候var
正好是0, 那么在parse time
的时候就已经被简单替换为了echo 0
了. 然后之后进入execution time
的时候执行的已经是echo 0
这条命令了.
所以我们看最初的这个例子:
for %%i in (h,e,l,l,o) do (
echo %%i
set /a var=%var%+1
)
用我们刚才的理论来看, 就不难发现这段代码在parse time
的时候就将会被简单替换为如下模样:
for %%i in (h,e,l,l,o) do (
echo %%i
set /a var=0+1
)
所以无论怎么执行, var
的值最终都将只会是0 + 1 = 1.
不过好像又有了新的问题, 按照我们的理论, %var%会被简单替换成0的话, 那么echo var is: %var%
中的%var%
也应该早就被替换为了0
, 那为什么还可以被打印出1这个值呢?
批处理文件说白了就是一堆命令的集合, 命令是以行为单位执行的, 批处理文件自然也是以行为单位进行处理的. 所谓的parse time
和execution time
的概念也是针对于一行来说的, 对与当前正在执行的行, 我们首先会进入parse time
, 对变量做简单替换, 然后进入execution time
, 真正的开始执行. 然后到了下一行的parse time
的时候, %var%
已经变成了1
, 所以echo var is: %var%
也会被替换成echo var is: 1
从而输出正确的值.
而且所谓一行的概念并不是我们看见的一行就是一行, 比如像for
命令这种, 尽管在我们看来占了好几行, 但是因为是括号括起来的, 所以无论括号中的代码有多长, 在批处理看来都同属这条for
命令, 仍然是一行.
絮絮叨叨说了半天, 那么现在大概就可以解释setlocal enabledelayedexpansion
的作用了.
setlocal
命令没啥用, 就是设定一个本地生效(就是本Batch文件生效)的变量效果.
enabledelayedexpansion
字面上翻译一下: 启用延迟展开.
怎么理解呢? 回到我们的parse time
和execution time
. 正常来说, parse time
的时候变量就已经被简单替换为了值, 但是这不是我们想要的效果, 怎么办? 那就想办法让这个效果延迟好了? delayed expansion
就是这个意思, 把这个变量展开
的效果延迟. 这句enabledelayedexpansion
的意思就是启用这个将变量延迟展开
的效果. 这样就可以延迟到execution time
了.
光是启用了这个扩展并没有用, 因为批处理并没有那么智能, 它看到代码中%
符号括起来的东西它仍然会愚蠢的进行简单替换. 那么怎么办? 聪明的人类就决定用一个新的符号来包裹变量, 那就是!
符号, 所以就有了我们能看见的!var!
. 这时由于!var!
并不是被%
所包裹, 所以它并不会被愚蠢的批处理程序简单替换掉, 这样它就能继续保留变量的模样得以苟延残喘.
那么好了, 现在我们应该能看懂这段代码了:
for %%i in (h,e,l,l,o) do (
echo %%i
set /a var=!var!+1
)
上面这段代码在parse time
的时候将不会被简单的替换掉, 作为变量而存在的!var!
将会原封不动的保留在其中, 原原本本的反映每一次变量的变化, 就如同任何一个其他正常的编程语言一样.(注意Batch并不是编程语言, 它只是一堆命令的集合...) 这样我们就能如预料一般获取正确的结果啦.
后记
以上的内容有不少都是我的神展开, 其实我查到的资料并没有那么多, 但是根据我的经验和我的实验结果, 完全可以得出这些结论.
其实我主要就参考这段话得出了这些结论: "Delayed Expansion will cause variables to be expanded at execution time rather than at parse time, this option is turned on with the SETLOCAL command." -- 原文请点击 这里