- 帖子
- 551
- 积分
- 2799
- 技术
- 39
- 捐助
- 0
- 注册时间
- 2011-4-22
|
本帖最后由 applba 于 2011-5-20 03:35 编辑
批处理中特殊字符的解释机制探索
其实批处理文件也是通过调用cmd.exe来解释和运行的。
cmd.exe 在读取批处理中的一个语句(块)后,需要对其中的特殊符号进行功能解释,然后才开始执行它。
这个解释过程应该是分阶段的,分层次的。
1、第一轮 解释
解释的符号有(% ^ ")。
这一轮的解释是全局性的,是无条件的,是发生语句执行前的。
关于这三个符号的优先级问题(即先解释哪个):
%具有最高优先级,^和"具有并列的优先级。(谢谢QZWQZW的指正)
意思就是说所有的%的处理完毕后才开始处理^和";而对于^和",哪个在前面就先处理哪个
(1)CMD.exe在遇到第一个%后,会检查后面紧跟的第一个字符。
如果后面紧跟的还是一个%,前面一个对后面一个转义,转义后得到一个普通的%字符,前面的%消失。
如果后面紧跟一个数字,则把%和数字一起解释为一个参数变量变完成值的替换。(变量名以数字开头出错的原因。)
如果后面紧跟一个其他字符(包含" ^ & |等),则会继续往后寻找配对的%
如果找到了,%%之间的内容被识别为环境变量名变完成值的替换。(特殊符号在此时已被替换掉!)
如果没有找到下一个%,此单%会被丢弃。
例子1:
@echo off
set a=1
echo %123%
echo %%a
echo %%a%%
echo %%%a%%%
echo %%%%a%%%%
pause
例子2:
@echo off
echo %%a
echo %%a%%
for /l %%a in (1,1,5) do (
echo %%a
echo %%a%%
)
pause
可以使用使用除%和=以外的特殊字符作为变量名。
因为这些特殊字符在执行前已经以%a%的形式被替换掉。
例子3:
@echo off
set "^^&\=12345"
echo %^^&\%
pause
例子4:
@echo off
set """"=5"
echo %""&"echo fax
echo %"""%&echo fax
(2)cmd.exe在读取到一个引号时,会继续寻找下一个引号。
如果没有找到,该引号后的同一行中的其他特殊符号(包括^,不包括%)都被转义,同时单引号被保留。
如果找到了下一个引号,则这两个引号对之间的特殊符号(包括^,不包括%)都被转义,同时引号对被保留。
例子1:
@echo off
echo "123&echo 456 &
echo 123"&echo 456 &"
echo "%a%"
echo "%%%%"
echo "^^^^
pause
echo "^^^^说明了,"号消去了^的特殊作用(即转义其他字符的作用)。
例子2:说明一下%的最高优先级
@echo off
SETLOCAL EnablEdElayEdExpansion
set "%abc=5^5
echo !%abc!
echo !abc!
pause
很明显,单独的%被消去了,实际上处理后执行的 set "abc=5^5
(3)对^的解释简单很多:^后紧跟的第一个特殊字符都会被转义(包括引号、不包括%)。
例子1:
@echo off
echo go^&dir
echo ^^^^^^^&dir
echo 123"&echo abc
echo 123^"&echo abc
pause>nul
上面的例子中,^转义了",使引号失去了特殊作用(即转义其他字符的作用)。
例子2: 说明一下%的最高优先级
@echo off
set "a=&^!"
echo ^%a%
pause>nul
在上面例子中,%解释后处理的结果是&^!,^再次把&^!解释成&!。
2、第二轮 解释
这一轮解释是局部性的,是由具体的命令或语句进行的,一边运行一遍解释。
解释的符号比较多:
通用特殊符号:括号() & && | || > >> < ,如果开启了变量延迟包括解释!及其范围的^
专用特殊符号:各命令自行识别的特殊符号,差别可能很大。如大多数命令都把空格识别为分隔符,echo把它识别为普通符号。
优先级问题:这些特殊符号哪个在前面就先处理哪个,前面的先处理,后面的后处理。
几个符号作用范围: 管道符号(|) 不能超越 输入输出重定向符号(< > >>) 不能超越 命令分隔符(&,&&,||) 不能超越 括号。
下面以set命令为例来分析一下专用特殊符号。
set命令在执行时对引号的解释(其实很多命令对引号的处理和这类似)
如果引号出现在行首,看行尾有没有配对的,如果有一起删除,如果没有删除行首的引号。
其他地方的任意数量的引号均被保留。
例子:下面三句效果一样
set "adfdf=dfdfd 89"
set "adfdf=dfdfd 89
set adfdf=dfdfd 89
set /a 命令对运算符的解释
比如%(第一轮中已经被转义),set /a把它解释为取余运算符。
如果开启了变量变量延迟,这一轮还会处理!以及^。
(1)变量延迟中!的解释机制
在没有开启变量延迟时,!只是作为一个普通字符,在set /a 语句执行时被解释成”逻辑非“。
在开启变量延迟后,!成为了变量引导符,在命令执行时(不是读取时)按照以下规则来解释:
遇到一个叹号后,如果后面的内容中含有^符号,会被识别为特殊符号并在此启动解释工作!
遇到第二个叹号后,他们之间的字符串(转义后)被解释成一个变量名并完成变量值的替换。
cmd.exe在读取字符串时是以语句为单位的,而这个语句可能含有多个命令甚至很多行。
而在执行时是以命令为单位的,比如含一个&的组合语句,是分两次执行的。
如此我们来解释一下一个经典的例子:
@echo off
SETLOCAL enabledelayedexpansion
set a=100
set a=ok&echo %a%
set a=ok&echo !a!
pause
上面的set a=ok&echo %a%在读取时就被处理成了set a=ok&echo 100,运行时自然不能得到正确的结果。
set a=ok&echo !a!读取时显然不会被处理,set a=ok 执行完毕后,再执行echo !a!,此时才进行变量值的替换。
!不会导致%被重新识别为特殊符号:
@echo off
SETLOCAL enabledelayedexpansion
echo !%%%%
echo !^^^^
pause
@echo off
SETLOCAL enabledelayedexpansion
set a=100
echo !%%a%%
pause
注意,开启变量延迟后,变量名中不能含有!。
(2)开启变变量延迟后,!后的^符号也会被再次识别为特殊符号,并启动二次解释。
例子1:
@echo off
SETLOCAL enabledelayedexpansion
set "^=1"
Set "^^=11"
Set "^^^^=1111"
echo %^^^^%
echo ^^^^
echo !^^^^
echo !"^^^^^
pause>nul
输出结果:
1111
^^
^
11
对 echo %^^^^% 的分析:
当读取 echo %^^^^% 时,%%之间的所有^被转义仅当作普通符号,%^^^^%将会被替换成变量值1111。
执行时实际上已经变成了 echo 1111
而对于 echo ^^^^ 的分析:
第一个^对第二个^转义,第三个对第四个转义,解释玩后成了^^。
执行时实际上是 echo ^^。
对于 echo !^^^^ 的分析:
读取时,被解释成 echo !^^
执行时由于!的存在,命令再次把^当成特殊符号,并启动转义过程!
最后^^被转义成了^,而!由于至于单个,没有被消去。
对 echo !"^^^^的分析
读取时,引号将^^^^全部被转义成普通字符,引号自身也被保留。
执行时,!触发的二次转义并不理会",最终被处理成 ^^,而!和"都被保留。
看了上面的分析,我们知道了!会使^再次成为特殊字符,不管是否单!还是双!。
小技巧:开启变量延迟后怎么输出单个感叹号?
@echo off
SETLOCAL enabledelayedexpansion
echo ^^!
pause>nul
3.call 触发的对%和^的再次解释
即call命令运行时也把^和%重新识别特殊字符,并再次启动解释。
call每嵌套一次,都会把%和^ 重新识别为特殊符号,并启动新一轮的解释。
(1)call对%的重新解释
@echo off
echo %%%%%%%%%
call echo %%%%%%%%%
call call echo %%%%%%%%%
我解释一下,cmd.exe在读取时会将%%%%%%%%%解释为普通字符%%%%
运行时,call命令又把%识别为特殊字符,将%%%%解释为普通字符%%
如果又嵌套一个call,它在运行时还会这样做,将%%解释为普通字符%
好了,call每运行一次,都会重新把%识别为特殊字符!
这样,我们也可以利用call来延迟变量。
@echo off
set a=ok &call echo %%a%%。
set a=ok &call call echo %%%%a%%%%。
注意了,%的数量会减半这一点!
(2)Call对^的重新解释
echo ^^^^
call echo ^^^^
call call echo ^^^^
@echo off
echo "^^^^
call echo "^^^^
call call echo "^^^^
为什么上面的正常下面的不正常呢?
call或许认为,执行时遇到的单个^肯定是读取时通过^^转义出来的。所以它在执行时想当然的把他们还原,即把一个^替换成两个。
然后又把^识别为特殊符号,又启动对^的解释,2个又变成一个了,然后得到的是正常结果。
如果call不这么做,那个^的命运将和%一样,每call一次都会减少一半。
call嵌套时依然是先还原后解释,嵌套多少次结果都一样!
我们现在使用单个"来转义所有的^,这样批处理在读取时就把^全部当作了普通字符,^没有发生数量减半……
但是call傻傻不知道,只要看到是^,依然对^进行翻倍处理,完全忽略引号的存在……
翻倍后由于引号的依然存在,call看到的依然是普通字符^,并不启动^的解释……
这样导致每call一次,^的数量都会增加一倍…… |
-
1
评分人数
-
|