Batch
👉 Guide to Windows Batch Scripting
介绍
批处理(batch)是一种简化的脚本语言,应用于 DOS 和 Windows 系统中,它是由 DOS 或者 Windows 系统内嵌的命令解释器(通常是 COMMAND.COM 或者 CMD.EXE)解释运行。批处理文件具有.bat 或者.cmd 的扩展名,其最简单的例子,是逐行书写在命令行中会用到的各种命令。
批处理可以用来自动化一些重复的任务,如:
- 批量复制文件
- 批量修改文件
- 批量运行程序
- 批量发送邮件
- 批量备份数据
批处理文件的结构
批处理文件是无格式的文本文件,它包含一条或多条命令。它的文件扩展名为.bat 或.cmd。在命令提示下键入批处理文件的名称,或者双击该批处理文件,系统就会调用 Cmd.exe 按照该文件中各个命令出现的顺序来逐个运行它们。
批处理文件的结构可以分为以下几个部分:
- 文件头
文件头由一个或多个命令组成,通常用于设置批处理文件的执行环境,如隐藏命令提示符窗口、设置变量等。
- 命令行
命令行是批处理文件的主要部分,由一条或多条命令组成。命令可以是内部命令,也可以是外部命令。
- 文件尾
文件尾可以是空行,也可以是其他命令。
以下是一个简单的批处理文件示例:
@echo off
echo 这是一条命令行
pause
这个批处理文件的文件头由一个命令组成:@echo off。这个命令用于隐藏命令提示符窗口。
批处理文件的命令行由两条命令组成:echo 和 pause。echo 命令用于在命令提示符窗口中输出文本,pause 命令用于暂停批处理文件的执行,直到用户按下任意键。
批处理文件可以使用批处理常用命令来实现各种功能。以下是一些常用的批处理命令:
- dir:列出指定目录中的文件和文件夹
- copy:复制文件
- move:移动文件
- del:删除文件
- ren:重命名文件
- start:启动程序
- echo:在命令提示符窗口中输出文本
- pause:暂停批处理文件的执行
批处理文件也可以使用批处理流程控制语句来控制批处理文件的执行流程。以下是一些常用的批处理流程控制语句:
- if:条件判断语句
- for:循环语句
- goto:跳转语句
批处理文件可以用来自动化一些重复的任务。例如,可以编写一个批处理文件来自动备份文件、批量修改文件、批量运行程序等。
运行
在 Windows 中运行批处理文件的简单方法是直接在 Windows 资源管理器(又名“我的电脑”)中双击该文件。但这种方法不方便查看输出和错误信息,因为脚本的命令提示符窗口会在脚本退出后立即消失。
在编辑新脚本时,通常需要在现有的命令窗口中运行批处理文件。对于新手来说,最简单的万无一失的运行脚本方法是将脚本拖放到命令提示符窗口中。命令提示符会在命令行中输入脚本的完整路径,并会为包含空格的路径加上引号。
以下是运行批处理文件的一些其他提示:
- 可以使用向上箭头和向下箭头键导航命令行历史记录,调用之前的命令。
- 我通常使用以下命令运行脚本:
%COMPSPEC% /C /D "C:\Users\User\SomeScriptPath.cmd" Arg1 Arg2 Arg3
这会在新的命令提示符子进程中运行脚本。/C 选项指示子进程在脚本退出时退出。/D 禁用任何自动运行脚本(这是可选的,但我使用自动运行脚本)。这样做的原因是防止命令提示符窗口在脚本或被调用的脚本调用 EXIT 命令时自动关闭。EXIT 命令会自动关闭命令提示符窗口,除非 EXIT 是从子命令提示符进程中调用的。这很烦人,因为你会丢失脚本打印的任何消息。
中止脚本
使用 exit
可以终止整个批处理操作,使用 exit /b
可以确保只终止批处理文件而不会终止调用该文件的父进程。
注释
在批处理文件中添加注释的正式方法是使用 REM(Remark)关键字:
REM 这是一条注释!
高级用户使用的方法是使用 ::,这是一种利用标签运算符 : 两次的巧妙方法,几乎总是被忽略。
大多数高级作者认为 :: 比 REM 更简洁。但要注意,在某些地方使用 :: 会导致错误。
:: 这也是一条注释!(通常情况下)
例如,FOR 循环会在使用 :: 样式注释时出错。如果遇到这种情况,只需改用 REM。
变量
变量声明
变量声明在 DOS 中并非必要。未声明或未初始化的变量的值为空字符串,即 ""。大多数人喜欢这种方式,因为它减少了需要编写的代码量。建议在使用变量之前需要先进行声明,因为这可以捕捉到变量名拼写错误等小问题。
变量赋值
SET 命令用于为变量赋值。
SET foo=bar
注意:不要在变量名和值之间使用空格。SET foo = bar 会失败,而 SET foo=bar 会成功。
/A 开关支持在赋值时进行算术运算。如果需要验证用户输入是否为数值,这是一个有用工具。
SET /A four=2+2
4
通常的约定是使用小写字母作为脚本变量名。系统范围的变量(称为环境变量)使用大写字母。这些环境变量描述了系统中某些事物的位置,例如%TEMP%
,它是临时文件的路径。DOS 不区分大小写,因此此约定不是强制性的,但建议遵守以使脚本更易于阅读和调试。
警告:SET
命令会始终覆盖(修改)任何现有变量。编写脚本时,最好验证是否不会覆盖系统范围的变量。可以使用 ECHO %foo%
快速确认变量 foo
是否存在。例如,可能想命名一个变量为 "temp",但这会改变广泛使用的 "%TEMP%" 环境变量的含义。DOS 包括一些像命令一样运行的“动态”环境变量,例如 %DATE%
、%RANDOM%
和 %CD%
。覆盖这些动态变量不是一个好主意。
变量值的读取
在大多数情况下,可以通过在变量名前后加上 %
符号来读取变量的值。下面的示例将变量 foo
的当前值打印到控制台输出中。
C:\> SET foo=bar
C:\> ECHO %foo%
bar
列出已有变量
不带参数的 SET 命令将列出当前命令提示符会话的所有变量。其中大部分变量是系统范围的环境变量,例如 %PATH%
或 %TEMP%
。
这将会列出所有已定义的变量及其值,包括:
- 用户定义的变量:在批处理文件或当前命令提示符中设置的变量。
- 环境变量:由操作系统定义的变量,用于存储系统配置信息,例如
%PATH%
、%TEMP%
、%USERNAME%
等。 - 动态变量:由系统动态生成的变量,例如
%DATE%
、%TIME%
等。 查看所有变量可以帮助您了解当前环境的配置以及批处理文件中使用的变量
注意:调用 SET
命令会列出当前会话的所有常规(静态)变量,但不包括动态环境变量,例如 %DATE%
或 %CD%
。可以通过查看 SET
命令的帮助文本末尾来列出这些动态变量,调用 SET /?
即可查看帮助文本。
变量作用域(全局 vs 局部)
默认情况下,变量是全局的,在整个命令提示符会话中都有效。要使变量局限于脚本的作用域,请使用 SETLOCAL
命令。调用 SETLOCAL
后,任何变量赋值都会在调用 ENDLOCAL
、调用 EXIT
或脚本执行到文件末尾(EOF)时恢复。
@echo off
SET VAL=globalVar
SETLOCAL
SET VAL=localVar
echo %VAL%
ENDLOCAL
echo %VAL%
REM 输出为
REM localVar
REM globalVar
合理使用变量作用域可以用来保护系统变量不被篡改
特殊变量
在某些特殊情况下,变量的使用方式会略有不同。传递给脚本的命令行参数也是变量,但不使用 %var%
语法。而是使用单个 %
和一个 0-9 的数字来读取每个参数,表示参数的序号。您将在后面看到使用相同的样式来创建批处理脚本中的函数/子例程。
还有一种使用 !
的变量语法,例如 !var!
。这是一种特殊情况,称为延迟扩展。在讨论条件语句(if/then)和循环时,您将了解更多有关延迟扩展的信息。
以下是一些特殊变量的类型:
- 命令行参数:
- %0:脚本本身的名称。
- %1 到 %9:传递给脚本的第一个到第九个参数。
- %*:所有参数(用空格分隔)。
- 延迟扩展变量:
- !var!:在执行时扩展的变量,通常用于循环和条件语句中。
- 其他特殊变量:
- %CD%:当前目录。
- %DATE%:当前日期。
- %TIME%:当前时间。
- %RANDOM%:0 到 32767 之间的随机数。
- %ERRORLEVEL%:上一个命令的退出代码。
理解这些特殊变量对于编写复杂的批处理脚本非常重要。
命令行参数
要将命令行参数传递给脚本,只需在脚本名称后面用空格分隔参数即可。例如,要将参数 "foo" 和 "bar" 传递给脚本 "myscript.bat",请执行以下操作:
myscript.bat foo bar
脚本将可以通过变量来访问这些参数。第一个参数将在变量 %1 中可用,第二个参数将在变量 %2 中可用,依此类推。
以下示例演示了如何访问命令行参数:
@echo off
rem 获取第一个命令行参数
set arg1=%1
rem 获取第二个命令行参数
set arg2=%2
rem 打印命令行参数
echo 第一个参数:%arg1%
echo 第二个参数:%arg2%
输出:
第一个参数:foo
第二个参数:bar
注意:
- DOS 支持超过 9 个命令行参数,但是,无法直接读取第 10 个或更高参数。这是因为特殊的变量语法无法识别 %10 或更高。实际上,shell 会将 %10 读取为 %0 命令行参数和字符串 "0" 的后缀。使用
SHIFT
命令可以从参数列表中弹出第一个参数,这会将所有参数向左“移位”。例如,第二个参数会从 %2 位置移到 %1 位置,从而将第 10 个参数暴露为 %9。 - 命令行参数可以是任何类型的数据,包括字符串、数字、日期和时间。
- 您可以使用单引号或双引号来包裹命令行参数,以防止它们被解释为命令。
命令行参数的妙用
命令行参数还支持一些非常有用的可选语法,用于对作为文件路径的命令行参数运行类似宏的操作。这些宏称为变量替换支持,可以解析文件路径、时间戳或文件大小。这个超级实用的功能的文档有点难找,请运行 FOR /?
并翻到输出的末尾。
以下是一些常用的命令行参数技巧:
- %~I:删除第一个命令行参数的引号,这在处理文件路径参数时非常有用。需要对所有文件路径加引号,但对文件路径加两次引号会导致文件找不到错误。batch
SET myvar=%~I
- %~fI:第一个命令行参数的完整路径。
- %~fsI:与上面相同,但额外的 s 选项会生成第一个命令行参数的 DOS 8.3 短名路径(例如,C:\PROGRA~1 通常是 C:\Program Files 的 8.3 短名变体)。这在使用不支持文件路径中空格的第三方脚本或程序时很有用。
- %~dpI:第一个命令行参数的父文件夹的完整路径。几乎所有批处理文件都会使用这个技巧来确定脚本文件本身的位置。语法
SET parent=%~dp0
会将脚本文件的文件夹路径放入变量 %parent% 中。 - %~nxI:仅第一个命令行参数的文件名和文件扩展名。这个技巧也经常用于在运行时确定脚本的名称。如果需要向用户打印消息,可以使用脚本名称作为消息前缀,例如
ECHO %~n0: some message
而不是ECHO some message
。前缀可以帮助最终用户知道输出来自脚本,而不是脚本调用的其他程序。这听起来很简单,但你可能会花费数小时来追踪由脚本生成的晦涩的错误消息。这是从 Unix/Linux 世界学到的一种很好的做法。
获取用户输入
SET /P
命令用于从用户获取输入。这在需要用户输入数据的脚本中非常有用。例如,您可能需要提示用户输入密码或其他敏感信息。
@echo off
SET /P variable_name=Prompt
循环
for 的语法
FOR
命令使用特殊的变量语法,即 % 后跟一个字母,例如 %I
。但当 FOR
命令在批处理文件中使用时,语法略有不同,需要额外的百分号,即 %%I
。这是编写脚本时非常常见的错误来源。如果您的 for 循环因语法无效而退出,请确保使用 %%
格式的变量。
- 命令行中的 FOR 命令: 在命令行直接使用 FOR 命令时,使用单个 % 来表示变量。例如:
FOR %I IN (1,2,3) DO ECHO %I
- 批处理文件中的 FOR 命令: 在批处理文件中使用 FOR 命令时,需要使用两个 %% 来表示变量。例如:
@ECHO OFF
FOR %%I IN (1,2,3) DO ECHO %%I
原因:
- 在命令行中,% 符号直接由命令解释器处理。
- 在批处理文件中,% 符号通常用于表示变量,所以需要额外的 % 来告诉命令解释器它是 FOR 命令的一部分,而不是普通变量。
避免错误提示:
- 在批处理文件中的 FOR 循环中,始终使用 %% 格式的变量,例如 %%I、%%J 等。
- 如果遇到 "FOR 语法无效" 的错误,请检查是否使用了正确的变量格式。
小贴士:
- 在批处理文件中,可以使用 SETLOCAL 命令来启用延迟变量扩展,这可以避免一些变量处理问题。
- 在编写复杂的 FOR 循环时,可以使用 ECHO 命令来显示变量的值,以便调试。
for 的应用
以下是批处理脚本中常用的循环遍历文件和目录的命令:
1. 循环遍历文件:
FOR %I IN (%USERPROFILE%\*) DO @ECHO %I
这会循环遍历 %USERPROFILE%
目录(通常为用户主目录)下的所有文件,并使用 ECHO
命令打印每个文件的路径。
2. 循环遍历目录:
FOR /D %I IN (%USERPROFILE%\*) DO @ECHO %I
这会循环遍历 %USERPROFILE%
目录下的所有目录,并使用 ECHO
命令打印每个目录的路径。
3. 递归遍历文件:
FOR /R "%TEMP%" %I IN (*) DO @ECHO %I
这会递归遍历 %TEMP%
目录下的所有子文件夹,并打印每个文件的路径。 /R
选项表示递归遍历,%TEMP%
表示要遍历的目录。
4. 递归遍历目录:
FOR /R "%TEMP%" /D %I IN (*) DO @ECHO %I
这会递归遍历 %TEMP%
目录下的所有子目录,并打印每个目录的路径。 /D
选项表示仅遍历目录。
解释:
FOR
命令用于循环遍历文件或目录。%I
是循环变量,它会在每次迭代中被赋值为一个文件或目录的名称。IN
关键字后面指定要遍历的文件或目录集合。DO
关键字后面指定要执行的命令。@ECHO %I
会打印当前文件或目录的路径。/R
选项表示递归遍历子文件夹。/D
选项表示仅遍历目录。
注意:
- 在批处理文件中,
FOR
命令中的变量需要使用两个百分号(%%
)表示,例如%%I
。 - 递归遍历可能会导致循环过多,请谨慎使用。
返回码
根据惯例,命令行执行成功时应返回零,失败时应返回非零值。警告消息通常不会影响返回代码。重要的是脚本是否正常工作。
以下是更详细的解释:
- 返回代码 0: 表示成功执行。这是最常见的返回代码,表示命令或脚本按预期完成所有任务。
- 非零返回代码: 表示执行失败。存在各种非零返回代码,每个代码通常代表特定的错误或问题。例如,返回代码 1 通常表示通用错误,而返回代码 127 通常表示找不到命令。
- 警告消息: 通常不会影响返回代码。警告消息用于提醒用户潜在问题或异常情况,但它们并不一定表示失败。
返回代码对于脚本和命令行工具的自动化至关重要。 它们允许脚本和工具相互通信并报告成功或失败。例如,如果一个脚本调用另一个命令行工具,它可以检查返回代码以确定该工具是否成功执行。
在脚本命令中检查返回代码
环境变量 %ERRORLEVEL%
包含上一个执行的程序或脚本的返回代码。一个非常有用的功能是内置的 DOS 命令,例如 ECHO
、IF
和 SET
,会保留 %ERRORLEVEL%
的现有值。
使用 IF
命令的 NEQ
(不等于)运算符检查非零返回代码的常规技术:
IF %ERRORLEVEL% NEQ 0 (
REM 在这里做一些事情来解决错误
)
另一种常见的技术是:
IF ERRORLEVEL 1 (
REM 在这里做一些事情来解决错误
)
当返回代码是任何大于或等于 1 的数字时,ERRORLEVEL 1
语句为真。但是,不建议使用这种技术,因为程序可以返回负数以及正数。大多数程序很少记录所有可能的返回代码,因此宁可使用 NEQ 0
样式显式检查非零值,也不要假设返回代码在出现错误时将为 1 或更大。
您可能还想检查特定的错误代码。例如,您可以通过简单地调用程序并检查返回代码 9009 来测试可执行程序或脚本是否在您的 PATH 中。
SomeFile.exe
IF %ERRORLEVEL% EQU 9009 (
ECHO error - SomeFile.exe not found in your PATH
)