[新手上路]批处理新手入门导读[视频教程]批处理基础视频教程[视频教程]VBS基础视频教程[批处理精品]批处理版照片整理器
[批处理精品]纯批处理备份&还原驱动[批处理精品]CMD命令50条不能说的秘密[在线下载]第三方命令行工具[在线帮助]VBScript / JScript 在线参考
返回列表 发帖

[其他] [讨论]批处理中特殊字符的解释机制探索

本帖最后由 applba 于 2011-5-20 03:46 编辑

回避了预处理这个概念。

把解释过程了分成了两个阶段,第一轮和第二轮。

第一轮解释
这一轮的解释是全局性的,是无条件的,是发生语句执行前的。
解释的符号有(% ^ ")。
关于这三个符号的优先级问题(即先解释哪个):
%具有最高优先级,^和"具有并列的优先级。(谢谢QZWQZW的指正)
意思就是说所有的%的处理完毕后才开始处理^和";而对于^和",哪个在前面就先处理哪个

第二轮解释
是局部性的,是由具体的命令或语句进行的,一边运行一遍解释。
解释的符号比较多:
通用特殊符号:括号() & && | || > >> < ,如果开启了变量延迟包括解释!及其范围的^
专用特殊符号:各命令自行识别的特殊符号,差别可能很大。如大多数命令都把空格识别为分隔符,echo把它识别为普通符号。
优先级问题:这些特殊符号哪个在前面就先处理哪个,前面的先处理,后面的后处理。
几个符号作用范围: 管道符号(|) 不能超越 输入输出重定向符号(< > >>) 不能超越 命令分隔符(&,&&,||) 不能超越 括号。

call 触发的对%和^的再次解释
即call命令运行时把^和%重新识别特殊字符,并再次启动解释。
call每嵌套一次,都会把%和^ 重新识别为特殊符号,并启动新一轮的解释。
3

评分人数

本帖最后由 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

评分人数

TOP

本帖最后由 applba 于 2011-5-16 18:40 编辑

5# zm900612


我起初也想用预处理这个概念来写这边文章,可是到后期发现有些地方有矛盾的,所以我回避了预处理这个概念。大体上,读取时对特殊字符的解释就是“预处理”,运行时对特殊字符的解释就是二次预处理,call运行时还会触发第三次第四次解释工作……这篇文章写的比较简单,也不是很全面,我觉得还是重在建立一个新的知识架构……

TOP

6# qzwqzw


你可以右键该图片——打开方式——IE/chrome/360浏览器都可以打开看的,和浏览网页一样的效果。


现在已经放出了txt了,在二楼

TOP

本帖最后由 applba 于 2011-5-16 18:47 编辑

6# qzwqzw


你不提出我还真没想到,大概是这样的:
在批处理中单独的%(非%i,非%%i,非配对的)在读取时会被丢弃的…而在控制台中,单个的%(非%a%)被识别为普通符号……而^作为转义字符是无法显示的,或者说他转义了一个普通字符,普通字符转义后还是普通字符……

TOP

我觉得把,批处理方面比较权威和系统的资料真的很少,不像c和delphi……
很多东西只能慢慢摸索……

TOP

11# qzwqzw

汗?确实可以,受教了

TOP

17# zm900612


经测试,没有发现新的cmd.exe进程

call call call (ping /n 50 127.0.0.0)

TOP

本帖最后由 applba 于 2011-5-16 23:48 编辑

13# qzwqzw


关于优先级的看法,我觉得你的还是很合理……

因为之前看了英雄的教程,也提到优先级的问题,但是解释不是很合理,有些地方还矛盾……

所以我自作聪明弄了一个新看法……

看来又要重写了。

补充一个问题:
是不是每个特殊符号的处理都是单独的一轮?

TOP

本帖最后由 applba 于 2011-5-17 02:30 编辑

16# zm900612


我觉得把,还是要区分一下特殊字符的处理时机的,是读取时还是运行时,这两个时机处理的内容是有差别的。
读取时只处理(%,",^)这三个符号的,其他的什么命令分隔符(& && ||)、重定向符号等是不处理的。
比如在读取时,不管是否开启变量延迟,都是不会处理感叹号的,感叹号的处理是在运行时由具体的命令处理。
    如果你没有开启变量延迟,set/a会把!解释成逻辑非,而其他命令把它识别为普通符号。
    开启变量延迟后,所有的命令都会把它识别延迟符号,并启动解释工作。

比如 ,%(%%、%n,%%i ,%a%)的解释是发生在读取时的,这个是无条件解释的,此时命令还没有被执行的。(当然了如果call会导致重新识别%为特殊字符,运行时再次进行解释。)再如 &、&&  ||等,读取时是不被解释的,他们是在运行时由具体的命令进行解释,因为cmd.exe在读取时是不知道命令是执行成功还是执行失败的。

空格等分隔符是专用的特殊符号,只能由一些命令在运行时进行解释,cmd.exe在读取时是不处理这些符号的。

TOP

学了两个月批处理,又想学学python了……

TOP

17# zm900612


不记得谁说过:
for和if是高级语句,相当于一个命令解释器,他们能独立的对后面的语句块进行解释……

TOP

echo命令不想set命令一样,set命令在运行时会去掉首尾的引号。

不知道你说的屏蔽是什么含义?是屏蔽他们的特殊作用?还是不显示他们?

TOP

本帖最后由 applba 于 2011-5-19 21:41 编辑

34# qzwqzw

set "a=df&df67"
echo %a%|find "f6"

上面的用法肯定会出错,&把此语句分割。
我是据此来推断对&的特殊功能解释是先于|的。

当然了一楼的”高于“是我弄反了,实际上是低于。

TOP

本帖最后由 applba 于 2011-5-20 01:28 编辑

46# qzwqzw

|  > >> < & && ||
这几个特殊符号肯定是在执行时进行功能解读的,前四个都涉及到句柄,后三个都必须等待前面的命令执行完毕。

你的说法也合理:即这几个符号那个在前面就先处理哪个,前面的先处理,后面的后处理。这样的话就存在一个作用范围的问题: 管道符号(|) 不能超越 输入输出重定向符号(< > >>) 不能超越 命令分隔符(&,&&,||) 不能超越括号。

TOP

返回列表