Board logo

标题: [其他] [讨论]批处理中特殊字符的解释机制探索 [打印本页]

作者: applba    时间: 2011-5-16 04:03     标题: [讨论]批处理中特殊字符的解释机制探索

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

回避了预处理这个概念。

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

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

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

call 触发的对%和^的再次解释
即call命令运行时把^和%重新识别特殊字符,并再次启动解释。
call每嵌套一次,都会把%和^ 重新识别为特殊符号,并启动新一轮的解释。
作者: applba    时间: 2011-5-16 04:18

本帖最后由 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一次,^的数量都会增加一倍……
作者: Batcher    时间: 2011-5-16 10:42

“该图片仅限百度用户交流使用”
作者: zz100001    时间: 2011-5-16 11:00

没txt就得了,图片还是无访问的外链,这发个毛教程,不过的确是有“新意”!
作者: CrLf    时间: 2011-5-16 13:42

看了下,挺不错。不过少量说法比较表面化,可能对预处理的理解还不够到位
作者: qzwqzw    时间: 2011-5-16 15:00

教程写的不错
只是不明白图片化的意义
大略是防编辑的

用windows自带的图片和传真查看器看着比较累
大概看了不到三分之一
看到一句话挺有意思
作者还给读者留了一道家庭作业

“在控制台中我们要显示单个%可以使用:echo ^%,想想为什么”
一道好题目
首先可以明确
在控制台中显示单个%直接用echo %就可以
在这里转义字符^实际上是个无关大碍的蛇足
那么为什么在控制台中可以直接显示%呢?

这个题目发扬楼主的风格留给楼下
作者: CrLf    时间: 2011-5-16 18:00

6# qzwqzw

cmd少一层预处理...
作者: applba    时间: 2011-5-16 18:20

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

5# zm900612


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

6# qzwqzw


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


现在已经放出了txt了,在二楼
作者: applba    时间: 2011-5-16 18:45

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

6# qzwqzw


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

1# applba
在CMD窗口中,只能一行一行的输入和执行语句,也就是说只能执行单行语句!

cmd 窗口中也可以输入和执行多行多行语句
方法是在命令行中输入()

C:\Documents and Settings\Administrator>(
More? dir
More? cd
More? )

作者: plp626    时间: 2011-5-16 20:24

本帖最后由 plp626 于 2011-5-16 21:17 编辑

我的观点,大概啊。。。可以帮助新人理解


%既有高级语言反斜杠\的转义特性(仅脚本中对自身转义),又有引号的配对特性,还有遇到 : ~ 0123456789* 的扩展特性,

!则在变量延迟开启时,有引号配对特性,和遇到:的扩展特性,还有二次预处理特性。

^仅是反斜杠转义特性,(除了可以自身转义外,还可对!"以及 &|,()<>=;回车符还有某些特殊控制字符转义)

" 是配对特性,又有对后续字符串的转义特性。

预处理优先级顺序 % 大于 (" ^ &  < > | ) 大于 !
括号内的优先级同。

call会在当前行引发二次预处理。。。

关于括号,。。。 会引出很多问题,,越说越复杂。。
作者: qzwqzw    时间: 2011-5-16 20:35

cmd.exe对批处理中特殊字符的解释机制
上面两步中如果遇到了%,会中断并跳入%的解释,处理完毕后会返回,继续对其他特殊符号进行转义。
applba 发表于 2011-5-16 04:18

我也认为学习其实是一个不断反复打破和重新确定某些旧有概念和思维定势的过程
比如“预处理”的概念在解释特殊字符处理时确实过于笼统
但某些基本的原理应该是经得起考验的
比如特殊字符优先级的问题
%明显比其他特殊字符具有更高的优先级
也就是说cmd的转义字符检查不止一次
cmd第一次读入语句后首先要检查的符号就是%
它在检查完整条语句中所有的%后
才去重新检查语句中其它的字符
所以对于%这里不存在中断与返回的问题
作者: qzwqzw    时间: 2011-5-16 20:54

10# applba
对于cmd控制台对于%的处理
比你和7楼的理解还要复杂
举几个例子
cmd控制台下for的迭代变量是单个%而不是两个%
cmd控制台下执行echo %%OS% 将显示%OS=Windows_NT
cmd控制台下%var%引用的环境变量若不存在会原样显示而不是扩展为空
作者: qzwqzw    时间: 2011-5-16 21:00

1# applba
Command.com最初是MS-DOS的命令解释程序,在Windows XP中依然得到了继承,它可以实现cmd.exe的大多数功能。

记得有一篇将Windows中的命令行的帖子
其中就主要讨论了cmd与command的关系
简单来说
Windows NT系列的command.com
是为了兼容MS-DOS时代的16位程序而存在的
是16位dos程序的命令行入口程序
其背后的命令解释器仍然是cmd.exe
只不过中间由ntvdm.exe增加了一道设备虚拟层
作者: CrLf    时间: 2011-5-16 21:07

我理解的批处理优先级如下,从上到下按从高到低排序:
%1
%
()                         【没被转义的括号是语块的划分符号,所以for命令和if命令其实本质应该不是语句,而是语块】
&和|和&&和||
>和<
%%a                   【执行时,因为%%已被转义,所以其实执行的是%a】
空格以及他分隔符
"和^                     【for /f中的“"”存在特例,对此我也不是很理解】
!

不知道还有没有漏的
作者: CrLf    时间: 2011-5-16 21:15

而call echo %%a%%的实质大概是它运行的是cmd /k echo %%a%%
此时在父cmd中进行了第一级预处理,变成cmd /k echo %a%,然后在子cmd中再解释变量a,由于子进程继承了父进程的变量环境,所以变量a能够被成功地延迟扩展。
值得一提的是,call命令对for命令和if命令似乎没辙,我到现在为止坚持认为for命令和if命令是最特殊的两个命令,因为它们都包含着语块,典型的例子是for,for命令的输出和输入都是语块,这是别的命令做不到的,虽然cmd命令也有“cmd /c "语句一&语句二&语句三"”这种类似于语块的用法,但其实现执行语块的本质还是将语块作为参数传递给子cmd。所以我猜测call命令可能无法解读语块,具体原因不明。
作者: CrLf    时间: 2011-5-16 21:19

14# qzwqzw


有道理,我武断了
作者: applba    时间: 2011-5-16 22:44

我觉得把,批处理方面比较权威和系统的资料真的很少,不像c和delphi……
很多东西只能慢慢摸索……
作者: applba    时间: 2011-5-16 22:47

11# qzwqzw

汗?确实可以,受教了
作者: applba    时间: 2011-5-16 23:31

17# zm900612


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

call call call (ping /n 50 127.0.0.0)
作者: CrLf    时间: 2011-5-16 23:34

21# applba


有的,测试过的了。
不过先前没注意到的是,不管有多少层call,似乎只创建了一个新cmd进程,看来还有猫腻
作者: CrLf    时间: 2011-5-16 23:35

发现call ping不像call pause,真的没有多余进程
作者: CrLf    时间: 2011-5-16 23:35

明白了,pause是内部命令
作者: applba    时间: 2011-5-16 23:42

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

13# qzwqzw


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

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

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

看来又要重写了。

补充一个问题:
是不是每个特殊符号的处理都是单独的一轮?
作者: applba    时间: 2011-5-17 00:06

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

16# zm900612


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

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

空格等分隔符是专用的特殊符号,只能由一些命令在运行时进行解释,cmd.exe在读取时是不处理这些符号的。
作者: Batcher    时间: 2011-5-17 01:32

19# applba


这就是在Windows里面写代码的悲哀啦,木有办法啊。
不像Linux,哪个命令的机制想不清楚了,看看源代码,于是豁然开朗。最近看到的一个例子:
http://blog.csdn.net/sosodream/archive/2011/05/16/6425192.aspx
作者: applba    时间: 2011-5-17 02:31

学了两个月批处理,又想学学python了……
作者: applba    时间: 2011-5-18 01:13

17# zm900612


不记得谁说过:
for和if是高级语句,相当于一个命令解释器,他们能独立的对后面的语句块进行解释……
作者: CrLf    时间: 2011-5-18 14:05

17# zm900612


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

确实,这个看法比我的观点更明晰准确,看来我以后要改口了
作者: hucuibai    时间: 2011-5-18 17:45

1# applba
  1. @echo off
  2. echo %123%
  3. pause
复制代码
  1. @echo off
  2. echo "123'
  3. pause
复制代码
  1. @echo off
  2. echo ^123
  3. pause
复制代码
这种特殊字符又怎么屏蔽呢?
作者: applba    时间: 2011-5-18 18:00

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

不知道你说的屏蔽是什么含义?是屏蔽他们的特殊作用?还是不显示他们?
作者: CrLf    时间: 2011-5-18 18:36

31# hucuibai


用非常非常拐弯抹角的方法设为变量
http://bbs.bathome.net/viewthrea ... muid=30406#pid78640
作者: qzwqzw    时间: 2011-5-19 09:40

1# applba
这几个符号的优先级 管道符号(|) 高于 输入输出重定向符号(< > >>) 高于 命令分隔符(&,&&,||)

这就是矫枉过正了
已确认包括 & | > < 都在同一个命令行预处理循环中
所以说他们的处理优先级是相等的
预处理结果只决定于它们出现的位置以及程序逻辑
这其中也包括对&& || >& <& 1> >> 等符号的处理
作者: qzwqzw    时间: 2011-5-19 09:56

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

这句话我也很耳熟
但是这样的理解我认为过于极端了
for和if的特殊之处只在于
它们可以在语句中使用()
这意味着他们可能要在语句执行中
重新启动命令行的部分预处理功能
但是本质上仍然是语句块
仍然是在读入时就被预处理了大部分
真正的命令解释器仍然是cmd.exe
for 和 if 都不会开启新的cmd进程
除非使用 for /f 中的命令行语法
作者: hucuibai    时间: 2011-5-19 10:39

32# applba


我的意思就是将这个字符打印成普通字符,或者不显示出来也可以
用set命令可以处理?
作者: applba    时间: 2011-5-19 21:38

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

34# qzwqzw

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

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

当然了一楼的”高于“是我弄反了,实际上是低于。
作者: CrLf    时间: 2011-5-19 21:54

37# applba


命令行的特点是从上到下、从左到右
作者: powerbat    时间: 2011-5-19 22:13

36# hucuibai
恰恰相反,|的优先级高于&

from verybat(怀念之):
'%' >> '^' > '()' > '>、>>' > | > & > && > ||
%是顶级优先处理的,所以用“远远大于(>>)”

预处理时要判定语句的结构和功能。

当时zqz0012005、xzyx等高手把这些东西分析得非常深入透彻。
可惜很多技术、原理现在都忘了,后悔没有好好保存那些帖子,谁让verybat突然消失了呢?
作者: CrLf    时间: 2011-5-19 22:14

35# qzwqzw


我也这么认为,前面被那个比较有条理的说法吓倒了...仔细想想for和if确实是语块,比如:
  1. @echo off&setlocal enabledelayedexpansion
  2. set "a b=."
  3. set "tmp=a b"
  4. echo for内:
  5. for %%a in ("a b") do if defined %%~a echo 存在“a b”
  6. ::正确
  7. echo;&echo 不使用变量延迟:
  8. (if defined a b echo 存在“a b”)
  9. ::出错
  10. echo;&echo 变量延迟:
  11. (if defined !tmp! echo 存在“a b”)
  12. ::正确
  13. echo;&pause
复制代码
我原来一直以为if defined是不支持空格变量名的,直到某天在某群里某个自以为是的家伙误打误撞写出来,我原以为会出错,结果一运行竟然没问题,这才发现原来if的条件语句是和整个语块一起进行划分的,也就是说,if不是一个语句,而是语块,for也是同样的情况,最有说服力的就是for循环中的for命令的开关、选项和参数是无法直接用变量修改的,这也证明了在语块划分完毕的时候,for语句的条件语句之前的部分也已经划分完了:
  1. for /f "tokens=!a!" ...
复制代码
众所周知这是会出错的
还有:
call if ...
call for ...
这两个都会出错,从错误提示来看,只能说明,for和if完全不是普通语句,而更可能是语块
作者: CrLf    时间: 2011-5-19 22:15

36# hucuibai
恰恰相反,|的优先级高于&

from verybat(怀念之):
'%' >> '^' > '()' > '>、>>' > | > & > && > ||
%是顶级优先处理的,所以用“远远大于(>>)”

预处理时要判定语句的结构和功能。

当时zq ...
powerbat 发表于 2011-5-19 22:06


'>、>>' > | > & > && > ||?
重定向的优先级应该排在语句的连接符之后吧
作者: powerbat    时间: 2011-5-19 22:24

以前不是有人说过for和if是“关键词”吗?

set n=3&for /l %%a in (1 1 !n!) do ...
是没问题的。
set n=3&for /l %%a in (1 1 !n!) do (... if %Got%==1 (set n=10) ...)
在循环体中改变循环条件是不行的。
作者: CrLf    时间: 2011-5-19 22:59

以前不是有人说过for和if是“关键词”吗?

set n=3&for /l %%a in (1 1 !n!) do ...
是没问题的。
set n=3&for /l %%a in (1 1 !n!) do (... if %Got%==1 (set n=10) ...)
在循环体中改变循环条件是不行的。
powerbat 发表于 2011-5-19 22:24

如果for和if是语块这个命题成立,那么条件语句将是语块的一部分,而非语句的一部分,这就可以解释为什么在语块中无法重新定义for和if的条件语句了,因为它们的条件语句是和整个语块一起被预处理的
作者: powerbat    时间: 2011-5-19 23:56

本帖最后由 powerbat 于 2011-5-20 00:24 编辑

'%' >> '^' > '()' > '>、>>' > | > & > && > ||
这个优先级应该没什么问题(当然我也没详细测试过)

set n=3&for /l %%a in (1 1 !n!) do ... //可以
set n=1&for /f "tokens=!n!" %%a in (循环集) do ... //不行
前者可以用延迟变量,说明for条件是在for命令被cmd执行时计算。而循环体中不能改变变量,则说明for计算条件只是在执行循环体之前处理一次。
后者可以认为是for /f 内部处理引号“从句”时,对tokens处理很简单,发现传给它的值是非数字就报错,没考虑值是变量的情况。

if的条件本来就只判断一次,何来改变?况且if的条件可以用延迟变量,说明if条件在预处理后还可以在if被cmd执行时才计算。
说for和if是“关键词”,因为它可以引导语句块。
其实不使用它们,单独用()把一组语句括起来,也是语句块,比如:
(echo a
echo b)>a.txt
作者: powerbat    时间: 2011-5-20 00:02

猜想CMD把for /L 语句转换为C语言是类似这样的:
for /l %a in (start,step,end) do ...
==>
int i = 0;
int iStart = getenv("start");//假设for /L 的三个值都是变量
int iStep = getenv("step");
int iStop = getenv("end"); //在执行for之前已经把条件都确定了
for (i = iStart; i < iStop; i += iStep) {...} //在循环体中再怎么改变start,step,end,已经不影响for的条件了
作者: qzwqzw    时间: 2011-5-20 00:07

37# applba
  1. set "a=10f611|findstr f6"
  2. echo %a% & echo string
  3. pause
  4. echo test1234 > test.txt
  5. find "test" < test.txt | find "1234"
  6. find "test" | find "1234" < test.txt
复制代码
这个例子也许不太好
我只是想说
& | < 谁先解释取决于谁在命令行之前
以及相应的功能逻辑

这与%不同
即使在命令行末尾
也会被先行扩展
作者: applba    时间: 2011-5-20 00:48

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

46# qzwqzw

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

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

本帖最后由 qzwqzw 于 2011-5-20 08:25 编辑

所谓的“不能超越”不能理解
不需要把概念搞的这样理论化
作者: CrLf    时间: 2011-5-20 13:30

46# qzwqzw

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

你的说法也合理:即这几个符号那个在前面就先处理哪个,前面的先处 ...
applba 发表于 2011-5-20 00:48


|是兼具句柄重定向和语句连接符功能的,而且具有连接符所有特征,所以不能将它和重定向符号并列起来。我举个例子
  1. (dir >1.txt >con)>2.txt
  2. ::重定向符号是并列的,但是每一次重定向都会刷新对应句柄的设置,所以对同样的句柄,只有最后一次重定向是有效的。
  3. set|findstr /i "pro"|sort +2
  4. ::而管道则不光将两个命令的0、1句柄桥接起来,还充当了语句之间的连接符,连接符的特征是按从左到右的顺序进行判断,举个例子:
  5. 2>nul ren %~s0 &&(echo 1,此处不显示)||(echo 2,此处显示)&&(echo 3,此处显示)||(echo 4,此处不显示)
  6. ::可见语句的优先级顺序就是连接符的优先级顺序,连接符是并列的,而且优先级高于重定向符。
  7. pause
复制代码

作者: applba    时间: 2011-5-20 20:18

48# qzwqzw


不能超越,我的意思就是前者会受到后者的限制。
echo 123&echo 456 >nul
上面例子中,>的作用范围受到了&的限制,>只能对echo 456重定向,不会对echo 123重定向。
作者: CrLf    时间: 2011-5-20 20:25

50# applba


那就用重定向的优先级低于管道来解释不是豁然贯通了吗?为什么要把他们强凑在一个优先级上,再打上一堆补丁去弥补这个看法的漏洞呢?
作者: applba    时间: 2011-5-20 20:38

51# zm900612


唉……我也不想这样啊……看来我以后还是老实写点批处理代码……这些东西想了也是白想,还不实用
作者: CrLf    时间: 2011-5-20 20:49

52# applba


话也不能这么说呀,这几天的争论中,我虽然一再否定自己原先的观点,但是并非意味着失败,而是由此越来越趋近于预处理机制的核心真相...这种争论,虽然有很多东西无从论证,但是总归是有助于相互印证的,毕竟要想一个人去揣测出所有方面的机制是很难的。很喜欢这样相互切磋,技术论坛,就应该有这种气氛
作者: powerbat    时间: 2011-5-20 20:52

为什么不回过头来想想39楼提到的优先级呢?
作者: CrLf    时间: 2011-5-20 21:00

为什么不回过头来想想39楼提到的优先级呢?
powerbat 发表于 2011-5-20 20:52

我认为连接符(包括管道)是平级的,而非“| > & > && > ||”




欢迎光临 批处理之家 (http://bbs.bathome.net/) Powered by Discuz! 7.2