Batch批处理文件中的"神坑"

神坑

原地址: 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 enabledelayedexpansionfor命令中的!var!了吗? 这个就是问题的关键所在.

想要理解setlocal enabledelayedexpansion!var!, 我们要先理解两个概念: parse timeexecution 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 timeexecution 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 timeexecution 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." -- 原文请点击 这里

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容

  • 个人学习批处理的初衷来源于实际工作;在某个迭代版本有个BS(安卓手游模拟器)大需求,从而在测试过程中就重复涉及到...
    Luckykailiu阅读 4,665评论 0 11
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • linux资料总章2.1 1.0写的不好抱歉 但是2.0已经改了很多 但是错误还是无法避免 以后资料会慢慢更新 大...
    数据革命阅读 12,112评论 2 34
  • 批处理符号简介 回显屏蔽 @ 重定向1 >与>> 重定向2 < 管道符号 | 转义符 ^ 逻辑命令符包括:&、&&...
    wyude阅读 2,953评论 2 5
  • 作为一个宅男,却向往阳光。平时喜欢看些段子,喜欢忘我的笑。以后每天我会给大家分享10个我看到的好笑的段子,喜欢的点...
    fzt234阅读 261评论 0 0