之前只用过Linux的shell脚本,从来没有用过Windows的bat脚本。最近有项工作,需要用到bat脚本,于是简单学习和总结一下。初次接触,一些地方估计有错误和遗漏,有待继续深入研究。
在D盘根目录下创建 test1214.bat
文件。打开CMD窗口,在D盘根目录下,通过 test1214.bat
命令运行脚本。
顾名思义,暂停脚本,按任意键继续。如果是双击运行脚本,该命令非常有用,否则窗口一闪就没了。
pause
运行结果如下:
D:\>test1214.bat
D:\>pause
请按任意键继续. . .
类似Linux的echo命令。比如:
echo "hello world"
运行结果如下:
D:\>test1214.bat
D:\>echo "hello world"
"hello world"
注意:不需要加双引号,如果加上双引号,则双引号也会当做字符串的一部分显示出来。但是如果字符串中包含特殊字符,比如 >
,就需要加上双引号。
注意:bat脚本默认是回显脚本内容的。如果运行时不想回显脚本内容,则可用 echo off
命令关闭回显。
echo off
echo "hello world"
运行结果如下:
D:\>test1214.bat
D:\>echo off
"hello world"
注:此时 echo off
本身还是会回显,可用 @echo off
来避免回显本行。
下面的例子都用 @echo off
来关闭回显以及其本身。
如果 echo
后面不跟参数,则表示查询当前的echo状态是打开还是关闭。
@echo off
echo
运行结果如下:
D:\>test1214.bat
ECHO 处于关闭状态。
注意:如果echo一个变量,而该变量当前没有设值,则变成显示echo状态了。
@echo off
set x=
echo %x%
运行结果如下:
D:\>test1214.bat
ECHO 处于关闭状态。
echo
命令还可用于换行:
echo.
echo\
@echo off
echo aaa
echo.
echo bbb
echo\
echo ccc
运行结果如下:
D:\>test1214.bat
aaa
bbb
ccc
前面已提到,其用于不回显本行内容。
@echo off
echo "hello world"
运行结果如下:
D:\>test1214.bat
"hello world"
注释本行。
@echo off
echo aaa
rem echo bbb
echo ccc
运行结果如下:
D:\>test1214.bat
aaa
ccc
调用另一个脚本。
@echo off
echo "hello world"
call a.bat
echo "end"
文件 a.bat
内容如下:
@echo off
echo "I am A"
运行结果如下:
D:\>test1214.bat
"hello world"
"I am A"
"end"
如果没有 call
,直接调用 a.bat
,则后者运行结束之后,前者也随之结束,后面的脚本不再运行。(为何如此设计?)
@echo off
echo "hello world"
a.bat
echo "end"
运行结果如下:
D:\>test1214.bat
"hello world"
"I am A"
可见, echo "end"
这一行并没有运行。
call
命令也可用于调用一个标签,就不用 goto
过去了。注意标签处要用 exit /b
返回。详见最下面的例子。
新开一个窗口,异步运行另一个脚本文件。
@echo off
echo "hello world"
start a.bat
echo "end"
运行结果如下:
D:\>test1214.bat
"hello world"
"end"
注意会新开一个窗口来运行 a.bat
,并且是异步运行的,不会等其结束, echo "end"
就会立即运行。
如果要同步运行(等待其运行结束),则需要加上 /wait
选项:
start /wait a.bat
则第二个窗口关闭(不是被调用的脚本运行结束)之后,第一个脚本才继续运行。
使用 /b
选项可以不打开新窗口:
@echo off
echo "hello world"
start /b a.bat
echo "end"
运行结果如下:
D:\>test1214.bat
"hello world"
"end"
D:\>"I am A"
虽然没有打开新窗口,貌似还是打开了一个新shell,因为此时可以用 exit
退回到上一层的shell。
脚本的命令行参数。注意 %0
代表的是脚本本身。
@echo off
echo "hello world"
echo %0
echo %1
echo %2
echo %3
echo "end"
运行结果如下:
D:\>test1214.bat aaa bbb
"hello world"
test1214.bat
aaa
bbb
ECHO 处于关闭状态。
"end"
因为 %3
没有值,所以相当于查看 echo
状态,显示 ECHO 处于关闭状态
。
让用户选择一个选项,最常见的就是选择 Y
或者 N
。
@echo off
choice
运行结果如下:
D:\>test1214.bat
[Y,N]?
此时,用户需要输入 Y
或者 N
(不区分大小写),比如:
D:\>test1214.bat
[Y,N]?Y
用户的选项会保存在环境变量 errorlevel
里。第1个选项值为1,第2个选项值为2,因此类推。
注意:不要显式设置 errorlevel
变量的值!否则 errorlevel
变量会一直是这个值!如果已经设置了,则可以用 set errorlevel=
来清除其值,就OK了。
@echo off
choice
echo You choice is: %errorlevel%
运行脚本,并输入 Y
,如下:
D:\>test1214.bat
[Y,N]?Y
You choice is: 1
可以通过 /c
选项来定制可选值,并通过 /m
提供一个消息,比如:
@echo off
choice /c XYZ /m "You must make a choice:"
echo You choice is: %errorlevel%
运行脚本,并输入 Y
,如下:
D:\>test1214.bat
You must make a choice: [X,Y,Z]?Y
You choice is: 2
还可以使用 /t
倒计时,并通过 /d
提供默认选项。
@echo off
choice /t 5 /d y
echo You choice is: %errorlevel%
运行脚本,不需要输入,等待5秒,脚本就会自动帮你选择 y
:
D:\>test1214.bat
[Y,N]?Y
You choice is: 1
注:可用 choice /?
查看 choice
的帮助信息。
设置变量。使用变量时需要用 %
包起来,例如:
@echo off
set x=good
echo %x%
注意:等号两边不要有空格!如果左边有空格,则实际上并没有赋值。
运行结果如下:
D:\>test1214.bat
good
注意:在脚本里set的变量,在脚本运行结束后仍然生效:
D:\>echo %x%
good
当然在下次运行脚本时,也仍然生效(在同一个窗口运行脚本)。
这一点是和Linux脚本不同的。Linux脚本的变量,默认只在子shell(运行时会开启一个子shell)中生效。当然,可以用 source
(即 .
)命令使之在当前shell中运行。
这个特点在调试bat脚本时,经常带来很大的困扰,比如实际并没有set进去,但是echo其值时,还能显示出早前set的值,导致我以为等号左边可以加空格。
清除变量,用 set x=
即可。
判断变量是否为空值,可用 "%x%" == ""
来判断。
@echo off
set x=aaa
if "%x%" == "" (echo x is null ) else (echo x is not null )
set x=
if "%x%" == "" (echo x is null ) else (echo x is not null )
运行结果如下:
D:\>test1214.bat
x is not null
x is null
/p
选项用来接收用户输入:
@echo off
set /p x="Please input a value:"
echo The value is: %x%
运行脚本,并输入 haha
:
D:\>test1214.bat
Please input a value:haha
The value is: haha
注意 choice
和 set /p
都可以和用户交互,前者是提供选项,后者则是任意输入。
/a
选项会把右边先求值,再赋值:
@echo off
set x=1
set y=2
set /a z=%x% + %y% + 3
echo %z%
运行结果如下:
D:\>test1214.bat
6
直接看例子:
@echo off
choice /c XYZ /m "You must make a choice:"
echo %errorlevel%
if %errorlevel% == 1 (
echo XXX ) else if %errorlevel% == 2 (
echo YYY ) else if %errorlevel% == 3 (
echo ZZZ ) else (echo Something is wrong )
注意格式,比如哪里可以换行,再比如括号两边的空格。如果格式不对脚本会出问题。
运行脚本,输入 z
,如下:
D:\>test1214.bat
You must make a choice: [X,Y,Z]?Z
3
ZZZ
/i
选项表示不区分大小写,比如:
@echo off
set /p x="Please input a value:"
echo %x%
if /i %x% == aaa (echo yes ) else (echo no )
运行脚本,输入 aAa
,则会打印 yes
:
D:\>test1214.bat
Please input a value:aAa
aAa
yes
同理,输入 AAA
、 aaa
等,效果一样的。
如果没有 /i
,则只能匹配 aaa
。
常见的关系操作:
equ
:相等neq
:不等gtr
:大于geq
:大于等于lss
:小于leq
:小于等于@echo off
set x=123
if x equ 100 (echo "x = 100 yes" ) else (echo "x = 100 no" )
if x neq 100 (echo "x != 100 yes" ) else (echo "x != 100 no" )
if x gtr 100 (echo "x > 100 yes" ) else (echo "x > 100 no" )
if x geq 100 (echo "x >= 100 yes" ) else (echo "x >= 100 no" )
if x lss 100 (echo "x < 100 yes" ) else (echo "x < 100 no" )
if x leq 100 (echo "x <= 100 yes" ) else (echo "x <= 100 no" )
运行结果如下:
D:\>test1214.bat
"x = 100 no"
"x != 100 yes"
"x > 100 yes"
"x >= 100 yes"
"x < 100 no"
"x <= 100 no"
not
表示“非”,貌似没有“与”和“或”。
@echo off
set x=123
if not x gtr 100 (echo "not x > 100 yes" ) else (echo "not x > 100 no" )
运行结果如下:
D:\>test1214.bat
"not x > 100 no"
有优先级吗?有待研究。
直接看例子:
@echo off
set list=a b c d e
for %%i in (%list%) do (
echo %%i
)
运行结果如下:
D:\>test1214.bat
a
b
c
d
e
特别要小心的是赋值。
@echo off
set list=a b c d e
set x=hello
for %%i in (%list%) do (
echo %%i
set x=%%i
echo %x%
)
运行结果如下:
D:\>test1214.bat
a
hello
b
hello
c
hello
d
hello
e
hello
set x=%%i
貌似并没有生效,这是为什么呢?
我也不太明白,总之如果想要这么做,需要先设置一个参数: setlocal enabledelayedexpansion
,同时要用 !
把变量包起来。
@echo off
set list=a b c d e
set x=hello
setlocal enabledelayedexpansion
for %%i in (%list%) do (
echo %%i
set x=%%i
echo !x!
echo %x%
)
运行结果如下:
D:\>test1214.bat
a
a
hello
b
b
hello
c
c
hello
d
d
hello
e
e
hello
可见, echo !x!
打印的是新值,而 echo %x%
打印的还是旧值,不知道为什么,有待研究。
goto
和标签一起使用。
@echo off
echo start
set x=0
:label1
echo %x%
set /a x= %x% + 1
if %x% lss 5 ( goto label1 )
echo end
运行结果如下:
D:\>test1214.bat
start
0
1
2
3
4
end
不要直接 exit
,否则会连同CMD窗口一起关闭。
常见做法是加上 /b
选项,可以传递一个返回值(用 errorlevel
变量接收)。
@echo off
echo hello
exit /b 1
运行结果如下:
D:\>test1214.bat
hello
此时查看 errorlevel
变量:
D:\>echo %errorlevel%
1
也可以不设置返回值,直接 exit /b
返回,则 errorlevel
变量的值不受影响。
下面的例子是简单的“结构化”的小脚本:
@echo off
echo Start
choice /c AB /m "Choose A or B:"
if %errorlevel% == 1 (
echo Going to labela
goto labela ) else if %errorlevel% == 2 (
echo Going to labelb
goto labelb ) else (
echo Something is wrong
exit /b 1
)
:labelend
echo End
exit /b
:labela
echo A
rem do something
goto labelend
:labelb
echo B
rem do something
goto labelend
运行脚本,输入 a
,如下:
D:\>test1214.bat
Start
Choose A or B: [A,B]?A
Going to labela
A
End
运行脚本,输入 b
,如下:
D:\>test1214.bat
Start
Choose A or B: [A,B]?B
Going to labelb
B
End
下面是一个完整的小例子。该脚本对2个list做处理。
@echo off
echo Start
echo.
set list1=a b c
set list2=x y z
echo Handling list1
setlocal enabledelayedexpansion
for %%i in (%list1%) do (
echo Looping %%i start
set x=%%i
call :label1
echo Looping %%i end
)
echo.
echo Handling list2
for %%i in (%list2%) do (
echo Looping %%i start
set x=%%i
call :label2
echo Looping %%i end
)
echo.
echo End
exit /b
:label1
echo Handling !x! in label1
rem do something
echo Done !x! in label1
exit /b
:label2
echo Handling !x! in label2
rem do something
echo Done !x! in label2
exit /b
运行结果如下:
D:\>test1214.bat
Start
Handling list1
Looping a start
Handling a in label1
Done a in label1
Looping a end
Looping b start
Handling b in label1
Done b in label1
Looping b end
Looping c start
Handling c in label1
Done c in label1
Looping c end
Handling list2
Looping x start
Handling x in label2
Done x in label2
Looping x end
Looping y start
Handling y in label2
Done y in label2
Looping y end
Looping z start
Handling z in label2
Done z in label2
Looping z end
End