Windows批处理杂记

exit命令

exit命令用于退出当前的批处理脚本,并返回一个退出代码ExitCode(即返回值)。退出代码通常是一个整数,用于指示脚本执行的状态,例如成功(0)或失败(非零值)。

exit命令的语法为:

1
exit [/b] [exitCode]

其中 /b 选项表示仅退出当前批处理脚本的执行,而不会影响调用脚本的父进程。如果不使用 /b 选项,则 exit 命令会终止当前整个的命令处理器进程(CMD.EXE),并返回退出代码给调用者。

exitCode 是一个可选参数,用于指定退出代码。如果未指定退出代码,则默认返回上一个命令的退出代码。

变量延迟扩展

SETLOCAL ENABLEDELAYEDEXPANSION的作用是设置本地变量延迟扩展。

CMD在执行命令前会对脚本进行预处理,在这个过程中,如果有类似%value%这样的变量就会对其进行识别,并且查找这个变量对应的值,从而用该值替换掉变量,这个替换值的过程,就叫变量扩展,这个类似于C/C++中的宏。

请看下面示例:

1
2
3
@echo off
set a=4
set a=5 & echo %a%

结果:4

为什么输出是4而不是5呢?

原因是批处理是按行读取命令的(另外例如for命令等,其后用一对圆括号闭合的所有语句也当作一行),在执行之前要完成必要的预处理工作,这其中就包括对该行命令中的变量赋值。上例中,批处理在运行到set a=5 & echo %a%之前,先把这一整句读取并做了预处理(对变量a赋了值),此时%a%当然就是4。

明白这个道理之后,我们将上例修改为如下方式,就可以输出结果5。

1
2
3
4
@echo off
set a=4
set a=5
echo %a%

为了能够让批处理感知变量的动态变化,批处理设计了变量延迟,即在读取了一条完整的语句之后,不立即对该行的变量赋值,而会在单条语句执行之前再进行赋值,也就是说“延迟”了对变量的赋值。

1
2
3
4
@echo off
SETLOCAL ENABLEDELAYEDEXPANSION
set a=4
set a=5 & echo !a!

结果:5

由于启动了变量延迟,得到了输出结果5。

变量延迟的启动语句是SETLOCAL ENABLEDELAYEDEXPANSION,并且变量要用一对叹号括起来,否则就没有变量延迟的效果。

调用npm命令无法返回原批处理

需要使用call命令,如:

1
call npm install

可以使用%errorlevel%获取命令的执行结果(通常0表示成功),如:

1
2
3
4
5
6
call npm run build-release
if %errorlevel% NEQ 0 goto ERROR


:ERROR
echo Compile Failed

切换脚本的当前目录

有些命令依赖批处理程序的当前目录,如npm、yarn这样的命令。假如项目位于D:\A\B路径,使用批处理(文件位于D:\build.bat)进行自动构建:

1
2
3
@echo off
cd D:\A\B
call npm run build

运行上面批处理,构建会失败,通常会提示诸如“D:\package.json不存在”的错误,原因是npm命令是批处理程序的当前目录查找package.json文件的,批处理程序的当前目录默认为批处理文件所在的目录,即D:\D:\package.json不存在,因此构建失败。

可以使用为cd命令指定/d参数来切换批处理程序的当前目录,上面示例可以修改为:

1
2
3
@echo off
cd /d D:\A\B
call npm run build

批处理命令换行

当批处理命令过长,如果都写在一行不便于阅读,可以使用^符号进行分割。

1
2
3
4
5
6
call "%qt_src_folder%\configure.bat" -silent -debug-and-release -force-debug-info -strip ^
-platform win32-msvc -static -static-runtime -no-opengl -no-dbus -no-icu ^
-nomake examples -nomake tests -skip qtwebengine -skip qtlocation ^
-qt-harfbuzz -qt-freetype -qt-zlib -qt-doubleconversion ^
-mp -optimize-size -ltcg -no-pch ^
OPENSSL_LIBS="-lUser32 -lAdvapi32 -lGdi32 -lWS2_32 -lCRYPT32 -llibcrypto32 -llibssl32"

ECHO 处于关闭状态

遇到“ECHO 处于关闭状态”提示,通常是因为输出变量为空导致。

此时需要注意如下情况:

  • 批处理变量左右不能有空格。如果有空格,空格会被当成变量名,如set a =1,变量名实际为a
  • 开启变量延迟扩展后,引用变量需要使用两个!的方式,如!str_a!

显示选择项提示用户选择

使用CHOICE命令可以提供用户在指定的选项中选择一项,并使用%ERRORLEVEL%获取选择的值。

CHOICE命令语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
CHOICE [/C choices] [/N] [/CS] [/T timeout /D choice] [/M text]

描述:
该工具允许用户从选择列表选择一个项目并返回所选项目的索引。

参数列表:
/C choices 指定要创建的选项列表。默认列表是 "YN"。

/N 在提示符中隐藏选项列表。提示前面的消息得到显示,
选项依旧处于启用状态。

/CS 允许选择分大小写的选项。在默认情况下,这个工具
是不分大小写的。

/T timeout 做出默认选择之前,暂停的秒数。可接受的值是从 0
到 9999。如果指定了 0,就不会有暂停,默认选项
会得到选择。

/D choice 在 nnnn 秒之后指定默认选项。字符必须在用 /C 选
项指定的一组选择中; 同时,必须用 /T 指定 nnnn。

/M text 指定提示之前要显示的消息。如果没有指定,工具只
显示提示。

/? 显示此帮助消息。

注意:
ERRORLEVEL 环境变量被设置为从选择集选择的键索引。列出的第一个选择返回 1,第二个选择返回 2,等等。
如果用户按的键不是有效的选择,该工具会发出警告响声。如果该工具检测到错误状态,它会返回 255 的ERRORLEVEL 值。
如果用户按 Ctrl+Break 或 Ctrl+C 键,该工具会返回 0的 ERRORLEVEL 值。
在一个批程序中使用 ERRORLEVEL 参数时,将参数降序排列。

如:

1
CHOICE /C YNC /M "确认请按 Y,否请按 N,或者取消请按 C。"

运行外部程序

start

使用 start 命令时,当前批处理不会等待外部程序运行结束,而是继续向下执行。

使用 start 命令运行其他批处理和 windows 命令行程序时,还会创建一个新的命令行窗口,并将在新的窗口中运行新的程序,如:

1
2
3
4
5
6
7
8
9
@echo off
echo 1
start ping baidu.com
echo 2
start notepad.exe
echo 3
start other.bat
echo 4
pause

call

使用 call 命令时,当前批处理会等待外部程序或批处理运行结束,然后才继续向下执行。并且始终在当前命令行窗口中运行其他批处理和命令行程序。

直接运行

直接运行与使用 call 命令一样。但直接运行外部批处理时会破坏当前批处理的运行流程,会导致执行流程无法返回到当前批处理继续执行。

1
2
3
4
5
6
7
8
9
@echo off
echo 1
ping baidu.com
echo 2
notepad.exe
echo 3
other.bat
echo 4
pause

上述示例,执行other.bat后,无法返回到当前批处理,从而无法执行echo 4及后面语句。

获取命令的返回值

在 Windows 批处理中,%errorlevel%变量保存着上一个命令的执行结果(即main函数的返回值)。一般来说,0 表示执行成功,非 0 则表示执行失败,具体的非 0 值的含义则取决于具体的命令。

在批处理脚本中,可以根据%errorlevel%的值来进行后续的处理逻辑,例如:

1
2
3
4
5
6
7
8
9
10
11
@echo off
REM 执行一个命令
start un-exist.exe

REM 判断命令执行结果
if %errorlevel% equ 0 (
echo success
) else (
echo failed with: %errorlevel%
)
pause

运行结果如下:

1
2
3
系统找不到文件 un-exist.exe。
failed with: 9059
请按任意键继续. . .

%errorlevel%是上一个命令的执行结果,因此上面例子中的 9059 是 start 命令的结果,而不是 un-exist.exe 返回的结果。

顺序执行

Windows 批处理提供了&&&|||>>>等顺序执行方式,它们区别如下(下面说的执行成功指的是命令返回值为 0):

  • & 顺序执行多条命令,而不管前面命令是否执行成功。
  • && 顺序执行多条命令,当前面命令执行出错,后面的命令将不会执行。
  • || 顺序执行多条命令,直到遇到执行成功的命令。
  • | 管道命令,将前一个命令的执行结果输出到后一个命令 如:help|more
  • > 文件写入命令,清除原文件中的内容后再写入。
  • >> 文件追加写入命令

以管理员权限运行批处理脚本

基本原理:

  • 根据 系统自带的cacls.exe 是否能够访问”%SystemDrive%\System Volume Information”文件,来判断当前批处理进程是否具有管理员权限。
  • 创建 vbs 脚本到临时目录,然后使用该脚本提权执行当前 bat 文件。

下面是批处理模板,在最后追加需要执行的脚本即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@echo off
cd /d "%~dp0"
cacls.exe "%SystemDrive%\System Volume Information" >nul 2>nul
if %errorlevel%==0 goto Admin
if exist "%temp%\getadmin.vbs" del /f /q "%temp%\getadmin.vbs"
echo Set RequestUAC = CreateObject^("Shell.Application"^)>"%temp%\getadmin.vbs"
echo RequestUAC.ShellExecute "%~s0","","","runas",1 >>"%temp%\getadmin.vbs"
echo WScript.Quit >>"%temp%\getadmin.vbs"
"%temp%\getadmin.vbs" /f
if exist "%temp%\getadmin.vbs" del /f /q "%temp%\getadmin.vbs"
exit

:Admin

rem ---------- Here is the script to be run ------------

在批处理中执行SQL

1
2
3
"%~dp0\mysql-5.6.19-win32\bin\mysql.exe" ^
-h127.0.0.1 -P31060 -uroot ^
--execute="update mysql.user SET password=PASSWORD('123456') WHERE user='root';";