.bat批处理(八):各种形式的变量%0、%i、%%i、var、%var%、!var!的含义和区别

前言

最近使用批处理程序处理文件的时候,发现这 bat中的变量形式真是“变化多端”,有时候加1个百分号%,有时候加2个百分号%%,还有的时候加感叹号!,真是让初学者一头雾水,于是查询资料做了一些小测试,终于大致弄清楚了这些变量的含义,接下来一一列举出来。

变量对比

下面通过一些具体的例子来看下标题中提到的这些变量什么时候使用,使用的时候有哪些注意事项。

%0

这个是批处理程序中的固定用法,类似于C++程序main函数中argv变量数组,类比可以知道,argv[0]表示exe程序的文件名,argv[1]表示启动程序的第1个参数,后面依次类推。而在批处理程序中%0表示这个批处理程序的文件名,%1表示调用这个批处理时传入的第1个参数,%2表示调用这个批处理时传入的第2个参数,最大可以到%9,具体用法可以参考之前的总结《.bat批处理(二):%0 %1——给批处理脚本传递参数》,简单测试如下:

1
2
3
4
5
@echo off

echo param0=%0
echo param0=%1
echo param0=%2

将上述代码保存在文件testparams.bat中,从cmd命令行运行批处理文件,只传入一个参数,运行结果如下:

C:\Users\Administrator\Downloads>testparams.bat “hello world”
param0=testparams.bat
param1=”hello world”
param2=

%i

在题目所列的这些变量中,这一个比较特殊,因为它不是批处理文件中的变量,只能用于cmd命令行下的for循环中,在命令行中for循环的语法是for %variable in (set) do command [command-parameters],其中的variable只能是单字母或者非特殊含义的字符,同样的for循环语句如果写在批处理文件中variable之前就要加两个%%了,先来看看%i的用法,直接在命令行中遍历集合打印输出:

C:\Users\Administrator\Downloads>for %i in (1,3,5,8) do echo %i
C:\Users\Administrator\Downloads>echo 1
1
C:\Users\Administrator\Downloads>echo 3
3
C:\Users\Administrator\Downloads>echo 5
5
C:\Users\Administrator\Downloads>echo 8
8

如果将其中的%i改成%%i,就会报语法错误,测试结果如下:

C:\Users\Administrator\Downloads>for %%i in (1,3,5,8) do echo %%i
此时不应有 %%i。

%%i

这种类型也是for循环中特有的,与%i相对,属于批处理程序的用法,换句话说就是在for循环中遍历的索引变量,如果在命令行中定义需要一个%,如果相同的语句定义在批处理文件中需要2个%%,语法为for %%variable in (set) do command [command-parameters],variable同样只能是单个字母或者普通字符,至于为什么同样含义的变量在批处理中要多加一个%,至今也没有找到官方的说法,查找MSDN也没有发现说明,不过就我个人理解可能就像我们在命令行中打印一个%,可以正常打印输出,如果通过printf()想输出%就需要2个%的原理一样吧,测试如下:

1
for %%i in (1,3,5,8) do echo %%i

运行结果:

C:\Users\Administrator\Downloads>testfor.bat
C:\Users\Administrator\Downloads>for %i in (1 3 5 8) do echo %i
C:\Users\Administrator\Downloads>echo 1
1
C:\Users\Administrator\Downloads>echo 3
3
C:\Users\Administrator\Downloads>echo 5
5
C:\Users\Administrator\Downloads>echo 8
8

观察运行结果发现,运行批处理文件的时候,实际上去掉了%%i变量的1个%,将文件中代码改为1个%试下:

1
for %i in (1,3,5,8) do echo %i

运行结果:

C:\Users\Administrator\Downloads>testfor.bat
此时不应有 i。

var

这个变量看起来挺正常的,也没有那么多奇奇怪怪的字符,和Lua、Python等语言中的变量长得挺像,实际上变量的这种形式很“短暂”,一般只能出现在给变量赋值的时候,也就是set语句之后,作为左值接受赋值时,或者在等号右测可评估的表达式中,举个例子,编写下面代码保存在normalVar.bat中:

1
2
3
4
5
6
7
@echo off

set var1=1
set /a var2=var1+1

echo var1
echo var2

运行之后的结果为:

C:\Users\Administrator\Downloads>normalVar.bat
var1
var2

看完结果之后觉得很神奇是不是,为什么和我学的其他语言不一样呢,我使用set分别为var1和var2赋了值,但是输出的时候居然不是数字而是变量名,其实这就引出了之后%var%这种用法,接着往下看。

%var%

在批处理中除了上面所说的在set语句后面的两种情况,再要想引用变量就需要在变量两端各加一个百分号%,明确的告诉引用者这是一个变量,使用时需要评估一下值,而不要当成字符串,上一个例子中echo后面想要输出的变量没有加%,那就被当成一个字符串处理,原样输出了,修改上个例子如下:

1
2
3
4
5
6
7
8
9
10
@echo off

set var1=1
set /a var2=var1+1

set var3=%var2%

echo %var1%
echo %var2%
echo %var3%

运行之后运行结果入下:

C:\Users\Administrator\Downloads>normalVar.bat
1
2
2

看了这次的结果感觉正常多了,有一点需要注意,set var3=%var2%这一句中var2变量中的%不能省略,因为它既不属于左值也不属于被评估值的表达式,如果不加%,赋值后var3的值会变成“var2”这个字符串。

!var!

这是最后一种常见的变量形式,同时也是一种不太好理解的形式,需要记住一点,这种变量与延迟环境变量扩展有关,如果没开启延迟环境变量扩展,那么!var!就是一个普通的包含5个字母的字符串,如果开启了延迟环境变量扩展,那么它就是变量var的实际值,可能说到这有的人会产生疑惑,引用变量var的值不是使用%var%吗?那么在开启延迟环境变量扩展的情况下,%var%和!var!有什么区别呢?下面举个例子测试下,编写如下代码保存在extVar.bat文件中:

1
2
3
4
@echo off

set var1=110
set var1=120&echo %var1%

运行之后的结果为:

C:\Users\Administrator\Downloads>extVar.bat
110

看到结果的时候是不是再次怀疑了世界,在打印变量var1之前明明重新赋值了120,为什么打印出来还是110呢?其实这是批处理脚本执行机制导致的,它会按行执行,在执行之前会先预处理,当执行set var1=110之后,变量var1变成了110,在执行set var1=120&echo %var1%之前先预处理,将变量%var1%替换成了110,然后语句变成了set var1=120&echo 110,所以就得到了我们上面测试的结果。

想要解决这个问题就需要开启延迟环境变量扩展,语句为setlocal enabledelayedexpansion,然后将引用变量的形式由%var1%改为!var1!即可,所以可以修改代码如下:

1
2
3
4
5
@echo off

setlocal enabledelayedexpansion
set var1=110
set var1=120&echo !var1!

运行之后的结果为:

C:\Users\Administrator\Downloads>extVar.bat
120

这回输出的结果符合预期了,开启了延迟环境变量扩展之后,!var!形式的变量在用之前才会评估确切的值,这是一个知识点,也是一个易错点,特别是在for循环中要格外注意,因为for循环语句的循环体括号中,所有的操作被看成是同一行,所以经常会用到延迟环境变量扩展。

总结

  1. for循环在cmd命令行中的固定用法for %i in (set) do (...),循环变量格式为%i
  2. for循环在bat处理程序中的固定用法for %%i in (set) do (...),循环变量格式为%%i
  3. 至于为什么for语法在批处理中需要多写一个%,希望知道的小伙伴能给出答案和参考资料,不胜感激
  4. 想要变量被使用的时候再评估值需要开启延迟环境变量扩展,语法为setlocal enabledelayedexpansion,同时使用!var!形式的变量
Albert Shi wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客