Board logo

标题: [原创] 批处理命令call echo 用法 [打印本页]

作者: 悬崖之树    时间: 2013-7-14 20:53     标题: 批处理命令call echo 用法

如有错误,请大家指正,我拭目以待^&洗耳恭听
  1. @echo off
  2. set ok=adsl
  3. set s=abc
  4. set b=qdj!ok!
  5. setlocal enabledelayedexpansion
  6. call echo %%!s:~1,1!%%
  7. endlocal
  8. pause
复制代码
运行结果是 qdj!ok!
关键在于call echo %%!s:~1,1!%%
预处理是这样的:
1去掉call, 把%%减半,然后替换!s:~1,1!变成 echo %b%
2 替换%b% 变成echo qdj!ok!,虽然这里出现了感叹号,但是cmd不再扩展了,(你知道是怎么回事吗?)
这样就输出 qdj!ok!
  1. @echo off
  2. set ok=adsl
  3. setlocal enabledelayedexpansion
  4. call echo !%%ok%%:~1,1!
  5. endlocal
  6. pause
复制代码
输出 ~1,1
原因是call没有对感叹号里面的%进行减半,这样 %%k%% 就无意义了。
作者: 522235677    时间: 2013-7-15 20:24

不太明白啊
作者: Demon    时间: 2013-7-15 21:31

不明觉厉
作者: bgst    时间: 2013-7-23 16:08

开始也看不懂,查了一下才大致明白,写出来供大家参考和补充
    @echo off
    set ok=adsl
    set s=abc
    set b=qdj!ok!
    setlocal enabledelayedexpansion
    call echo %%!s:~1,1!%%
    endlocal

    pause
前4行很简单,第5行和第7行是一起的,启用延缓环境变量,什么意思暂时还没闹明白,关键是第6行
这行整个的意思是显示以环境变量s的第二个字符为名字的环境变量b的值,qdj!ok!
首先看!!,当启用变量延迟时,!!将变量名扩起来表示对变量值的引用
%s:~1,1%表示提取环境变量s第1个字符后面的1字符,这里是b(注意不是第一个字符,如果要选第一个字符应写成%s:~0,1%)
那么%!s:~1,1!%,就变成了%b%,echo %%!s:~1,1!%%就变成了echo %%b%%,所以结果是输出b的值qdj!ok!
作者: bgst    时间: 2013-7-23 16:15

另外,通过我的实验,这里的call不是必须的,代码第6行修改为echo !%s:~1,1%!
同样可以输出b的值
花了好几个小时才看明白,还有延缓环境变量没有搞懂

充分说明了楼主大材,希望能够亲自献身解说一下
作者: Tony_zhangl    时间: 2013-8-12 15:14

谢谢楼主分享,又长知识了...
作者: mingdedream    时间: 2014-6-21 01:36

实际上
call echo !%%ok%%:~1,1!  
这个语句中由于call 的存在会执行两次
第一次:执行过程 echo !%ok%:~1,1!(会脱去OK的百分号)
第二次:执行过程 echo  !adsl:~1,1!
所以最终得出结果~1,1
作者: cjiabing    时间: 2014-6-23 17:26

回复 7# mingdedream


    这种用法投机取巧了,执行效率低。
作者: shelluserwlb    时间: 2014-11-8 09:23

另外,通过我的实验,这里的call不是必须的,代码第6行修改为echo !%s:~1,1%!
同样可以输出b的值
花了好几个 ...
bgst 发表于 2013-7-23 16:15


    如果去掉call 双百分号在for循环体内好像不行。
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. set ok=abcd
  4. set a=aaaaa & set b=bbbbb & set c=ccccc & set d=ddddd
  5. for /l %%i in (0,1,3) do call echo %%!ok:~%%i,1!%%
  6. endlocal
  7. pause>nul
复制代码
如果把上面第5行中的"call echo %%!ok:~%%i,1!%%" 换成
“echo !%ok:~%%i,1%!” 在执行时就会出错。
作者: amwfjhh    时间: 2014-11-13 12:54

楼主第一个批处理其实用不着开启变量延时。下方的取值操作位于非语句块内,s值已可被正常读取,直接用call echo %%%s:~1,1%%%即可。
第二个批处理中call还是对%%ok%%进行了处理,变成%ok%,第二次是以%ok%作为变量名来进行取值的,由于当前环境找不到此变量名,导致了异常结果的显示。
可以用下方代码进行验证:
  1. @echo on
  2. set ok=adsl
  3. set %%ok%%=lsda
  4. setlocal enabledelayedexpansion
  5. call echo !%%ok%%:~1,1!
  6. endlocal
  7. pause
复制代码

作者: amwfjhh    时间: 2014-11-13 13:13

回复 9# shelluserwlb


   
开启变量延时后,只会对!!之间变量进行实时取值,for的用法有点特殊,有点类似于执行到FOR这里,就会根据条件立马初始化分配空间,后面只是单纯执行,而DO后方的语句,都会被以语句块的形式解释,所以对FOR循环内进行实时取值都需要开启延时变量,要么就用CALL,这里%ok:~%%i,1%按说是一个无效变量,但回显开启发现它直接将其初始化成了ok的值,导致行行了几次!abcd!,无此变量,故显示ECHO状态,有点莫名其妙。
作者: qzwqzw    时间: 2014-11-21 11:02

回复 9# shelluserwlb


    关键问题在于对%,和!扩展顺序的理解

在cmd中,%的扩展是优先于其他特殊字符的
%%会扩展成%
所以
%%i会扩展成%i

所以
for /l %%i in (0,1,3) do call echo %%!ok:~%%i,1!%%
首先被扩展成以下的结果
for /L %i in (0 1 3) do call echo %!ok:~%i,1!%

然后for先对命令字句中可替换参数%i进行扩展
call echo %!ok:~0,1!%
call echo %!ok:~1,1!%
call echo %!ok:~2,1!%
call echo %!ok:~3,1!%

然后for再对命令字句中的延迟变量进行扩展
call echo %a%
call echo %b%
call echo %c%
call echo %d%

然后call再对命令中环境变量进行扩展
echo aaaaa
echo bbbbb
echo cccccc
echo ddddd
作者: qzwqzw    时间: 2014-11-21 16:56

本帖最后由 qzwqzw 于 2014-11-21 17:01 编辑

回复 11# amwfjhh


    这里%ok:~%%i,1%按说是一个无效变量,但回显开启发现它直接将其初始化成了ok的值,导致行行了几次!abcd!,无此变量,故显示ECHO状态,有点莫名其妙。

实际上cmd是将%ok:~%%i,1%理解成了两个环境变量扩展
一个是%ok:~%
因为~后没有跟数字
照道理应该是无效的
但是cmd容忍了这个错误
用默认的~0做了扩展

另一个是%i,1%
因为不存在i,1的变量
所以扩展为空

批处理脚本中解释%的机制是就近匹配
第一个%后紧接着就会找第二个百分号
如果第二个字符就是
则扩展为%
如果隔几个字符找到%
则将中间的字符理解为环境变量名进行扩展

for对%扩展机制要更为复杂一些
将in关键字前的%理解为可替换参数的定义
将do关键字后的%理解为可替换参数的引用

但是在cmd命令行中
%的扩展可能有些不同
这源于命令行环境与批处理环境预处理机制的不同
echo %%path%%

而在DOS命令行中
则保持了与批处理一样的扩展机制
作者: amwfjhh    时间: 2014-11-21 17:01

回复 13# qzwqzw


    谢谢指点,思维固化了,光想着解析里面的变量了,没想到组装后其实这句已经是在执行两个变量输出了。
作者: amwfjhh    时间: 2014-11-21 17:26

对于脚本语句与CMD里直接执行语句的区别可以这么看,CMD是BAT的执行宿主,将文件载入内存时会有一套翻译机制,否则载入的脚本文件全是一堆文字,我凭哪点知道哪些是命令,哪些是数据?这就是所谓的“预处理”,这点在DEMON大大的逆向跟踪相关文章里面已有印证。而在CMD里面执行语句则是直接在其自身运行空间内执行,所以无需翻译,直接执行(其实也有翻译,只不过处理关键步骤要少些)。
突然想到对于论坛对于echo命令分割符的讨论贴子了,echo命令为何会有那么多的参数分割符,因为它是内部命令,说白了,内部命令整条命令(包括参数)也对于CMD来说,也是一个参数啊,所以它必须提前对于一般程序的默认处理作下改变,所以平时用的"echo[空格]参数",这里面的空格,跟我们用外部命令的空格,猜想已经不是同一概念了(只是它“恰巧”也提供了一个空格的分割符,以使其符合大多数人的使用习惯),这样CMD才能在一个参数中切割出不同的部分来调用相应的函数,表现出执行了一条命令的样子……
作者: qzwqzw    时间: 2014-11-21 22:21

回复 15# amwfjhh


    突然想到对于论坛对于echo命令分割符的讨论贴子了,echo命令为何会有那么多的参数分割符,因为它是内部命令,说白了,内部命令整条命令(包括参数)也对于CMD来说,也是一个参数啊,所以它必须提前对于一般程序的默认处理作下改变,所以平时用的"echo[空格]参数",这里面的空格,跟我们用外部命令的空格,猜想已经不是同一概念了(只是它“恰巧”也提供了一个空格的分割符,以使其符合大多数人的使用习惯),这样CMD才能在一个参数中切割出不同的部分来调用相应的函数,表现出执行了一条命令的样子……

很有意思的见解
不过有些内容我不太苟同
如果你仔细看过Demon关于预处理以及echo的分析帖的话
就会理解
无论是内部命令还是外部命令
像空格、水平制表符等一般分隔符都是通用的

而对于+[].等字符
其实cmd并不视其为分隔符
只是为了尽可能兼容一些怪异的用户习惯
想尽办法做的FindAndFix而已
因为只是一些Fix
所以大多数命令甚至很多内部命令
对这些伪分隔符字符是不“认账”的
作者: amwfjhh    时间: 2014-11-22 00:29

回复 16# qzwqzw


    嗯,谢谢指正。是我表述错误,我说的“外部命令”应该说成“开始->运行->"程序名 参数表……"”或者在“创建程序快捷方式,然后在里面指定启动参数”,这类调用;在CMD里面不管是内部命令还是外部命令处理分割符的方式应该是一样的。前者不知有人逆向跟踪过没,是否也如在CMD里面的参数读取过程一样呢?
作者: amwfjhh    时间: 2014-11-22 00:47

本帖最后由 amwfjhh 于 2014-11-22 01:31 编辑

测试了一下,随意用个播放器,在运行里面运行"程序名,参数"或者建一个快捷方式,在其指向里添上",参数",两者均提示无效调用,而将","换成空格,正常执行,看来只是在CMD环境下才是对,=;等视作分割符标志,空格应是先被转义统一作为普通字符,待预处理读取完一整行(或者一整个语句块)作为参数传给宿主程序后,词法分析到关键字后再在分割符表里查关键词后有没有相应的字符,空格则正好是其中之一,从这个意义上说,它与,=;等字符具有同等意义。而不像其它程序的非CMD环境调用参数,未转义的空格直接作为参数分割符。
作者: qzwqzw    时间: 2014-11-22 22:54

你测试的这种情况应该是explorer.exe来传递的命令行参数
它应该是调用MSVCRT的标准函数来实现命令行词法切分
这些标准函数通常都会以空格作为参数分隔符
作者: amwfjhh    时间: 2014-11-22 23:55

回复 19# qzwqzw


    为什么表情里面没有点赞的表情,只有文字点了,一句话说到了本质。
作者: CrLf    时间: 2014-11-22 23:57

回复 18# amwfjhh


不知道有没有解析命令行参数的api...
但反正编译器貌似都有内置解析参数的功能吧,常见的规则大同小异,win 下空格分隔、双引号转义是起码的
具体执行的时候有一些小区别,比如 gcc 会把参数中的 * 解析成一串的文件路径,tcc 印象中是会自动去掉双引号,这些就很无奈了,只好取出原始命令行参数自行分割
至于 tab , ; = 这些分隔符,貌似不是主流,全看作者心情了
作者: amwfjhh    时间: 2014-11-23 00:19

本帖最后由 amwfjhh 于 2014-11-24 12:02 编辑

win下的程序,应该都是统一的参数分割标准,CMD也是一个WIN程序,其本身也是这样,但是在CMD运行环境下调用内部命令或者外部命令,按照常情也应该像在WIN下调用程序一样,那么问题来了,对于一个输入字符串,如何解析,势必参照WIN下的处理方式,于是有了那一系列的处理,至于额外的规则,说不定当初写CMD的某个人或者某个团队中的核心人物,想恶作剧一把,于是就产生了现在看起来相对于WIN下的参数方式有点“非主流”的切割符列表。
哪天看下VC的源代码,看看能不能找到怎么处理参数事宜的。不过看一般的入口函数int main(int argc, char** args),程序名与参数也是一个字符串来的,一直在用从字符串中取参数,还没想过哪里规定的这样取参数,要进一步去看是哪里对于参数处理以及处理规则。


PS:
vc下能看到的启动过程是kernel32! 0x7c81776f==>crt0.c==>各种main()
还需要继续
作者: qzwqzw    时间: 2014-11-24 17:53

回复 22# amwfjhh


    win下的程序,应该都是统一的参数分割标准,CMD也是一个WIN程序,其本身也是这样,但是在CMD运行环境下调用内部命令或者外部命令,按照常情也应该像在WIN下调用程序一样,那么问题来了,对于一个输入字符串,如何解析,势必参照WIN下的处理方式,于是有了那一系列的处理,至于额外的规则,说不定当初写CMD的某个人或者某个团队中的核心人物,想恶作剧一把,于是就产生了现在看起来相对于WIN下的参数方式有点“非主流”的切割符列表。

所谓“额外的规则”
初衷显示不是开发者的恶作剧
而是为了保持向下兼容
cmd缘起于DOS
许多命令行习惯必然从DOS获得继承
而在DOS下
空格与分号、等号等同是“主流”分隔符
而这些分隔符的由来
同时是为了兼容初期的DOS
乃至类似的命令行环境的用户习惯
保持用户界面友好
这是MSDOS之所以流行的主因
作者: yiwuyun    时间: 2014-11-24 20:23

我的理解是这样的:
第一次去掉%是这样的结果
echo !k%:~1,1!  

第2次直接得
~1,1
作者: amwfjhh    时间: 2014-11-24 21:11

qzwqzw 发表于 2014-11-24 17:53
所谓“额外的规则”
初衷显示不是开发者的恶作剧
而是为了保持向下兼容
cmd缘起于DOS
许多命令行习惯必然从DOS获得继承
而在DOS下
空格与分号、等号等同是“主流”分隔符
而这些分隔符的由来
同时是为了兼容初期的DOS
乃至类似的命令行环境的用户习惯
保持用户界面友好
这是MSDOS之所以流行的主因



    第一,原来如此,里面还有如此厚重的历史气息,谢谢科普;第二,你曝露年龄了,这几天从您及其他论坛诸位达人的的贴子里面学到很多东西,谢谢。




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