批处理for语句 文本解析显神威

发布时间:2020-05-20编辑:脚本学堂
本文介绍下,批处理中for语句的详细用法,相当经典的一篇文章,如果你有意研究下批处理的内容,这篇一定不要错过。

  通过上面的分析,我们可以知道:
  1、为什么要使用变量延迟?因为要让复合语句内部的变量实时感知到变量值的变化。
  2、在哪些场合需要使用变量延迟语句?在复合语句内部,如果某个变量的值发生了改变,并且改变后的值需要在复合语句内部的其他地方被用到,那么,就需要使用变量延迟语句。而复合语句有:for语句、if……else语句、用连接符&、||和&&连接的语句、用管道符号|连接的语句,以及用括号括起来的、由多条语句组合而成的语句块。最常见的场合,则是for语句和if……else语句。
  3、怎样使用变量延迟?
  方法有两种:
  ① 使用 setlocal enabledelayedexpansion 语句:在获取变化的变量值语句之前使用setlocal enabledelayedexpansion,并把原本使用百分号对闭合的变量引用改为使用感叹号对来闭合;
  ② 使用 call 语句:在原来命令的前部加上 call 命令,并把变量引用的单层百分号对改为双层。

  “变量延迟”是批处理中一个十分重要的机制,它因预处理机制而生,用于复合语句,特别是大量使用于强大的for语句中。只有熟练地使用这一机制,才能在for的世界中如鱼得水,让自己的批处理水平更上一层楼。很多时候,对for的处理机制,我们一直是雾里看花,即使偶有所得,也只是只可意会难以言传。希望大家反复揣摩,多加练习,很多细节上的经验,是只有通过大量的摸索才能得到的。Good Luck!

四、翻箱倒柜遍历文件夹:for /r

(一)for /r 的作用及用法

  按照帮助信息里文绉绉的说法,for /r 的作用是“递归”,我们换一个通俗一点的,叫“遍历文件夹”。

  更详细的解释就是:在下面的语句中,如果“元素集合”中只是一个点号,那么,这条语句的作用就是:列举“目录”及其之下的所有子目录,对这些文件夹都执行“命令语句集合”中的命令语句。其作用与嵌套进 for /f 复合语句的 "dir /ad /b /s 路径" 功能类似。如果省略了“目录”,将在当前目录下执行前面描述的操作。

for /r 目录 %%i in (元素集合) do 命令语句集合
代码:
[code21]
 

复制代码 代码示例:
@echo off
for /r d:test %%i in (.) do echo %%i
pause

  执行的结果如下所示:
[quote]
d:test.
d:test1.
d:test2.
d:test3.
[/quote]

  效果就是显示 d:test 目录及其之下是所有子目录的路径,其效果与 dir /ad /b /s d:test 类似。若要说到两者的区别,可以归纳出3点:

  1、for /r 列举出来的路径最后都带有斜杠和点号,而 dir 语句则没有,会对获取到的路径进行进一步加工产生影响;
  2、for /r 不能列举带隐藏属性的目录,而 dir 语句则可以通过指定 /a 后面紧跟的参数来获取带指定属性的目录,更加灵活;
  3、若要对获取到的路径进行进一步处理,则需要把 dir 语句放入 for /f 语句中进行分析,写成 for /f %%i in ('dir /ad /b /s') do …… 的形式;由于 for /r 语句是边列举路径边进行处理,所以,在处理大量路径的时候,前期不会感到有停顿,而 for /f 语句则需要等到 dir /ad /b /s 语句把所有路径都列举完之后,再读入内存进行处理,所以,在处理大量路径的时候,前期会感到有明显的停顿。

第2点差别很容易被大家忽视,导致用 for /r 列举路径的时候会造成遗漏;而第3点则会让大家有更直观的感受,很容易感觉到两者之间的差别。
要是“元素集合”不是点号呢?那又如何?
代码:
[code22]
 

复制代码 代码示例:
@echo off
for /r d:test %%i in (a b c) do echo %%i
pause

运行结果:
D:test1a
D:test1b
D:test1c
D:test2a
D:test2b
D:test2c
D:test3a
D:test3b
D:test3c
含义:列举 d:test 及其所有的子目录,对所有的目录路径都分别添加a、b、c之后再显示出来。

再来看一个代码:
[code23]
 

复制代码 代码示例:
@echo off
for /r d:test %%i in (*.txt) do echo %%i
pause

运行结果是:
D:testtest.txt
D:test11.txt
D:test12.txt
D:test2a.txt
D:test2b.txt
D:test31.txt

代码的含义:列举 d:test 及其所有子目录下的txt文本文件(以.txt结尾的文件夹不会被列出来)。
再回过头来归纳一下这个语句的作用:
for /r 目录 %%i in (元素集合) do 命令语句集合

上面语句的作用是:
  1、列举“目录”及该目录路径下所有子目录,并把列举出来的目录路径和元素集合中的每一个元素拼接成形如“目录路径元素”格式的新字符串,然后,对每一条这样的新字符串执行“命令语句集合”中的每一条命令;
  特别的是:当“元素集合”带以点号分隔的通配符?或*的时候,把“元素集合”视为文件(不视为文件夹),整条语句的作用是匹配“目录”所指文件夹及其所有子文件夹下匹配的文件;若不以点号分隔,则把“元素集合”视为文件夹(不视为文件);
  2、当省略掉“目录”时,则针对当前目录;
  3、当元素集合中仅仅是一个点号的时候,将只列举目录路径;

(二)for /r 还是 dir /ad /b /s?列举目录时该如何选择
  前面已经说过,当列举目录时,for /r 和 dir /ad /b /s 的效果是非常类似的,这就产生了一个问题:当我要获取目录路径并进行进一步处理的时候,两者之间,我该如何选择?
  这个问题,前面其实已经有过一些讨论,现在我们再来作详细的分析。
  我们来看一下两者各自的优缺点:
  1、for /r:
    1)优点:
    ① 只通过1条语句就可以同时实现获取目录路径和处理目录路径的操作;
    ② 遍历文件夹的时候,是边列举边处理的,获取到一条路径就处理一条路径,内存占用小,处理大量路径的时候不会产生停顿感;

    2)缺点:
    ① 不能获取到带隐藏属性的目录,会产生遗漏;
    ② 不能获取带指定属性的目录

  2、dir /ad /s:
    1)优点:
    ① 能一次性获取带任意属性的目录,不会产生遗漏;
    ② 能通过指定不同的参数获取带任意属性的目录,更具灵活性。

    2)缺点:
    ① dir /ad /s 语句仅能获取到目录路径,若要实现进一步的处理,还需要嵌入 for /f 语句中才能实现,写法不够简洁;
    ② 嵌入 for /f 语句之后,需要写成 for /f "delims=" %%i in ('dir /ad /b /s') do …… 的格式,受 for /f 语句运行机制的制约,需要先列举完所有的路径放入内存之后,才能对每一条路径进行进一步的处理,处理大量路径时,内存占用量偏大,并且在前期会产生明显的停顿感,用户体验度不够好;

  综合上述分析,可以做出如下选择:

  1、若仅仅是为了获取某文件夹及其所有子文件夹的路径的话,请选择 dir /ad /b /s 语句;
  2、若需要过滤带隐藏属性的文件夹的话,for /r 和 dir 语句都可以实现,但 for /r 内存占用小,处理速度快,是上上之选;
  3、若需要获取所有文件夹,则除了 dir /ad /b /s 外,别无选择,因为 for /r 语句会遗漏带隐藏属性的文件夹;

  在实际的使用中,我更喜欢使用 for /f 和 dir 的组合,因为它不会产生遗漏,并能给我带来更灵活的处理方式,唯一需要忍受的,就是它在处理大量路径时前期的停顿感,以及在这背后稍微有点偏高的内存占用;在我追求速度且可以忽略带隐藏属性的目录的时候,我会换用 for /r 的方案,不过这样的情形不多——有谁会愿意为了追求速度而容忍遗漏呢?

五、仅仅为了匹配第一层目录而存在:for /d
for /d 中 /d ,完整的含义是 /directory,本意是为了处理文件夹,它的完整语句应该是这样的:
for /d %%i in (元素集合) do 命令语句集合
  当“元素集合”中包含有通配符?或*时,它会匹配文件夹,但是,相比 for /r 而言,这个时候的for /d,其作用就小得可怜了:它仅能匹配当前目录下的第一级文件夹,或是指定位置上的文件夹,而不能匹配更深层次的子文件夹。

例如:for /d %%i in (d:test*) do echo %%i 这样的语句 ,会匹配到形如:d:test、d:test1、d:test2之类的文件夹,若不存在这样的路径,将不会有任何回显。
当“元素集合”中不包含任何的通配符时,它的作用和 "for %%i in (元素集合) do 命令语句集合" 这样的语句别无二致。

因此,for /d 的角色就变得很微妙了:当“元素集合”中包含通配符?或*时,它的作用就是匹配文件夹,此时,它仅能匹配当前目录下的第一级文件夹,或是指定位置上的文件夹,在层次深度上不及 for /r,但和 for /r 一样的坏脾气:不能匹配带隐藏属性的文件夹;在灵活性上不及for /f和dir的组合;当“元素集合”中不包含任何统配符的时候,它完全是 "for %%i in (元素集合) do ……" 语句的翻版,但是又稍显复杂。

for /d 的作用是如此有限,我使用的次数是如此之少,以至于我一度找不到它的用武之地,认为它食之无味,弃之可惜,完全是鸡肋一块。
某年某月,我在cmd窗口里写下了这样的代码:
[code24]
for /d %i in (test*) do @echo %i
我的本意是想查看在我的临时目录下,长年累月的测试工作到底建立了多少测试文件夹,以便我随后把echo换成rd删除之。这个时候,我发现这条代码是如此的简洁,是 for /r 或 for和 dir /ad /b 的组合所无法替代的(echo换成rd就可以直接删除掉这些测试目录)。
简洁的代码给我带来的喜悦仅仅持续了短短10几秒的时间,我便开始了迷惘——能用到for /d的类似情形,貌似少之又少且乏善可陈啊。

六、计数循环:for /l
/l者,/loop的缩写是也,从鸟语翻译过来,loop就是循环的意思。实际上,所有的for语句,都可以看成是一种“循环”,只是在/l中,特指按照指定次数进行循环罢了。
for /l 语句的完整格式是这样的:for /l %%i in (x,y,z) do (……),在这个语句中,x、y和z都只能取整数,正负皆可,x指代起始值,y指代步长,z为终止值,具体含义为:从x开始计数,以y为步长,直至最接近z的那个整数值为止,这之间有多少个数,do后的语句就执行多少次。
例子:
[code25]
 

复制代码 代码示例:
for /l %%i in (1,2,10) do echo bathome

在以上的代码中,初始值是1,步长为2,终止值为10,表明计数从1开始,每隔2个数计算一次,直至最接近10的那个整数,罗列出来,就是1,3,5,7,9,再下一个就是11,超过10了,不再计算在内,所以,do后的语句只执行5次,将连续显示5个bathome。

实际上,x,y和z的值可正可负,甚至为0,限制非常宽松:
  1、步长y的值不能为0;
  2、当步长y的值为正整数时,终止值z不能小于初始值x;
  3、当步长y的值为负整数的时候,终止值z不能大于初始值x。

  换而言之,必须保证in和do之间能取到一个有效的数组序列。

  例如:
[code26]
 

复制代码 代码示例:
for /l %%i in (-1,2,5) do echo bathome

[code27]
 

复制代码 代码示例:
for /l %%i in (5,-2,-1) do echo bathome

以上两条代码的功能完全一样,都将显示4次bathome,区别就在于[code26]是正序计算,而[code27]是逆序计数而已。

以下几条代码都是有问题的:
[code28]
 

复制代码 代码示例:
for /l %%i in (1,0,1) do echo bathome

[code29]
 

复制代码 代码示例:
for /l %%i in (2,1,1) do echo bathome

[code30]
 

复制代码 代码示例:
for /l %%i in (1,-1,2) do echo bathome

其中,[code28]违反了步长不能为0的限制,将陷入无限循环中;[code29]和[code30]都犯了同样的错误:无法获得有效的数列元素,导致in和do之间取到的值为空元素,从而使得整条for语句无从执行。

当大家明白了 for /l 的具体功能之后,是否会想到了与它有异曲同工之妙的goto循环语句呢?似乎,for /l 和 goto 循环语句可以相互替换?

一般而言,for /l语句可以换成goto循环,但是,goto循环并不一定能被 for /l 语句替换掉。具体原因,请大家仔细想想,我在此不再详细解说,只是就大家非常关心的一个问题提供一个简洁的答案,那就是:什么时候该用 for /l 计数循环,而什么时候又该用goto条件循环?

答案非常简单:当循环次数确定的时候,首选 for /l 语句,也可使用goto语句但不推荐;当循环次数不确定的时候,用goto语句将是唯一的选择,因为,这个时候需要用if之类的条件语句来判断何时结束goto跳转。