文章目录
- 什么是BAT
- 常用命令与语法
- help与/?
- title
- color
- mode
- echo
- @
- pause
- call
- rem
- set
- /a
- /p
- goto
- start
- if
- if errorlevel
- for
- 普通用法
- `for /l` 用法
- `for /d`用法
- `for /r`用法
- `for /f`用法
- in (file)
- delims和tokens
- skip
- eol
- usebackq
- 变量扩展
- 变量延迟
- setlocal
- shift
- dir
- rd(删除文件夹)
- del(删除文件)
- pushd与popd
- type
- find
- assoc
- ftype
- 特殊符号篇
- %
- 用途一:批处理参数
- 用途二:引用变量
- *、?(通配符)
- <、>、>>(重定向符)
- ^(转义字符)
- |(管道符)
- &、&&、||(组合符)
- %~(参数扩展)
- :(截取/替换字符串)
- 截取
- 替换
- "" (字符串界定符)
- , (空格的替代符)
- ; (参数并列符)
- ()
- !
- nul
- 其他常用命令汇总
- 与操作系统的交互命令
- explorer
- 参考文献
什么是BAT
全称即Batch,批处理,是一类可执行的文本文件,扩展名为.bat
。
常用命令与语法
help与/?
都可以用来查看某个指令的帮助文档。
语法格式:指令 /?
或者是help 指令
如title /?
,就是打印title
这个指令的帮助文档。
需要注意,单独执行help
,会打印出支持的所有指令以及简单的功能说明。
title
语法格式:title window_name
作用设置当前的cmd窗口标题
color
语法格式:color 16进制颜色格式
用来设置批处理控制台的背景和字体颜色,颜色属性由两个十六进制数字组成,第一个数字表示背景颜色,第二个数字表示字体颜色。
每个数字可以为以下值:
0=黑色
1=蓝色
2=绿色
3=湖蓝色
4=红色
5=紫色
6=黄色
7=白色
8=灰色
9=淡蓝色
A=淡绿色
B=淡浅绿色
C=浅红色
D=淡紫色
E=淡黄色
F=亮白色
如color 07
表示常规的黑底白字。
mode
一般用来调整当前命令行窗口的大小、代码页等。
如:
mode con cols=113 lines=15 & color 0A
就是调整窗口的长是113个字符,宽是15个字符,顺带给窗口换了换颜色,一般会放在最前面执行。
可以通过在cmd窗口中输入mode /?
来查看详细功能。
echo
语法格式:
echo [{on|off}] [message]
- on:允许批处理文件在处理时回显命令;
- off:不允许。
在调用了echo off
之后,接下来执行的所有命令都将不会在前台打印出命令本身。
举个例子,现在有一个a.bat,其内容为:
echo on
dir
echo 111111
这时候执行a.bat,前台得到的输出是:
D:\TEMP>a.bat
D:\TEMP>echo on
D:\TEMP>dir
驱动器 D 中的卷是 Data
卷的序列号是 20FB-E397
D:\TEMP 的目录
2023/07/12 23:26 <DIR> .
2023/07/12 23:26 <DIR> ..
2023/07/12 23:48 27 a.bat
2021/10/17 20:40 <DIR> U盘旧资料
1 个文件 27 字节
3 个目录 14,122,348,544 可用字节
D:\TEMP>echo 111111
111111
可以看到每行命令本身都被打印了出来。
如果我们把第一行改成echo off
,则前台得到的输出是:
D:\TEMP>a.bat
D:\TEMP>echo off
驱动器 D 中的卷是 Data
卷的序列号是 20FB-E397
D:\TEMP 的目录
2023/07/12 23:26 <DIR> .
2023/07/12 23:26 <DIR> ..
2023/07/12 23:52 28 a.bat
2021/10/17 20:40 <DIR> U盘旧资料
1 个文件 28 字节
3 个目录 14,122,348,544 可用字节
111111
可以看到,除了第一行命令外,其他命令本身都没有被打印出来。而且看着更舒服了有没有。
@
@
可以加在一行命令的行首位置,表示禁止当前行命令本身回显在屏幕上。
如:@echo on
相比使用echo on|off
来控制整个脚本层级的回显,@
可以更方便的控制行级的回显。
所以以上一小节介绍的代码为例,如果想当前屏幕完全不显示任何命令,需要写成这样:
@echo off
dir
echo 111111
前台结果:
D:\TEMP>a.bat
驱动器 D 中的卷是 Data
卷的序列号是 20FB-E397
D:\TEMP 的目录
2023/07/12 23:26 <DIR> .
2023/07/12 23:26 <DIR> ..
2023/07/13 00:02 29 a.bat
2021/10/17 20:40 <DIR> U盘旧资料
1 个文件 29 字节
3 个目录 14,122,348,544 可用字节
111111
这个结果看起来就舒服多了。
pause
用于暂停批处理的执行,由用户决定是否继续执行。
echo 111
pause
echo 2222
前台在执行完第一句代码之后,就会显示"按任意键继续。。",同时卡住不再继续执行,除非用户点击了任意键之后,才会再执行第三句代码。
比较有意思的是,如果你不想让界面显示"按任意键继续。。",但还想要让界面卡住不向下执行,则可以结合重定向符来实现,比如:
@echo off
echo 1
pause
echo 2
pause > nul
echo 3
有兴趣可以试试,输出2之后,光标会卡住,但界面上没有显示,除非按任意键,才能继续向下执行。
这个本质是将pause指令的输出重定向到了黑洞里,即nul。
call
可以用来调用其他的批处理文件,语法是:
call xxx.bat
这里的作用跟start基本一致。
也可以实现类似goto的效果,调用执行当前bat中,指定标号后面的命令,直到批处理结尾,
语法格式:
call :label_name [arguments]
比如:
@echo off
call :x 参数
echo 111
:x
echo 222
echo %1
打印结果:
D:\TEMP>a.bat
222
参数
111
222
ECHO is off.
可以看到,call在跳转执行完之后,又会返回自己的位置,接着向下执行。
需要注意,call后面的标号,必须加上冒号,即:label
。
感觉没有goto好用,所以个人感觉,call的最大作用,就是用来调用其他的批处理文件。不过call带参数的这个设定,倒是有点像函数了。
rem
用来注释命令,语法:
rem 注释内容
需要注意,如果你设置了echo on
的话,注释也会显示在前台输出里。
set
用来设置变量的,语法:
set [options] var_name='var_value'
不过如果在cmd里执行help set
之后,打印出来的set的命令帮助里,set的功能其实是显示、设置或删除cmd.exe环境变量
。
set命令里,提供有两个命令行开关,即options:
set /a expression
set /p variable=[promptString]
/a
/a
:指定等号右边的字符串为被评估的数字表达式,即执行简单的算术运算或者逻辑位与运算等,并将结果赋值回变量,如:
@echo off
set /a var=1+2
echo %var%
set /a var=2*4
echo %var%
set /a var=100
echo %var%
set /a var="这是什么"
echo %var%
依次的输出为:
D:\TEMP>a.bat
3
8
100
0
注意等号右边需要是数字表达式,起码也得是数字,看最后一个示例,传进来的是字符串,直接返回0了。
额外提一句,bat中支持运算的数值范围是(-2147483647, 2147483647),举例:
set /a var=2147483647+1
echo %var%
输出:-2147483648
注意:2147483647 = 2^31-1,是32位操作系统中最大的符号型整型常量。
/a
用来做逻辑位与运算的时候,运算符应该用双引号括起来,要不然会报错,比如说:
set /a var=3"&"2
echo %var%
/p
/p
:允许将变量设置成用户输入的一行输入,读取输入之前,可以显示指定的提示信息,即promtString,promtString可以是空的。如:
set /p var=请输入文本:
echo %var%
执行后会打印提示信息,然后光标等待输入:
D:\TEMP>a.bat
请输入文本:
等用户键入信息,回车后,就输出为:
D:\TEMP>a.bat
请输入文本:you are hero
you are hero
还可以用来显示变量,
如set x
表示打印出所有以字母x为开头的变量。
变量名在使用的时候,需要在前后都加上百分号,如:
@echo off
set xxx = '123'
set x
echo %xxx%
echo 1111
打印输出:
D:\TEMP>a.bat
xxx = '123'
ECHO 处于关闭状态。
1111
比较有意思的有两个地方。
第一个地方,echo字符串成功了,但是echo %xxx%
失败,从结果可知,是被echo off
卡死了,也就是说echo变量的时候,是受echo off
影响的。
另外一个有趣的地方,是set x
。
如果我保持同一个cmd不关闭,在执行完a.bat之后,再在这个cmd中,执行一个b.bat文件,其内容为:
@echo off
set xxx_2 = '456'
set x
这时候打印的输出为:
D:\TEMP>b.bat
xxx = '123'
xxx_2 = '456'
可以看到,刚才a.bat声明的变量也在这里被打出来了。
所以我感觉,set x
的变量空间,应该是指在同一个cmd内。
goto
goto是一个流程控制转向功能,早期的编程语言很多都支持goto语句,但是后来由于goto跳转带来的debug不确定性,慢慢的,后面的语言都不支持goto了。
语法格式:
goto 某个label
:某个label
注意label是有长度限制的,不能超过8个字符,如果超过,则只会取前8个字符。
看着功能跟call差不多,但还是有些区别的:
- call在跳转执行完label代码后,会再跳回原来的位置,继续向下执行代码,所以label处代码是会被执行两次;但goto在跳转执行完label代码后,就会继续向下执行,不会跳转回去。
如以下代码:
@echo off
goto last
echo 111
:last
echo 222
打印输出为:
D:\TEMP>a.bat
222
start
start命令,启动一个新的cmd窗口,并在其中执行指定的命令。
语法格式:
start [title] [/dPath] [/i:] [/min] [/max] [{/separate | /shared}] [/wait] [{/low | /normal | /high | /realtime | /abovenormal | belownormal}] [/B]
- title:可选项,表示新的cmd窗口的标题,注意需要用双引号括起来;
- /dPath:表示路径;
- [/i:]:表示将当前cmd的环境,传送给新的cmd窗口;
具体啥作用不清楚,我原先以为是把变量空间传过去,但刚才测试了一下,不加/i,新的窗口仍然可以调用老cmd里定义的变量
- [/min]:将新cmd窗口最小化;
- [/max]:将新cmd窗口最大化;
- [{/separate | /shared}]:前者表示在单独的内存空间启动16位程序,后者表示在共享的内存空间启动。
没懂是啥意思
- [/wait]:启动应用程序,并等待它结束(新cmd窗口被关闭)后,才会继续向下执行;
- [{/low | /normal | /high | /realtime | /abovenormal | belownormal}]:启动应用程序的优先级,依次是空闲优先级、一般优先级、高优先级、实时优先级、超常规优先级、低出常规优先级;
- [/B]:表示启动应用程序时不必打开新的cmd窗口
注意,参数啥的要放在start后面,如start /min [title]
简单举个例子,比如说打开记事本:start notepad
if
常用分支控制结构
语法格式:
if [not] 条件1 括号包起来的方法体1 [else 方法体2]
if [not] exist filename 方法体1
if [not] errorlevel number 方法体1
if [/i] string_1 compareop string_2 方法体1
if defined variable 方法体1 [else 。。。]
if "a"=="a" ()
errorlevel number 似乎是指上一条命令的运行返回状态。
[/i]
表示强制比较,意思是忽略大小写。
常用的compareop有:
- EQU:等于
- NEQ:不等于
- LSS:小于
- LEQ:小于等于
- GTR:大于
- GEQ:大于等于
比较数字和字符都是通过上面的比较符,bat里不支持<或者>这种运算符,因为这在bat里是重定向符。
估计bat是将所有的变量都视为是string型了。
if defined variable 方法体1 [else 。。。]
用来判断变量是否已经定义了;
注意:
- if的方法体需要用括号包起来。
- 方法体的左括号必须跟在代码的行末,且else必须跟if方法体的右括号在同一行;
举例:
@echo off
if defined xxx (echo 123) else (echo 456)
会输出456,。
但是如果方法体不用括号包起来,也能跑,但是啥输出都没有。
格式举例:
@echo off
if abc EQU bcd (
echo 11
) else (
echo 22
)
[/i]
表示强制比较,意思是忽略大小写。
举例:
@echo off
if /i abc EQU ABc (
echo 11
) else (
echo 22
)
打印11。如果把/i
取消掉,那么打印的是22。
判断文件是否存在:
if exist "c:\test" (echo 存在文件)
if errorlevel
语法格式为:if [not] errorlevel number 方法体1
如果上面最近一条命令返回一个等于或大于指定数字的退出代码,则指定条件为true。这个errorlevel是一个系统变量,里面会保存上一条命令的执行状态(执行完毕后的退出代码,即0~255之间)。
一般来讲,退出代码等于0,表示命令正确执行成功;
如:
net user
if %errorlevel% == 0 echo 命令执行成功!
输出:
D:\TEMP>a.bat
User accounts for \\LAPTOP-94Qxxxxx
-------------------------------------------------------------------------------
Administrator DefaultAccount Guest
wxx WxxxxxxxlityAccount
The command completed successfully.
命令执行成功!
再补一个例子,就不追加输出了:
set /p var=请输入一个命令:
%var%
if %errorlevel% == 0 goto yes
:yes
echo %var%执行成功
for
for是bat里很强大,也是很复杂的一个命令。建议看一下参考文献10,介绍的很详细。
基本语法:
for [/l | /d | /r | /f | ] {%variable | %%variable} in (set) do (
方法体
)
%variable
是在命令提示符下调用for循环时,定义变量的方式,即在cmd中直接写代码时的用法,注意这里变量是区分大小写的;
%%variable
是在批处理中调用for循环时,定义变量的方式,注意这里变量也是区分大小写的。我们这里介绍的主要是BAT批处理,所以使用的就是%%variable
。
set
:指定目录、文件、或者是字符串;
参数解析:
/l
:它的set是一系列按步长划分的数字,表示for循环需要在指定范围内循环,如for /l %%n in (0, 1, 9) do ()
表示从0到9,步长为1(没错,步长是放在了中间。。。),如此共循环10次;/d
:表示只匹配目录及文件夹,不匹配文件名;/r
:表示递归,语法比较特殊,是for /r [[drive :]path] [%% | %]variable in (set) do command [command-parameters]
,/r后面指定目录,将在这个目录树的每个目录中执行for语句,如果没有指定目录,则默认为当前目录。如果set只是一个句号,即(.),则只列举目录树;/f
:灵魂参数,用于提取文本信息,如读取文件内容、提取分割字符等等等等,是专门用来操作文本字符串的。
普通用法
普通用法就是for循环后面什么参数都不跟,只是针对set集合里的元素做遍历,
语法格式例如:
for %%variable in (set) do command [command-parameters]
或者是
for %%variable in (set) do (
command [command-parameters]
)
注意的点有几个:
- set必须括号括起来,其代表一个序列,包含一个及一个以上的元素,多个元素间可以通过逗号、制表符、空格、分号或者等号分割;
- for会循环将set里的值赋给
%%variable
,直到set里面的值遍历完毕后,for循环才结束; - 为了避免跟命令行传参冲突,
%%variable
应该尽量避免定义成%%0~%%9这种形式;
举一个实际例子:
@echo off
for %%a in (1,2,3,4) do (
echo %%a
)
输出结果:
D:\Code\BAT>a.bat
1
2
3
4
再举一个例子:
举例,打印当前目录下的所有txt文件的内容:
@echo off
set /a sum=0
for %%x in (*.txt) do (
echo %%x文件内容如下:
type %%x
echo.
set /a sum=sum+1
)
echo 一共打印了%sum%个文件
打印输出:
D:\Code\BAT>a.bat
a.txt文件内容如下:
这是a.txt
b.txt文件内容如下:
这是b.txt
c.txt文件内容如下:
这是c.txt
一共打印了3个文件
其中,echo.
表示换行。。。。
for /l
用法
/l
更像是编程里常规意义上的for循环,是带有计数器的,其中,set里表示一个步长序列,其语法为:
for /l %%variable in (start, step, end) do command
这里的/l
,就是loop的意思。
以(1,1,5)
为例,表示从1到5生成一个步长为1的序列,即1,2,3,4,5;
以(5,-1,1)
为例,表示从5到1,生成一个步长为-1的序列,即5,4,3,2,1;
简单举个例子:
@echo off
for /l %%a in (8, -2, 0) do (
echo %%a
)
打印结果:
D:\Code\BAT>a.bat
8
6
4
2
0
for /d
用法
加上/d参数之后,set集合里放的应该就是一个路径,for循环将遍历这个路径下的所有文件夹,但是不会去深入遍历文件夹下的子文件夹,也不会去遍历出单个文件。
所以这里的/d
其实就是directory。
以目录D:\Code\BAT
为例,其下的文件结构如下:
D:\Code\BAT
-- d1/
-- dd1/
-- d2/
-- d3/
-- a.txt
-- b.txt
执行代码:
@echo off
for /d %%a in (D:\Code\BAT\*) do (
echo %%a
)
输出:
D:\Code\BAT>a.bat
D:\Code\BAT\d1
D:\Code\BAT\d2
D:\Code\BAT\d3
注意:
- set里记录的路径最后面,一定要带上星号,代表目录下的所有,不然不输出;
- set里面同样支持多个路径并列,多个路径之间可以用逗号、空格等隔开,形如
for /d %%a in (D:\Code\BAT\* D:\Code\ttss\*) do
,for将按顺序一个路径一个路径遍历;
for /r
用法
/r
参数跟/d
功能近似,主要是应用于文件遍历,不能遍历文件夹。
/r
用于搜索指定路径及其子目录下符合情况的文件,如果后面没有指定路径的话,默认用当前目录。它的语法有点特殊:
for /r [path] %%variable in (模式匹配) do ()
注意:
- 路径是直接跟在/r参数后面的,而不是放在set里;
- set里放的是待匹配的pattern,比如说
(*.txt)
表示所有的txt文件 ;
常用功能:匹配指定模式的文件,统一改名。
以目录D:\Code\BAT
为例,其下的文件结构如下:
D:\Code\BAT
-- d1/
-- dd1/
-- ddd1.txt
-- d2/
-- d3/
-- a.txt
-- b.txt
-- c.txt
执行代码:
@echo off
for /r D:\Code\BAT\ %%a in (*.txt) do (
echo %%a
)
打印结果:
D:\Code\BAT>a.bat
D:\Code\BAT\a.txt
D:\Code\BAT\b.txt
D:\Code\BAT\c.txt
D:\Code\BAT\d1\dd1\ddd1.txt
for /f
用法
/f
是for里面用来处理跟字符串相关的工作,如文件内容、命令的返回内容等。
语法格式:
for /f ["options"] %%variable in (file) do command
for /f ["options"] %%variable in ('command') do command
for /f ["options"] %%variable in ("string") do command
注意:
第一种语法里:
- for读取文件里的内容,并以行为单位循环处理,每次处理一行,但是会忽略空行,以及分号打头的行;
- 默认情况下,即不使用options额外配置的情况下,每行会以默认的分隔符(空格和制表符)再度分割为第1小节、第2小节等等,然后只返回第1小节;
第二种语法里:
- 注意set里的字符串是一个完整的命令,是需要单引号括起来的,整个for循环其实是循环这个命令的输出结果;
第三种语法里:
- 注意set里的字符串是通过双引号括起来的,for循环是循环这个字符串 ;
接下来只以in (file)
为例,讲解for /f
里常用的options。in ("string")
和in ('command')
用法跟它差不多,所以就不赘述了。
in (file)
假设现在有一个a.txt,其内容为:
11111
22 222
333;33
4444=4
;55555
=66666
77777
88888
99999
让我们执行代码:
@echo off
for /f %%a in (D:\Code\BAT\a.txt) do (
echo %%a
)
打印结果为:
D:\Code\BAT>a.bat
11111
22
333;33
4444=4
=66666
77777
88888
99999
可以看到:
- 第2行由于行内有空格,所以只返回了空格前的这一部分;
- 第4行由于是空行,所以没有输出;
- 第6行由于行首是分号,所以也没有输出,但以等号、空格作为行首的第七八行却正常输出;
对于第2行这种情况,如果想拿到每行切出来的所有内容,或者是想让每行按照自己想要的方式切割,可以使用delims和tokens这两个options。
delims和tokens
delims
用来定义每行的分隔符,默认是使用空格和制表符作为分隔符,但是要注意delims=''
是相当于保持默认,而不是表示什么分隔符都不加;
tokens
用来控制每行返回切割后的哪些列,如:
tokens=2-5
表示返回第2列到第5列,- 或者是列举式的,如
token=2,3,4,5
; token=2,*
表示返回第二列到最后一列;- 如果需要全部显示,则
token=*
,只需要一个变量来接收即可。
但是我们在for循环里只定义了一个变量,即%%variable,如何用这一个变量来拿到每行切出来的所有列呢?
这个其实也简单,可以这么写:
for /f "tokens=1,2,3" %%a in (a.bat) do (
echo %%a,%%b,%%c
)
因为我们的变量定义的是%%a,所以如果想取到其他的列,那就需要按照英文字母顺序继续向下排,依次是%%b,%%c等等。
那如果字母排完了呢?有兴趣再查吧,一般情况下是用不完。。。
2023-9-16 10:13:37 有一个比较有趣的事情,就是如果只加delims,而不指定tokens,可以猜一下会输出什么?
我们现在有一个test.txt文件,其文件内容如下:
how are you?
I'm fine, and you?
I'm fine, thank you.
bye
然后代码如下:
@echo off
for /f "delims=," %%a in (test.txt) do (
echo %%a
)
猜猜结果是怎样的?
输出如下:
D:\TEMP>a.bat
how are you?
I'm fine
I'm fine
bye
可以看到,如果不指定token的话,是只返回第一个分隔符前面的部分,实际上是等价于for /f "delims=, tokens=1" %%a
,这个可不是因为我们只定义了一个变量去接收值哈,你就算在echo的时候加入了%%b也是没效果的。
skip
忽略前n行。
语法格式:skip=n
,表示跳过第1行到第n行,从第n+1行开始读取,默认n=0
举个例子:
for /f "skip=6 token=2-5 delims=''" %%a in (a.bat) do ()
eol
用来指定,当某一行行首以什么符号开始时,就忽略它。
比如说,eol=#
,表示以#开头的行都忽略掉。
需要注意,eol只能指定单个字符,不能指定多个!
usebackq
这是一个增强型参数,用处不大,就是改变了对引号的使用限制。
- 对于command,由单引改为反引号,即in (`command`)
- 对于字符串,由双引号改成单引号,即in (‘string’);
- 对于文件路径,原来是啥引号都不加,现在是改成双引号括起来。
仅做了解吧。
即for /f "usebackq" %%i in ....
变量扩展
%%~a
,即%%a
的扩展,可以删除双引号。
直接上例子:
@echo off
for /f %%a in (temp.txt) do echo %%a
echo 扩展后输出:
for /f %%a in (temp.txt) do echo %%~a
输出为:
"111"
"222
333"
"44"444"
55"555"
'666'
扩展后输出:
111
222
333"
44"444
55"555"
'666'
引号的删除有几大原则:
- 如果字符串首尾两端都有双引号,则首尾两端的双引号都删除;
- 如果字符串首端有双引号,而尾部没有,则删除首端双引号;
- 如果字符串尾端有双引号,而首端没有,则不做任何处理。
- 只删双引号,不删单引号。
另外,for循环里也支持bat里常规的参数扩展(见下文"特殊符号篇" - "参数扩展"一节)。
如%~fa
表示文件路径名、%~da
表示文件所在的驱动器号等。
直接举例:
for /f %%a in ('dir /b') do echo %%~fa
输出为:
D:\Code\BAT\111.bat
D:\Code\BAT\a.bat
D:\Code\BAT\a.txt
D:\Code\BAT\b.bat
D:\Code\BAT\b.txt
D:\Code\BAT\c.txt
D:\Code\BAT\d1
D:\Code\BAT\d2
这里还有一个特殊的for参数扩展,即%~$PATH:i
,其中$PATH
表示名为PATH
的环境变量。这个参数扩展的意思是,在指定的环境变量里,寻找包含变量%i
的目录,并返回第一个符合情况的全路径。
如果环境变量未定义,或者没有找到符合情况的文件,就会返回空字符串。
举例:
for /f %%a in ("notepad.exe") do echo %%~$path:a
输出为:
C:\Windows\System32\notepad.exe
变量延迟
变量延迟的这个概念,其实不止在for循环中有体现,在整个bat编程中都是有涉及的。
常规的bat里是有个缺陷的,
以下面为例,
set num=0
for /f %%i in ('dir') do (
set /a num+=1
echo 当前的值是%num%
)
echo 最后的值是%num%
猜猜输出是什么,其输出结果是:
D:\TEMP>a.bat
当前的值是0
当前的值是0
当前的值是0
当前的值是0
当前的值是0
当前的值是0
当前的值是0
当前的值是0
当前的值是0
当前的值是0
当前的值是0
当前的值是0
当前的值是0
当前的值是0
当前的值是0
最后的值是15
for循环体内部的%num%
值始终未被改变,这就是一个大问题。
简单的讲,bat里面的代码,有个预编译的过程,而预编译时,代码是一块接着一块来预编译的,那什么样的代码算一块呢?比如说像set num=0
或者@echo off
这样的代码,一行就算一块。而像for循环的这种,一整个for循环算一块。比如说刚才举的那个例子,其实一共有三块代码,第一块是set num=0
,第三块是echo 最后的值是%num%
,中间的for循环则整体算作一块。
块里的代码在预编译的时候,其实是会把%variable_name%
这样的代码替换成常量,所以说上面例子里的%num%
就一直输出为0,而实际上内存里的num其实是一直在随着for循环变化的。
怎么说呢?显示早于计算,或者说同一块代码里,对同一变量,既有调整,又有输出的话,输出的值是不变的。
那如何解决这个问题呢?那就可以引入变量延迟了,举例:
setlocal enabledelayedexpansion
set num=0
for /f %%i in ('dir') do (
set /a num+=1
echo 当前的值是!num!
)
echo 最后的值是%num%
输出为:
D:\TEMP>a.bat
当前的值是1
当前的值是2
当前的值是3
当前的值是4
当前的值是5
当前的值是6
当前的值是7
当前的值是8
当前的值是9
当前的值是10
当前的值是11
当前的值是12
当前的值是13
当前的值是14
当前的值是15
最后的值是15
这下子就正常了。
上面的例子里,我们做了两处修改:一是加入了setlocal enabledelayedexpansion
来启用变量延迟功能,二是配合变量延迟,使用!variable_name!
来替换%variable_name%
或者是使用call
+ %%variable_name%%
的方式,也能达到变量延迟的效果,比如说将上述实例改写成:
set num=0
for /f %%i in ('dir') do (
set /a num+=1
call echo 当前的值是%%num%%
)
echo 最后的值是%num%
输出是一样的。
setlocal
在批处理运行过程中,设置自身的临时环境变量,不会影响系统环境变量。
命令格式:
setlocal [enableextensions | disableextensions] [enabledelayedexpansion | disabledelayedexpansion]
前一组参数表示启用/禁用命令扩展,直到与之配套的endlocal
;
第二组参数表示启用/禁用延迟环境变量扩展,直到出现与之配套的endlocal
;
举例说明:
@echo off
setlocal
path=d:
echo 局部环境变量path:
set path
endlocal
echo 全局环境变量path:
set path
打印输出:
D:\Code\BAT>a.bat
局部环境变量path:
Path=d:
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
全局环境变量path:
Path=C:\Program Files (x86)\Common Files\Oracle\Java\javapath;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;D:\software\python37\Scripts\;D:\software\python37\;C:\Users\linhui\AppData\Local\Microsoft\WindowsApps;D:\software\PyCharm2022.2.2\bin;;D:\software\IntelliJ2022.2.2\bin;;D:\software\Microsoft VS Code\bin;D:\software\jdk1.8;D:\software\jdk1.8\bin;D:\software\Git\bin;
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
可以看到在临时环境中,PATH的值被重写了。
上面的例子展示的是如何在临时环境中重置环境变量,那如果我是想在临时环境中新增环境变量的值呢,即保证原有值不变:
@echo off
setlocal
path=d:;%path%
echo 局部环境变量path:
set path
endlocal
打印输出:
D:\Code\BAT>a.bat
局部环境变量path:
Path=d:;C:\Program Files (x86)\Common Files\Oracle\Java\javapath;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;D:\software\python37\Scripts\;D:\software\python37\;C:\Users\linhui\AppData\Local\Microsoft\WindowsApps;D:\software\PyCharm2022.2.2\bin;;D:\software\IntelliJ2022.2.2\bin;;D:\software\Microsoft VS Code\bin;D:\software\jdk1.8;D:\software\jdk1.8\bin;D:\software\Git\bin;
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
可以看到,我们成功为PATH新增了一个值;
shift
shift在实际中并不常用,是用来移动批处理中,可替换参数的位置。
因为在批处理中,最多只能接收9个命令行参数,内部是通过%0~%9来调用,其中%0是指当前bat文件的文件名。
那如果我想传的命令行参数大于9个呢,比如说20个?
这时候就可以使用shift
,将每个参数复制到上一个参数中使用。
命令格式:
shift [/n]
n是数字,且只能是0~8;
当赋予n某个值的时候,就意味着命令从第n个参数开始移位,如调用一次shift /2
会将%3移动到%2,将%4移动到%3,其他依次类推,而%0和%1则不受影响
当n赋予的值是0、1或者是不带任何数字的shift
时,表示批处理文件中命令行参数整体左移一个位置,直到可替换参数为空。
举例:
@echo off
echo %0 %1 %2 %3 %4 %5 %6 %7 %8 %9
shift /0
echo %0 %1 %2 %3 %4 %5 %6 %7 %8 %9
shift /0
echo %0 %1 %2 %3 %4 %5 %6 %7 %8 %9
打印输出为:
D:\Code\BAT>a.bat 1 2 3 4 5 6 7 8 9 10 11 12
a.bat 1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9 10
2 3 4 5 6 7 8 9 10 11
可以看到,从第0位参数开始,后面命令行参数依次前移,调用一次,前移一次。
接下来我们试一下shift /1
,打印输出为:
D:\Code\BAT>a.bat 1 2 3 4 5 6 7 8 9 10 11 12
a.bat 1 2 3 4 5 6 7 8 9
a.bat 2 3 4 5 6 7 8 9 10
a.bat 3 4 5 6 7 8 9 10 11
可以看到0号参数并没有被替换。
再试一下shift
,打印输出为:
D:\Code\BAT>a.bat 1 2 3 4 5 6 7 8 9 10 11 12
a.bat 1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9 10
2 3 4 5 6 7 8 9 10 11
所以,shift
的输出效果,跟shift /0
是一样的。
但是0号参数是文件名,以调用命令行参数为例,一般我们也不会从%0开始调用,而是从%1开始调用。
基于此,shift
和shift /0
以及shift /1
的效果是一样的,我们只需要每次读取%1就可以把所有命令行参数都遍历读取完。
教程里给的例子很有趣,在这里记录一下:
@echo off
:start
if "%1" == "" goto end
echo %1
shift
goto start
:end
echo 已经结束
打印输出:
D:\Code\BAT>a.bat 1 2 3 4 5 6 7 8 9 10 11 12
1
2
3
4
5
6
7
8
9
10
11
12
已经结束
通过if + goto的形式,实现了循环的作用,很有趣。
另外别忘记,if里在做等于的判断时,两边变量都套了双引号。
dir
展示指定目录下的所有文件和文件夹,类似linux里的ls -l
dir支持很多参数,举例:
dir /a
表示以特定模式做展示,其中/ad
表示只展示文件夹,/aa
应该是表示只展示文件;
dir /b
表示以简单模式输出,即只输出文件名和文件夹名,其他一概不输出,如:
D:\Code\BAT>dir /b
a.bat
a.txt
d1
dir /s
则表示展示指定目录及其子目录下所有的文件和文件夹。
rd(删除文件夹)
语法格式:rd /参数 文件夹路径
支持的参数有:
/s
:移除指定路径下的所有目录和文件;/q
:安静模式,在删除目录的时候不会再询问是否确认删除。
如果不带参数的话,那就只能删除空目录。
del(删除文件)
语法格式:del /参数 文件路径
支持的参数有很多:
/q
:安静模式,删除文件的时候不会再询问是否确认删除;/f
:强制删除,包括只读文件;/p
:删除每个文件之前都要先问一遍/s
:在指定目录下的所有子目录下都删除指定文件;
如:
del /q/a/f/s d:\temp\*.* #删除 d:\temp 及子文件夹里面的所有文件,包括隐藏、只读、系统文件,不包括子目录
不带参数的话,不能删除隐藏、系统、只读文件。
pushd与popd
pushd 指定路径
:将当前目录存储到一个虚拟栈里,然后将当前工作目录切换成指定的路径,如pushd c:\
或者是pushd c:\users\
popd
:将当前工作目录切换成栈里保存的上一个目录,作用相当于cd 上一个目录
这里需要注意,pushd存储路径的时候是相当于用一个栈去存储的,然后popd一个一个弹出栈,直到栈里弹空了。
type
很简单的指令,用来展示文件的内容,跟linux里的cat
功能一致。
其语法格式为:
TYPE [drive:][path]filename
find
作用是查找文件中的指定字符串,常用语法格式:
find [/v][/c][/n][/i] "search_string" [drive:][path]filename
- /v:表示仅显示不包含指定字符串的行;
- /c:表示包含指定字符串的行数;
- /n:显示所展示列的行号;
- /i:搜索字符串时忽略大小写;
假设我有一个test.txt,其内容是:
how are you?
I'm fine, and you?
I'm fine, thank you.
bye
执行find "you" test.txt
,输出为:
---------- TEST.TXT
how are you?
I'm fine, and you?
I'm fine, thank you.
执行find /v "you" test.txt
,输出为:
---------- TEST.TXT
bye
执行find /c "you" test.txt
,输出为:
---------- TEST.TXT: 3
执行find /n "you" test.txt
,输出为:
---------- TEST.TXT
[1]how are you?
[2]I'm fine, and you?
[3]I'm fine, thank you.
find
在日常使用中,其实也经常跟管道符和type
结合使用,如type test.txt | find /n "you"
,输出为:
[1]how are you?
[2]I'm fine, and you?
[3]I'm fine, thank you.
可以看到这样的输出更加简洁,不会再输出文件名了。
assoc
assoc:设置文件扩展名关联的文件类型
单纯执行assoc
,会打印出当前电脑所有文件扩展名对应的文件类型,如:
.001=WinRAR
.386=vxdfile
.3g2=WMP11.AssocFile.3G2
.3gp=WMP11.AssocFile.3GP
.3gp2=WMP11.AssocFile.3G2
.3gpp=WMP11.AssocFile.3GP
.7z=WinRAR
.aa=iTunes.aa
.AAC=WMP11.AssocFile.ADTS
.aax=iTunes.aax
.accda=Access.ACCDAExtension.16
.accdb=Access.Application.16
.accdc=Access.ACCDCFile.16
.accde=Access.ACCDEFile.16
.accdr=Access.ACCDRFile.16
.accdt=Access.ACCDTFile.16
想查看指定扩展名对应的文件类型,如想看txt后缀的,可以使用:assoc .txt
,输出为:
.txt=txtfile
意思是txt后缀的,对应文件类型是txtfile。
也可以通过assoc
来修改指定扩展名对应的文件类型。
比如说已知.doc=Word.Document.8
,我现在想把.txt文件也设置成word文档文件,那么就:
assoc .txt=Word.Document.8
需要注意,这个命令需要在管理员权限下的窗口下才能运行,可能还需要重启电脑。
之后我们的txt文件就得用word文档来打开了。
assoc这个功能感觉没啥子用啊。。。。
ftype
ftype:设置文件类型的关联方式
所谓的关联方式,其实就是默认打开的软件。
使用方法跟assoc
其实基本一致。
单纯执行ftype
,会打印出本机所有文件类型对应的打开软件的路径,如:
ChromeHTML="C:\Program Files\Google\Chrome\Application\chrome.exe" --single-argument %1
cloudmusic.aac="D:\software\CloudMusic\cloudmusic.exe"--play="%1"
查看指定文件类型的打开软件,如txtfile,可以通过ftype txtfile
,输出:
txtfile=%SystemRoot%\system32\NOTEPAD.EXE %1
就是windows自带的记事本软件。
修改的话,跟assoc
也是一样,就不赘述了。
这个指令讲真看着也没啥用。
特殊符号篇
%
百分号的用处还是挺大的。
用途一:批处理参数
在批处理中,最多只能接收9个命令行参数,内部是通过%1~%9来调用,而%0是指当前bat文件。%*
表示所有参数。
如:
echo %0
echo %1
echo %2
echo %*
输出示例:
D:\Code\BAT>b.bat 参数1 参数2 参数3
b.bat
参数1
参数2
参数1 参数2 参数3
由于%0
代表当前bat文件自身,因此当你需要复制当前文件的副本的时候,可以这么写:
copy %0 d:\b.bat
用途二:引用变量
使用%var_name%
,表示引用一个变量。
简单举个例子:
set a=12
echo %a%
*、?(通配符)
这个跟大家都是一样的,*表示任意多个字符(也包含0个),而?表示任意一个字符(也包含0个)。
这个比较简单,常用于匹配文件名上:
if exist *.txt echo 当前文件夹下有文本文件
dir a?.txt
后者表示列出当前目录下,第一个字母是a,第二个字母是任意字符的文件。
<、>、>>(重定向符)
跟shell一样,用来控制数据输入的方向和方式。
<,表示输入重定向,右侧数据流入左边;
>,表示输出重定向,数据从左边流入右边,且覆盖式写入;
>>,表示输出重定向,数据从左边流入右边,但是是追加式的写入到右边;
随便举个例子:
echo type a.txt > b.txt
将a.txt的内容复制到b.txt,类似shell中的cat a.txt > b.txt
.
这里没什么好讲的。
这里有个比较有意思的应用,就是利用输入重定向符来修改当前的系统时间:
echo 2023-01-01 > temp.txt
date < temp.txt
然后以管理员权限打开的CMD来运行上面的bat,之后查看系统时间就变成了:
这个确实有点意思。
^(转义字符)
^
有两大作用,一个是作为转义的前导字符,另一个是作为换行的续行字符。
^
实际上是对重定向符<、>,以及&的专用前导字符,用来抹除它们的特殊意义,只作为普通字符使用。
如echo test > a.txt
,是将字符串test,覆盖式写入a.txt文件之中。
但如果是echo test ^> a.txt
,则是会将后面内容作为一个完整字符串,直接输出,即:
可以看到,^>
被转义输出成了>
^
也可以作为续行字符,在每行的后面追加一个^,在输出时可以作为一行来输出,举例:
echo 中华^
人民^
共和国
echo 万岁
最终打印出:
D:\Code\BAT>b.bat
中华人民共和国
万岁
但如果把每行末尾的^去掉,则是:
D:\Code\BAT>b.bat
中华
'人民' is not recognized as an internal or external command,
operable program or batch file.
'共和国' is not recognized as an internal or external command,
operable program or batch file.
万岁
直接报错。
|(管道符)
跟shell里一样,是将前面命令的输出结果,作为后面命令的输入,传递并执行。
举个教程里的例子:
find "red" a.txt | sort > result.txt
find会打出所有包含"red"字符串的行。
管道符有个比较有意思的用法,有的指令可能需要用户键入特殊值之后才能执行,比如删除文件的时候需要输入yes,确认后才能删除。但是使用管道符的话,就可以伪装用户输入yes。
以puase
指令举例
正常只执行一个puase
的时候,当前cmd会卡在以下界面,直至你点击了任意键,才会退出当前bat的执行。
但如果你执行的是:
echo aaa | pause
bat会将aaa传输给pause指令,相当于你执行了pause指令之后,又连着点了三下键a,所以实际运行的时候会直接退出当前bat,即:
&、&&、||(组合符)
&&
连接两条命令,顺序执行,当前面的命令执行出错后就不再执行后面的命令,执行成功后才会继续向下执行;
如dir h:\ && dir c:\ && dir d:\
如果你没有h盘的话,那第一个命令就会报错,整行命令都不会执行。
||
同样是连接两条命令,顺序执行,但跟&&
正好相反,它是当前执行失败后才会执行下一条指令,执行正确后反而不会再向下执行。
&
则更加简单了,它是顺序执行多条命令,不管前面命令是否执行成功,都会去执行后面的命令。语法格式命令一 & 命令二 & 命令三 &...
注意,管道符、重定向符、组合符在运行的时候,是有优先级的区别的,即:
管道符|
的优先级 > 重定向符 > 组合符。
如dir c:\ && dir d:\ > a.txt
由于重定向符的优先级要高于组合符,所以最终只会把d盘的目录输出到a.txt文件里。
%~(参数扩展)
之前讲过,%0
表示当前脚本,而%1
、%2
、%3
…%9
表示命令行参数,而%~
则表示针对这些参数的扩展,以针对%0
的扩展为例,支持的扩展方式有:
%~f0
:将%0扩展到一个完全合格的路径名。f应该是代表full,即全路径;%~d0
:仅将%0扩展到一个驱动器号,其实就是输出当前脚本所处的盘符;%~p0
:仅将%0扩展到一个路径,不包含盘符,即只到当前脚本的上层文件夹,p应该对应path;%~n0
:仅将%0扩展到一个文件名(不带后缀),这个很好理解,就是只显示当前脚本的名字,n应该可以理解为name;%~x0
:仅将%0扩展到一个文件扩展名,即只显示当前脚本的后缀名;%~s0
:扩展的路径只含有短名,s大概代表short,短名的意思是如果文件名超过了一定的长度限制后,会按照某种规则把名字给缩短来显示。很奇怪的使用方式
;%~a0
:将%0扩展到当前脚本的文件属性;%~t0
:将%0扩展到文件的日期和时间(创建时间),这个t自然代表time或者date的意思;%~z0
:将%0扩展到文件的大小,z可能代表了size?
以上命令扩展也支持%~f1
、%~f8
等形式。
直接上例子,写一个test.bat,内容如下:
@echo off
echo "%%~f0" %~f0
echo "%%~d0" %~d0
echo "%%~p0" %~p0
echo "%%~n0" %~n0
echo "%%~x0" %~x0
echo "%%~s0" %~s0
echo "%%~a0" %~a0
echo "%%~t0" %~t0
echo "%%~z0" %~z0
输出结果如下:
D:\TEMP>test.bat
"%~f0" D:\TEMP\test.bat
"%~d0" D:
"%~p0" \TEMP\
"%~n0" test
"%~x0" .bat
"%~s0" D:\TEMP\test.bat
"%~a0" --a--------
"%~t0" 2023/07/19 22:38
"%~z0" 182
%s0和%a0确实没看懂。。。。
另外,这些扩展参数是可以互相组合的,比如说%~dp0
,就是拿当前脚本的完整路径,不含文件名;
参数扩展同样也支持其他参数,比如说%~x1
,如果你传的第一个参数确实是个文件名,比如说是a.txt,那么%~x1
的确会返回一个.txt。
:(截取/替换字符串)
- 注释
两个冒号在一起,表示注释,作用跟rem是一样的,语法格式::: 这是注释
,或者是:+这是注释
,但还是更推荐rem
- 结合goto实现跳转
这个自行查看goto语法就可以
- 对字符串进行操作:截取和替换
截取
截取的语法:%variable_name:~start_pos,n_length%
如:
%v:~0,5%
表示从变量v的第1个字符开始,一共截取5个字符;
%v:~-5%
表示截取最后5个字符;
%v:~2,-5%
表示从第3个字符截取到倒数第5个字符;
%v:~-2,5%
表示从倒数第2个字符开始,向后截取5个字符,如果不够5个,那就有多少显示多少;
如:
@echo off
set v=123456789
echo %v%
echo %v:~0,5%
echo %v:~-1,5%
echo %v:~-5%
echo %v:~-1,-5%
输出:
D:\Code\BAT>b.bat
123456789
12345
9
56789
56789
替换
语法格式:
%varable_name:待替换字符串=新字符串%
如:
echo %date%
echo %date:/=%
echo %date:/=-%
输出:
D:\Code\BAT>b.bat
周三 2023/08/16
周三 20230816
周三 2023-08-16
“” (字符串界定符)
""
允许我们在字符串里包含空格。
比如说cd "c:\Program Files"
2023-8-21 22:02:33 但是讲道理,这个的用处好像不大,因为我刚才在自己电脑上试了一下,cd c:\Program Files
也能正常进入目录。可能在别的场景里会更有用吧。。。
, (空格的替代符)
就相当于是空格。
如dir,d:\
,作用跟dir d:\
一毛一样。
; (参数并列符)
当多条指令,所使用的命令相同的时候,但参数值,或者说目标值不同的时候,可以使用;
来并列所有的参数。
如dir c:\;d:\
,会顺序执行dir c:\
和dir d:\
但这里要注意,并列后的命令,只要有一条执行出错,那所有的命令都不会执行。
如dir c:\;d:\;h:\
,如果我没有h盘的话,那这条命令直接报错,即使我有c盘和d盘,也不会去执行前两个命令。
并列符只是会让写起来更简便一些,个人觉得用处不大。
()
2023-8-21 22:15:29 没看懂具体的作用,说是括号内的多条命令会被组合视为一条命令来执行。
感觉用处不大,除了在for循环里。
!
跟变量延迟有关
nul
> nul
:表示将指令的输出重定向至空设备,不在前台做显示,类似linux里的/dev/null
,即黑洞;
用法就更简单了,比如说echo 111 > nul
,前台不会有任何输出。
其他常用命令汇总
ren 当前文件名 新文件名
,即rename,修改文件名;
与操作系统的交互命令
explorer
语法格式:explorer 完整路径
会打开指定路径处的文件资源管理器。
如:explorer D:\Code\BAT
日常做一些小脚本的话还是有点用处的。
这个的原理其实很简单,因为explorer.exe就是windows系统的文件资源管理器程序,而且路径默认是加入了环境变量的,因此我们可以在任意位置输入explorer
来打开文件资源管理器。
参考文献
- 程序员血月-B站主页 22年的,看着挺多挺全面的,而且很详细,建议看看
- 哔哩哔哩-windows批处理教程 虽然有点老,前面讲的也有点晦涩,有的地方有些,嗯,但是后面给出了丰富的场景和实例,还行吧。
- 批处理之家 有点老了,不过bat这么多年也没咋变过,所以还是比较实用,有很多小例子。
- bat批处理命令之Start的详细用法\批处理打开指定的应用程序\批处理最大化\最小化打开程序
- Windows官方文档-setlocal
- Windows官方文档-shift
- 批处理命令-----shift的用法!
- BAT脚本中%~dp0(获取当前目录)
- bat脚本中的%~的作用
- 【BAT】for命令用法 写的非常好,全面且深入
- Windows命令之findstr命令
- 批处理实用技巧
- bat中冒号的作用(注释 ,跳转, 截取、替换字符串)