Batch Script

Table of Contents

1. Batch

在 Windows 中,使用 Batch 文件可以方便地完成自动化任务,类似于 Linux 中的 Shell 脚本。Batch 的完整语法可参考:Using batch files

2. Tips

2.1. for 循环

FOR 这条命令基本上都被用来处理文本,但还有其它一些好用的功能!

它的基本格式(注:这里我引用的是批处理中的格式,变量名前要用两个 % 号,直接在命令行执行则只需要一个 % 号):

FOR 参数 %%变量名 IN (相关文件或命令) DO 执行的命令

说明如下:

  1. FOR 有 4 个参数 /d /l /r /f 它们的作用在后面会用例子进行解释;
  2. %%变量名 :这个变量名可以是小写 a-z 或者大写 A-Z ,它们区分大小写,FOR 会把每个读取到的值给它;
  3. (相关文件或命令):FOR 要把什么东西读取然后赋值给变量,看后面的例子;
  4. 执行的命令:对每个变量的值要执行什么操作就写在这。

在 CMD 输入 for /? 可以查看系统提供的帮助。

下面开始讲解每个参数的含义。

2.1.1. 参数/D

参数 /D 表示“目录”。如果 Set (也就是我上面写的“相关文件或命令”) 包含通配符(* 和 ?),将对与Set相匹配的每个目录(而不是指定目录中的文件组)执行指定的 Command。
系统帮助的格式: FOR /D %%variable IN (set) DO command

它主要用于目录搜索,不会搜索文件;看例子:

@echo off
for /d %%i in (*) do @echo %%i
pause

把它保存放在 C 盘根目录执行,就会把 C 盘目录下的全部目录名字打印出来,而文件名字一个也不显示。

再看一个例子,比如我们要把当前路径下文件夹的名字只有 1-3 个字母的打出来:

@echo off
for /d %%i in (???) do @echo %%i
pause

这样的话如果你当前目录下有目录名字只有 1-3 个字母的,就会显示出来,没有就不显示了。

2.1.2. 参数/R

参数 /R 表示“递归”。

进入根目录树 [Drive:]Path,在树的每个目录中执行 for 语句。如果在/R后没有指定目录,则认为是当前目录。如果 Set 只是一个句点 (.),则只枚举目录树。
系统帮助的格式: FOR /R [[drive:]path] %%variable IN (set) DO command

它可以把当前或者你指定路径下的文件名字全部读取,注意是文件名字,下面是一个例子:

@echo off
for /r c:\ %%i in (*.exe) do @echo %%i
pause

它把 C 盘根目录,和每个目录的子目录下面全部的 exe 文件都列出来了。

再看一个例子:

@echo off
for /r %%i in (*.exe) do @echo %%i
pause

参数不一样了,这个命令前面没加写搜索路径,这样它就会以“当前目录”为搜索路径,比如你这个 BAT 你把它放在 d:\test 目录下执行,那么它就会把 d:\test 目录和它下面的子目录的全部 exe 文件列出来。

2.1.3. 参数/L

参数 /L 表示“迭代数值范围”。
使用迭代变量设置起始值 (Start#),然后逐步执行一组范围的值,直到该值超过所设置的终止值 (End#)。/L 将通过对 Start# 与 End# 进行比较来执行迭代变量。如果 Start# 小于 End#,就会执行该命令。
如果迭代变量超过 End#,则命令解释程序退出此循环。还可以使用负的 Step# 以递减数值的方式逐步执行此范围内的值。例如,(1,1,5) 生成序列 1 2 3 4 5,而 (5,-1,1) 则生成序列 (5 4 3 2 1)。
系统帮助的格式: for /L %% Variable in (Start#,Step#,End#) do Command

例如:

@echo off
for /l %%i in (1,1,5) do @echo %%i
pause

保存执行看效果,它会打印从 1 2 3 4 5 这样 5 个数字,(1,1,5) 这个参数也就是表示从 1 开始每次加 1 直到 5 终止!

再看这个例子:

@echo off
for /l %%i in (1,1,5) do start cmd
pause

执行会启动 5 个 CMD 窗口,如果把那个 (1,1,5) 改成 (1,1,65535) 会有什么结果,会打开 65535 个 CMD 窗口!

参考:
http://stackoverflow.com/questions/2591758/batch-script-loop

2.1.4. 参数/F

含有 /F 的 for 有很大的用处,在批处理中使用的最多,用法如下:

FOR /F ["options"] %%i IN (file) DO command
FOR /F ["options"] %%i IN ("string") DO command
FOR /F ["options"] %%i IN ('command') DO command

这个可能是最常用的,也是最强的命令,主要用来处理文件和一些命令的输出结果。
file 代表一个或多个文件
string 代表字符串
command 代表命令
options 可选

对于 FOR /F %%i IN (file) DO command
file 为文件名,按照官方的说法是,for 会依次将 file 中的文件打开,并且在进行到下一个文件之前将每个文件读取到内存,按照每一行分成一个一个的元素,忽略空白的行,看个例子。
假如文件 a.txt 中有如下内容:

第1行第1列 第1行第2列 第1行第3列
第2行第1列 第2行第2列 第2行第3列
第3行第1列 第3行第2列 第3行第3列

你想显示 a.txt 中的内容,会用什么命令呢?当然是 type a.txt
for 也可以完成同样的命令: for /f %%i in (a.txt) do echo %%i
还是先从括号执行,因为含有参数 /f,所以 for 会先打开 a.txt,然后读出 a.txt 里面的所有内容,把它作为一个集合,并且以每一行作为一个元素,所以会产生这样的集合:
{“第1行第1列 第1行第2列 第1行第3列”, //第一个元素
“第2行第1列 第2行第2列 第2行第3列”, //第二个元素
“第3行第1列 第3行第2列 第3行第3列”} //第三个元素

集合中只有 3 个元素,同样用 %%i 依次代替每个元素,然后执行 do 后面的命令。具体过程:
用 %%i 代替“第1行第1列 第1行第2列 第1行第3列”,执行do后面的echo %%i,显示“第1行第1列 第1行第2列第1行第3列”,
用 %%i 代替“第2行第1列 第2行第2列 第2行第3列”,执行echo %%i,显示“第2行第1列 第2行第2列第2行第3列”,
依次,直到每个元素都代替完为止。

为了加强理解 /f 的作用,请执行一下两个命令,对比即可明白:

for /f %%i in (a.txt) do echo %%i //会显示a.txt里面的内容,因为/f的作用,会读出a.txt中的内容
for %%i in (a.txt) do echo %%i    //只会显示a.txt这个名字,并不会读取其中的内容

通过上面的学习,我们发现 for /f 会默认以每一行来作为一个元素,但是如果我们还想把每一行再分解更小的内容,该怎么办呢?不用担心,for 命令还为我们提供了更详细的参数,使我们将每一行分为更小的元素成为可能。
它们就是:delims 和 tokens
delims 用来告诉 for 每一行应该拿什么作为分隔符,默认的分隔符是空格和 tab 键
比如,还是上面的文件,我们执行下面的命令:

for /f "delims= " %%i in (a.txt) do echo %%i

显示的结果是:

第1行第1列
第2行第1列
第3行第1列

为什么是这样的呢。因为这里有了 delims 这个参数,=后面有一个空格,意思是再将每个元素以空格分割,默认是只取分割之后的第一个元素。
执行过程是:

将第一个元素“第1行第1列 第1行第2列 第1行第3列”分成三个元素:“第1行第1列” “第1行第2列” “第1行第3列”,它默认只取第一个,即“第1行第1列”,然后执行do后面的命令,依次类推。

但是这样还是有局限的,如果我们想要每一行的第二列元素,那又如何呢?
这时候,tokens 跳出来说,我能做到。它的作用就是当你通过 delims 将每一行分为更小的元素时,由它来控制要取哪一个或哪几个。使用 tokens 时,会自动分配新的变量!
FOR /F "eol=; tokens=2,3* delims=, " %%i in (myfile.txt) do @echo %%i %%j %%k
echo 后面的变量 i,j,k 是新分配的,它们对应 tokens 值为 2,3 和*(*在此表示 tokens 3之后余下的所有 tokens)

执行如下命令: for /f "tokens=2 delims= " %%i in (a.txt) do echo %%i ,执行结果:

第1行第2列
第2行第2列
第3行第2列

如果要显示第三列,那就换成 tokens=3。
同时 tokens 支持通配符*,以及限定范围。
如果要显示第二列和第三列,则换成 tokens=2,3 或 tokens=2-3,如果还有更多的则为:tokens=2-10 之类的。
此时的命令为: for /f "tokens=2,3 delims= " %%i in (a.txt) do echo %%i %%j

怎么多出一个%%j?
这是因为你的 tokens 后面要取每一行的两列,用%%i来替换第二列,用%%j来替换第三列。
并且必须是按照英文字母顺序排列的,%%j不能换成%%k,因为i后面是j
执行结果为:

第1行第2列 第1行第3列
第2行第2列 第2行第3列
第3行第2列 第3行第3列

对以通配符*,就是把这一行全部或者这一行的剩余部分当作一个元素了。
比如: for /f "tokens=* delims= " %%i in (a.txt) do echo %%i
执行结果为:

第1行第1列 第1行第2列 第1行第3列
第2行第1列 第2行第2列 第2行第3列
第3行第1列 第3行第2列 第3行第3列

其实就跟 for /f %%i in (a.txt) do echo %%i 的执行结果是一样的。
再如: for /f "tokens=2,* delims= " %%i in (a.txt) do echo %%i %%j
执行结果为:

第1行第2列 第1行第3列
第2行第2列 第2行第3列
第3行第2列 第3行第3列

用%%i代替第二列,用%%j代替剩余的所有
最后还有 skip 和 eol,这两个简单,skip 就是要忽略文件的前多少行,而 eol 用来指定当一行以什么符号开始时,就忽略它。
比如: for /f "skip=2 tokens=*" %%i in (a.txt) do echo %%i
结果为:

第3行第1列 第3行第2列 第3行第3列

用 skip 来告诉 for 跳过前两行。
如果不加 tokens=* 的话,执行结果为:

第3行第1列

不知道怎么回事。
再如,当 a.txt 内容变成:

.第1行第1列 第1行第2列 第1行第3列
.第2行第1列 第2行第2列 第2行第3列
第3行第1列 第3行第2列 第3行第3列

执行 for /f "eol=. tokens=*" %%i in (a.txt) do echo %%i 的结果是:

第3行第1列 第3行第2列 第3行第3列

用 eol 来告诉 for 忽略以“.”开头的行。
同样也必须加 tokens=*,否则只会显示“第3行第1列”

2.2. 变量替换

The syntax %var:str1=str2% takes the environment variable (or pseudo-variable in case of %TIME% and replaces str1 by str2. If nothing follows after the equals sign then str1 is simply deleted.

下面是把冒号换成连接符的例子:

C:\>echo %time%
18:52:22.20

C:\>echo %time::=-%
18-52-25.89

2.3. 参数 modifier

The following table lists the modifiers you can use in expansion.

Table 1: Modifiers
Modifier Description
%~1 Expands %1 and removes any surrounding quotation marks ("").
%~f1 Expands %1 to a fully qualified path name.
%~d1 Expands %1 to a drive letter.
%~p1 Expands %1 to a path.
%~n1 Expands %1 to a file name.
%~x1 Expands %1 to a file extension.
%~s1 Expanded path contains short names only.
%~a1 Expands %1 to file attributes.
%~t1 Expands %1 to date and time of file.
%~z1 Expands %1 to size of file.
%~$PATH:1 Searches the directories listed in the PATH environment variable and expands %1 to the fully qualified name of the first one found. If the environment variable name is not defined or the file is not found, this modifier expands to the empty string.

The following table lists possible combinations of modifiers and qualifiers that you can use to get compound results.

Table 2: Possible combinations of modifiers
Modifier Description
%~dp1 Expands %1 to a drive letter and path.
%~nx1 Expands %1 to a file name and extension.
%~dp$PATH:1 Searches the directories listed in the PATH environment variable for %1 and expands to the drive letter and path of the first one found.
%~ftza1 Expands %1 to a dir-like output line.

参考:
http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/percent.mspx?mfr=true

2.3.1. 应用实例

普通变量无法使用上面这些修饰符的,它们的典型应用场景是命令行参数,函数参数,for 循环中的变量。

应用举例1:分解命令行参数中的路径和文件名。

C:\DOCUME~1\jack>type 1.bat
@echo off
echo path is: %~dp1
echo file name is: %~nx1

C:\DOCUME~1\jack>1.bat C:\test\file.txt
path is: C:\test\
file name is: file.txt

应用举例2:实现 Linux 中的 basename 功能。
修饰符无法直接用于普通变量,如果一个普通变量中保存着一个带路径的文件名,怎么得到对应的不带路径的文件名呢?把变量传给一个函数即可。

C:\DOCUME~1\jack>type 2.bat
@echo off
set file1=C:\test\myfile.txt

set filename=
call :basename %file1% filename
echo file name is: %filename%

goto:eof
:basename <pathVar> <resultVar>
(
  set "%~2=%~nx1"
)

C:\DOCUME~1\jack>2.bat
file name is: myfile.txt

2.4. 实现 substring

描述1:
It is possible to retrieve specific characters from a string variable.

Syntax

  %variable:~num_chars_to_skip%
  %variable:~num_chars_to_skip,num_chars_to_keep%

实例:

c:\>set A=abcdef

c:\>echo $A:~2%
cdef

c:\>echo $A:~2,3%
cde

描述2:
The :~ substring command works like this:
:~[START POS],[LENGTH]

If [START_POS] is positive or zero the substring will start from the left. If the number [START_POS] is negative it will start from the right. And [LENGTH] is the number of characters in the opposite direction of the starting point.

If we wanted to get the current year we could start 4 from the end, and 4 in length. Like this:

c:\>echo %date%
Thu 10/31/2013

c:\>echo %date:~-4,4%
2013

参考:
http://www.intelliadmin.com/index.php/2007/02/create-a-date-and-time-stamp-in-your-batch-files/

2.5. 输出空行

输出空行,使用命令 echo. 即可。

Author: cig01

Created: <2017-06-03 Sat>

Last updated: <2018-02-09 Fri>

Creator: Emacs 27.1 (Org mode 9.4)