Windows 批处理脚本学习教程

Collected by Clove

第四章:条件 循环

4.1 条件 if

4.1.1 if 是一种极其普遍却又非常重要的语句,说得严重点这就是一种能够体现程序灵魂的东西之一。在大多数的编程语言(例如 C VB JScript Java 等)中都能看到 if 的身影。if 语句的功能正如它的字面含义一样——如果。批处理程序的语言格式相比较我们常见的 C 语言来说,并不是那么的严谨,至少看上去是更自由一些。比如 if 在批处理中的具体用法及格式就有很多,使用和发挥的余地也很大,但随之带来的问题就是我们不得不多花一些时间来记忆其各种用法和格式并分辨它们之间的差异。为了简化该问题使之更容易理解,在此我们并不打算过早地接触 if 的全面用法或格式,而是从最基本的用途开始。

4.1.2 在有前几篇文章的学习作基础的情况下,如果您能理解以下几行语句,那么您已经对 if 有了深刻的认识了(其中两个连续的等号 == 表示:是否等于)。
set var=Tom
if %var%==Tom echo It works
if %var%==Jerry echo We will never see this
如果变量 var 的值为 Tom Hanks ,即中间含有空格之类的特殊符号,那么我们在使用 if 时,就得为字符串加上双引号,就像 if "%var%"=="Tom Hanks" echo It works (注意,给字符串加上双引号后,在进行判断的时候会连双引号一起考虑进去。所以,为了使两边的对比均衡,所以一定要在 == 两边的两个字符串上同时都加双引号)。这里也体现了批处理程序语言格式的多样性(如果您熟悉 C 语言格式的话,就知道一串字符总是要被双引号引起来)。不过为了方便记忆,我们在使用 if 的时候,不妨总是在字符串上使用双引号,这样既好阅读,又不容易引起歧异。

::::::::::改变颜色.bat::::::::::
@echo off
echo 您希望字体的颜色是红色还是绿色?

:RETRY
set /p choice=请输入您的选择,R 或者 G :

if "%choice%"=="R" goto R
if "%choice%"=="r" goto R
if "%choice%"=="G" goto G
if "%choice%"=="g" goto G
goto RETRY

:R
color c
echo 您选择了红色字体
pause
exit

:G
color a
echo 您选择了绿色字体
pause
exit
::::::::::::::::::::::::::::::::
上面的例子能很好地说明了 if 与 goto 的结合使用带来的实用效果。其中,在使用 if 进行判断变量 choice 的值是否为字母 R 或 G 时,分别对其大小写都进行了判断。其实您还可以使用 if 的参数 /i ,就像 if /i "%choice%"=="r" ,这样在进行比较的时候就不区分大小写了。

4.1.3 说到了 if 就不得不说一下 else ,else 无法单独使用,必须与 if 配合连用。
:::::::::else的用法.bat:::::::::
@echo off

if "%TIME:~0,2%" lss "12" (
echo 现在是上午
) else (
echo 现在是下午
)

pause
::::::::::::::::::::::::::::::::
其中,变量 TIME 是动态环境变量之一,表示当前时间(在 set/? 中有介绍)。%TIME:~0,2% 的含义还没忘记吧,意思是取变量 TIME 的前两个字符(忘记的朋友请参阅上一篇[赋值 调用 参数])。lss 是 if 命令扩展用法,表示 小于 的意思。此外,还有等于、不等于、大于、大于等于、小于等于 的缩写,详细信息可以在 if/? 中获得。因此,对于上述批处理的理解就是:如果当前时间(前两位表示小时)小于12(点)的话,那么将显示输出“现在是上午”,否则就显示为“现在是下午”。另外:这里的大小比较判断只是对其ASCII符的大小比较,并不是真正的数值型变量的比较,稍后下文会有关于数值型变量比较的介绍。

对于 if 和 else 的编写格式有较严格的要求,尤其是在两个圆括号上,若以不正确的格式使用可能会导致 if 或 else 等命令无效。虽然上面的编写格式并不是唯一的,但使用统一、固定的格式编写代码会大大提高代码的可读性。为了加深对 if else 的理解,我们可以把上面的批处理扩展一下。
:::::::::else的用法.bat:::::::::
@echo off

if "%TIME:~0,2%" lss "12" (
if "%TIME:~0,2%" lss " 6" (
echo 现在是凌晨
) else (
echo 现在是上午
)
) else (
if "%TIME:~0,2%" lss "18" (
echo 现在是下午
) else (
echo 现在是晚上
)
)

pause
::::::::::::::::::::::::::::::::

4.1.4 刚才提到的数值型变量的比较,其实很简单,就如下面的例子中描述的一样。
::::::::::::::::::::::::::::::::
@echo off
set /a num=5

if %num% == 5 (
echo 变量 num 等于 5
)

if not %num% == 4 (
echo 变量 num 不等于 4
)

set /a num = ( %num% + 3 ) * 2
:: 变量 num 加3并乘2后再赋给变量 num 自身

if %num% == 16 (
echo 经过运算后,现在变量 num 等于16
)

if not %num% == 16 (
echo 此时的变量 num 不会不等于 16 ,因此这一句不会显示了
)

pause
::::::::::::::::::::::::::::::::

4.1.5 延迟变量扩充。考虑到读取一行文本时所遇到的目前扩充的限制时,延迟变量扩充是很有用的,而不是在执行的时候。下面的例子可以很好的说明直接变量扩充与延迟变量扩充的区别。
::::::::延迟变量扩充.bat::::::::
@echo off
setlocal EnableDelayedExpansion

set /a num=5

if %num% == 5 (
set /a num*=3

echo 在 if 语句之前,变量 num 等于 %num%
echo 但变量 num 在经过运算后,且由于延迟变量扩充被启用,变量 num 等于 !num!
)

echo 但最终变量 num 还是等于 %num%

pause
::::::::::::::::::::::::::::::::
if 条件下的两行 echo 在输出变量值的时候用到的符号不一样,一个是用百分号 % 包括起来的,另一个用的却是惊叹号 ! 。虽然在显示 %num% 之前已经使变量 num 的数值乘了3倍,但是由于没有延迟变量的扩充,使得 %num% 的结果仍然是 5 。但用 !num! 显示出的值已经变为 15 了。注意到批处理中的 setlocal EnableDelayedExpansion (setlocal/? 查看相关信息),这表示开启延迟变量扩充。此时的 !num! 才有意义。不然 !num! 将无法被识别,因为在默认情况下,延迟变量扩充是被停用的。

4.1.6 此外,if 还有其他的用法—— if exist 和 if defined 。if exist 可判断文件是否存在,就像这样:
if exist "D:\test my folder\a.txt" (
del "D:\test my folder\a.txt"
) else (
echo 您所要删除的文件不存在
)
在对文件进行操作之前进行判断其是否存在很有意义,这使得代码更加健壮。

而对于 if defined 来说,与 if exist 类似,只不过 if defined 的判断对象不是文件,而是变量,它用于判断环境变量是否被定义。

4.2 循环 for

4.2.1 如果批处理不具备批量处理的功能,那么它就徒有虚名了。而命令 for ,在某种意义上彻底体现出了批处理的强大快捷省事批量的作用。在看过 for/? 后,可以归纳出 for 大致可以分三种常用的类型(或者叫使用方法)。从针对的循环目标来看,它们分别是针对于文件、数字、以及文字。

4.2.2 for %i in (*.*) do @echo %i 。这就是 for 的一般使用格式。注意到其中的浅靛色文字 for 、in 和 do ,是 for 的固定用法。其内容可以理解为:在某一范围内(in),对于其中的某一文件来说(for),做如下的处理(do)。而 for %i in (*.*) do @echo %i 就是在当前工作目录的所有文件中(in (*.*)),对于其中的某一文件(for %i),做出显示其名称的处理(do @echo %i)。变量 i 仅在当前循环语句 for 里起作用,%i 表示其值。
注意:以上是直接在命令提示符里以命令的形式表达出来的写法;在批处理文件中应使用双百分号 %% 代替单百分比号 % ,就像:%%i。关于它们之间的区别我研究了好半天才分清楚 orz [具体请参阅后文第4.2.5节]。

批量修改文件名是其比较有用的典例之一。看看下面的批处理
:::::::批量修改文件名.bat:::::::
@echo off
setlocal EnableDelayedExpansion
set /a num=1
for %%i in (D:\test\*.txt) do (
ren "%%i" !num!.txt
set /a num+=1
)
::::::::::::::::::::::::::::::::
这个批处理并不难理解。就像第4.1.5节所说的:使用了 setlocal EnableDelayedExpansion 后,可以让 for 或 if 后面的执行语句中变量的值随其变化而不断更新(所以后面使用了 !num! 而不是 %num%)。整个批处理的处理过程就是对 D:\test\*.txt 中的所有文本文件进行批量改名,文件名从 1.txt 开始依次为 2.txt 、3.txt ……。
注意:请确保循环语句 in 路径中的文件不是重要的文件,因为改名后将无法使用撤消,如果像我一样不小心把重要文件误改名的话就又要 orz 了一次。

以上批处理是固定了文件的路径以及文件后缀名。为了增加该批处理的功能,我们可以让用户自己选择要进行改名的文件所在路径,以及选择所进行文件修改的后缀名。当然,有些朋友还希望有给文件批量加上前缀(比如:前缀1.txt 前缀2.txt 等等)。(关于 批量改文件名.bat 在第六章中还有进一步的修改)
::::::::批量改文件名.bat::::::::
@echo off
setlocal EnableDelayedExpansion

set /p zpath=请输入目标文件所在的路径:
set /p prefix=请输入文件名前缀(不能包含以下字符\/:*?"<>|):
set /p ext=请输入文件的扩展名(例如 .txt):
set /a num=1

for %%i in (%zpath%\*%ext%) do (
ren "%%i" "%prefix%!num!.%ext%"
set /a num+=1
)
::::::::::::::::::::::::::::::::

4.2.3 也许大家注意到了,上面 for 的用法仅仅是针对多个文件来进行循环重复操作的。如果想对一系列有规律的数字进行循环,或是在一定的次数内对某个操作进行循环重复的执行,使用 for 也能够实现。/l 是可以跟在 for 后面的重要参数之一。比如:for /l %i in (5,3,16) do echo %i ,可以让数值型的变量 i 依次成为:5、8、11、14 。正如 in 里所描述的规律 (5,3,16) 一样,从 5 开始,每次增加 3 ,直到 16为止。同样,我们还可以试一下 for /l %i in (19,-4,3) do echo %i ,这次 i 是递减的规律。很明显,结果将依次显示为:19、15、11、7、3 。

这样的用法很自然的能让我们想到,重复执行N遍完全一样的事情不再是麻烦而又无聊的了。在下面的例子里,您一定会找到惊喜的。
::::::::::圆圈方阵.bat::::::::::
@echo off
setlocal EnableDelayedExpansion

set var=○
for /l %%i in (1,1,7) do set var=%var%!var!
:: 此时变量 var 已经变成一行连续的8个圆圈了

for /l %%i in (1,1,8) do (
echo 这是第 %%i 份>输出结果%%i.txt
for /l %%j in (1,1,8) do echo %var%>>输出结果%%i.txt
)

echo 8 X 8 的 ○ 矩阵已经画好,并保存到8份文本文件里了
pause
::::::::::::::::::::::::::::::::
注意:%%i ,上一节中提到过,在批处理文件中需要用连续的两个百分号 %% 来描述循环变量 i ,而不是一个。
注意:%var% 与 !var! ,它们的用法与区别,在第4.1.5节中有解释。
注意:i 与 j ,在循环里面再套循环时,前一个循环变量 i 在没有释放之前,不应该让第二个循环变量的名称与 i 重复。
注意:> 与 >> ,同样是向某设备里输出,但却有区别,请参阅第2.2节。

4.2.4 for 也可以对指定范围内的文字进行循。for 后面跟参数 /f ,/f 后面跟选项,所指定的范围 in 里可以是一个文件里的文字,可以是一个字符串,也可以是一条命令的输出结果。我们首先以一个文件里的文字作为循环对象,循环时,每一行将被循环一次。
::::::::::文字筛选.txt::::::::::
@echo off
echo 测试 文字筛选.txt 里每一行的首单词
for /f %%i in (文字筛选.txt) do echo %%i
pause

echo.
echo skip=2 表示前两行被跳过
for /f "skip=2" %%i in (文字筛选.txt) do echo %%i
pause

echo.
echo tokens=2,4-6 表示提取每行的第2个、以及第4到6个单词
for /f "skip=2 tokens=2,4-6" %%i in (文字筛选.txt) do echo %%i, %%j, %%k, %%l.
pause

echo.
echo eol=N 表示当此行的首字母为 N 时,就忽略该行
for /f "eol=N skip=2 tokens=2,4-6" %%i in (文字筛选.txt) do echo %%i, %%j, %%k, %%l.
pause

echo.
echo delims=e 表示不再以空格区分每个词,而是以字母 e 作为间隔
for /f "eol=N skip=2 tokens=2,4-6 delims=e" %%i in (文字筛选.txt) do echo %%i, %%j, %%k, %%l.
pause

echo.
echo usebackq 表示双引号里的东西是文件名而不是字符串
for /f "usebackq eol=N skip=2 tokens=2,4-6 delims=e" %%i in ("文字筛选.txt") do echo %%i, %%j, %%k, %%l.
pause
::::::::::::::::::::::::::::::::
作为测试,可以在上述批处理文件的同一路径下创建一个用于测试的文本文件 文字筛选.txt ,其内容为:
Hello there!
This text is an example of test for the batch file 文字筛选.bat.
Notice the first letter in this line, N.
If the eol charactor was set to be letter N.
The third line will not be considered by the batch.

4.2.5 ESCAPE字符 % ,通常被译为转义字符,但也有更形象的译名脱逸字符、逃逸字符等。也就是说 % 不仅仅将与其相关的特定字符串转义并替换为特定字符串,而且自身也会被“脱逸”。而且类似于C语言中的转义字符 \ ,双%会转义并脱逸为单百分号 % ,四%则脱为双百分号 %% 。[3]

注意下面这个批处理中的双百分号 %% 的用法
::::::::::::::::::::::::::::::::
@echo off
set Text=Hello world!
for /l %%i IN (0,1,11) do call echo %%Text:~%%i,1%%
pause
::::::::::::::::::::::::::::::::

Collected by Clove--Mail to Me

Windows 批处理脚本学习教程