Board logo

标题: [原创] [分享]批处理命令for 另类用法 [打印本页]

作者: CrLf    时间: 2012-3-4 01:10     标题: [分享]批处理命令for 另类用法

本帖最后由 CrLf 于 2014-4-29 00:54 编辑

一、十六进制转十进制
  1. ::常见方案
  2. set hex=4F
  3. set /a num=0x%hex%
  4. echo 十六进制数 %hex% 的十进制数为 %num%
复制代码
  1. ::for /l 方案
  2. set hex=4F
  3. for /l %%a in (0x%hex% 1 0x%hex%) do echo 十六进制数 %hex% 的十进制数为 %%a
复制代码
二、useback,你可以安息了
注:useback 不是笔误,它和 usebackq 其实没有区别。
应对字符串本身带有引号的情况:
  1. ::常见方案:
  2. set str="<test>"
  3. for /f useback %%a in ('%str%') do echo %%a
复制代码
  1. ::转义方案
  2. set str="<test>"
  3. for /f %%a in (^"%str%^") do echo %%a
复制代码
处理文件路径必须用引号的情况:
  1. ::常见方案
  2. set "file=%programfiles%\(te)&(st).txt"
  3. for /f "useback delims=" %%a in ("%file%") do echo %%a
复制代码
  1. ::首引号方案(勘误方案,原理见 8 楼,感谢 7 楼的指正与启迪):
  2. set "file=%programfiles%\(te)&(st).txt"
  3. for /f "delims=" %%a in ("%file%
  4. ) do echo %%a
  5. rem 如果%file% 中不含特殊字符,可以写成一行为:
  6. rem     for /f "delims=" %%a in (^"%file%) do echo %%a
复制代码
  1. ::上级路径方案
  2. set "file=%programfiles%\(te)&(st).txt"
  3. for /f "delims=" %%a in ("%file%\test"\..) do echo %%a
  4. rem 时灵时不灵,仅作参考...
复制代码
三、避免 for /f 从命令获取输入时,命令行参数首尾都有引号产生的错误处理字符串字符串本身带引号的情况:
  1. ::错误模拟(假定 %programfiles% 含空格)
  2. for /f %%a in ('"%programfiles%\winrar\rar.exe" v "1.rar"') do echo %%a
  3. rem 错误产生原因见附文
复制代码
  1. ::解决方案
  2. for /f %%a in ('^;"%programfiles%\winrar\rar.exe" v "1.rar"') do echo %%a
复制代码
四、显示 test.txt 中含有“测试”的行
  1. ::find 方案,直接,但是外部命令的效率欠佳
  2. find "测试"<test.txt
复制代码
  1. ::if+变量替换方案,效率和可控性好一些
  2. for /f "delims=" %%a in (test.txt) do (
  3.         set str=%%a
  4.         setlocal enabledelayedexpansion
  5.         if "!str:测试=!" neq "!str!" echo !str!
  6.         endlocal
  7. )
复制代码
  1. ::(感谢 powerbat 指出原纯 for 方案有盲点,现改为 for+if 方案)for+ if 方案,某些情况下是比较好的选择
  2. for /f "delims=" %%a in (test.txt) do (
  3.     for /f "delims=试" %%b in ("[%%a]") do (
  4.         for /f "tokens=2 delims=测" %%c in ("[%%b]") do if %%c==] echo %%a
  5.     )
  6. )
复制代码
五、for 参数的妙用
以往我们认为 delims 的内容无法含双引号(由于 ^ 无法左右 for 内部的引号匹配,所以 "delims="^" 没有效果),以及 eol 不能为空("eol=" 会将双引号设为分隔符),但是换个角度来思考即可迎刃而解:for 参数真的需要双引号吗?
  1. ::以双引号为分隔符
  2. for /f delims^=^" %%a in ("a"b"c") do echo 以 " 为分隔符的第一节为:%%a
复制代码
如此可以避免 for 解析参数时将 " 理解为参数分隔符,之所以要对 = 转义,是因为等号为 for 的默认分隔符,未通过转义符或双引号转义的等号在预处理时会被解释为空格
与 for 参数相关的讨论见:http://bbs.bathome.net/viewthread.php?tid=12500
  1. ::设置 eol 为 null
  2. for /f "delims="eol^= %%a in (";") do echo 设置 eol 为 null 的结果为:%%a
复制代码
经过验证,在以 "delims="eol^= 为参数 for /f 参数时 0x1~0x79 均不是 eol 的值,含 \x0 的行会被自动忽略故无法验证
至于 0x80 之后的扩展字符因为通用性问题用到的机会很少(见注)故未测试,因此可以视为此处 eol 等于 null。
注:eol 存在只取宽字符的首字节的特性,所以“eol=测”("测"的gbk码为B2E2)等效于“eol=悴”("悴"的gbk码为B2E3),故不建议设置 eol 为宽字符
附文:
【三】的例子中无论 rar.exe 存不存在,都能触发错误解析,先看一下 for /? 系统帮助中的这一部分:
    可以用 FOR /F 命令来分析命令的输出。方法是,将
括号之间的 filenameset 变成一个反括字符串。该字符串会
被当作命令行,传递到一个子 CMD.EXE,其输出会被捕获到
内存中,并被当作文件分析
。如以下例子所示:

正如帮助中所述,因为 for /f 从命令获取输入时,实际上是执行了:
  1. cmd /c "%programfiles%\winrar\rar.exe" v "1.rar"
复制代码

而在 cmd /? 的系统帮助中又提到:
如果指定了 /C 或 /K,则会将该开关之后的
命令行的剩余部分作为一个命令行处理,其中,会使用下列逻辑
处理引号(")字符:

   1.  如果符合下列所有条件,则会保留
       命令行上的引号字符:

       - 不带 /S 开关
       - 正好两个引号字符
       - 在两个引号字符之间无任何特殊字符,
         特殊字符指下列字符: &<>()@^|
       - 在两个引号字符之间至少有
         一个空格字符
       - 在两个引号字符之间的字符串是某个
         可执行文件的名称。

   2.  否则,老办法是看第一个字符
       是否是引号字符,如果是,则去掉首字符并
       删除命令行上最后一个引号,保留
       最后一个引号之后的所有文本。
从这里可以看出实例代码中给出的参数在不满足条件1 的同时又满足了条件2,因此预处理时将消除最外层引号各一,所以 cmd.exe 子进程所执行的是 %programfiles%\winrar\rar.exe" v "1.rar,很明显的,这不是我们所希望的,当然破解方法也很简单,只要不让双引号位于参数头或尾,这个隐患即不攻自破,解决方案中的 ^; 即是在传递给子进程的参数前附加一个 ; 符号,子进程认为它是分隔符而忽略,同时也使得条件2 不被满足,同理,也可以使用 ^=、^, 或 @ 来代替 ^;,原理大同小异。
作者: applba    时间: 2012-3-4 12:23

奇技淫巧!
作者: find    时间: 2012-3-4 13:06

人不淫荡枉少年
作者: CrLf    时间: 2012-3-4 13:08

回复 2# applba


    并非华而不实的技巧,是实战中经常用到的
用 for /l 进行进制转换效率高于set /a,而且不需要临时变量
useback 是有名的 bug 多多,但有时候你不得不使用它(比如文件名含特殊字符的时候)
命令行参数首尾都有引号产生错误是经常有人碰到的
搜索字符或词语时,需要区分大小写或关键词含有 ~、!、*、= 等字符时,纯 for /f 方案就是不二选择,它的效率很高且比 if+变量替换方案 更通用
作者: powerbat    时间: 2012-3-6 08:26

  1. @echo off
  2. (
  3. echo [汉语8级考试]哪些符合汉语语法()
  4. echo A.测试一天
  5. echo B.测一天试
  6. echo C.考试一天
  7. echo D.考一天试
  8. echo 测
  9. )>test.txt
  10. ::显示 test.txt 中含有“测试”的行
  11. ::纯 for 方案,某些情况下是比较好的选择
  12. for /f "delims=" %%a in (test.txt) do (
  13.     for /f "delims=试" %%b in ("[%%a]") do (
  14.         for /f "eol=] tokens=2 delims=测" %%c in ("[%%b]") do echo %%a
  15.     )
  16. )
  17. pause
复制代码
这个会误判,需要完善。(此法是否可行?)
作者: CrLf    时间: 2012-3-6 13:47

本帖最后由 CrLf 于 2012-3-6 18:17 编辑

回复 5# powerbat


    确实忽略了这个可能性,感谢指正!
   稍作修改:
  1. for /f "delims=" %%a in (test.txt) do (
  2.     for /f "tokens=2 delims=测" %%b in ("[%%a]") do (
  3.         for /f "eol=] delims=试" %%c in ("%%b测") do (
  4.             for /f "tokens=2 delims=测" %%d in ("[%%c]") do echo %%a
  5.         )
  6.     )
  7. )
  8. rem 代码中的 # 处需使用不会出现在 test.txt 中的字符
复制代码
或使用更简便而通用的 for+if:
  1. for /f "delims=" %%a in (test.txt) do (
  2.     for /f "delims=试" %%b in ("[%%a]") do (
  3.         for /f "tokens=2 delims=测" %%c in ("[%%b]") do if %%c==] echo %%a
  4.     )
  5. )
  6. rem 不是纯 for 方案,但是 for 配合 if 更快,而且也没有什么盲点
复制代码
不过限于非遍历性质的代码思路,对存在多个测或多个试的行没办法进行有效判断
作者: qzwqzw    时间: 2012-3-7 22:10

::空设备方案
set "file=%programfiles%\(te)&(st).txt"
for /f "delims=" %%a in (nul "%file%") do echo %%a
CrLf 发表于 2012-3-4 01:10

测试没有得到预想的结果
结果输出了file变量的值
而没有输出file对应的文件内容
作者: CrLf    时间: 2012-3-8 15:36

本帖最后由 CrLf 于 2012-3-17 19:22 编辑

回复 7# qzwqzw


    经测试,这一条确实是错误的,对不起!
   因为这用法在我印象中似乎有过成功的先例(可能记错了吧,惭愧),而且知道 for /f 是支持顺序从多个文件获取输入的,所以发帖之前没有再次进行验证

   【检讨】
    自从对预处理和各种命令的理解日深之后,渐渐不再对那些能够用个人理解去解读的代码进行测试了,直接结果就是,当个人理解有所偏差或者出现笔误的时候,写出的代码就是不可行的,因此发了很多误导人的帖子,实在抱歉了,各位坛友...


     【新的疑惑】
      另一方面,这个对于 for 的误会也揭开了一个新的问题,for /f 是如何区分字符串、文件和命令的?
    先假设 %file%=te st.txt
  1. echo;&echo 测试内容 = nul "%file%"
  2. for /f %%a in (nul "%file%") do echo %%a
  3. rem 试了一下 prn、con 等非文件设备似乎都被忽视了?我们用文件试一试
  4. pause
  5. echo;&echo 测试内容 = 文本1.txt 文本2.txt "%file%"
  6. for /f %%a in (文本1.txt 文本2.txt "%file%") do echo %%a
  7. rem 看来任意设备只要在 command 中打头,都能促使 for /f 从左到右遍历各个参数所对应的文件
  8. pause
复制代码
看来似乎可以总结出:“当 for /f 的 command 参数中以设备开头时,将按类似 %1、%2、%3... 的方式分别对其进行解析

但是还有一个现象:
  1. echo;&echo 测试内容 = "%file%" "%file%"
  2. for /f "delims=" %%a in ("%file%" "%file%") do echo %%a
  3. rem 这是常见的代码,此处 "%file%" "%file%" 将被输出为 %file%" "%file%
  4. pause
  5. echo;&echo 测试内容 = nul "%file%" "%file%"
  6. for /f "delims=" %%a in (nul "%file%" "%file%") do echo %%a
  7. rem 这是以设备开头的代码,此处 "%file%" "%file%" 将被输出为 %file%
  8. pause
复制代码
含 ' 的参数也具有同样的特性:
  1. for /f "delims=" %a in (nul 'pause' "test") do echo %a
  2. rem 终止于 'pause' 结束
复制代码
说明这个解析过程中如果发现字符串参数或命令行参数,则将其作为一行内容解析输出,并停止寻找下一参数
那么如果把 nul 和 "%file%" 出现的顺序反过来呢?
  1. echo;&echo 测试内容 = "%file%" nul
  2. for /f "delims=" %%a in ("%file%" nul) do echo %%a
  3. rem 此处为何仍提示“系统找不到文件 1 2.txt" nul。”呢?
  4. rem 也就是说可能是仅当参数首尾都为双引号时,才会将参数作为字符串解析
  5. pause
复制代码
作为上一个推论的证明:
  1. echo;&echo 测试内容 = ^^"%file%
  2. for /f "delims=" %%a in (^"%file%) do echo %%a
  3. rem 果然被解释为文件!
  4. pause
复制代码
也就是说,在不使用 useback 的情况下,当 for /f 排除分隔符后发现参数以 ' 或 " 打头,同时以 ' 或 " 结尾时将理解为是从字符串或命令获取输入。否则先理解为这些参数指的是文件,并把参数划分为 %1、%2、%3... 再分别对当前项进行判断其类型。
作者: CrLf    时间: 2012-3-8 17:34

另外,1 楼补充了一个上级路径方案,但是遇到了些奇怪的问题...
  1. ::上级路径方案
  2. rem 本机测试:
  3. set "file=%programfiles%\(te)&(st).txt"
  4. echo TEXT>"%file%"
  5. for /f "delims=" %%a in ("%file%\t"\..) do echo %%a
  6. rem 输出[1]:TEXT
  7. rem 无误
  8. for /f "delims=" %%a in ("%file%\test"\..) do echo %%a
  9. rem 输出[1]:TEXT
  10. rem 输出[2]:系统找不到文件 춧咆。
  11. rem 正常输出 %file% 但也报错
复制代码
测试了好几中组合,结果失灵时不灵,似乎找不到规律
作者: qzwqzw    时间: 2012-3-17 16:39

回复 8# CrLf
最后的结论大部分都是常识
很像是某个官方文档的描述
看来楼主的对for/f的认识有一个辗转反侧的过程
不过其中部分测试代码还是很有创意的
非常钦佩楼主的探索精神

你在9楼提到的for/f的内存泄漏问题值得关注
最近也在搜集有关for/r内存泄漏相关的资料
不知道还有没有其它比较大概率泄漏的代码案例可以提供?
作者: CrLf    时间: 2012-3-17 19:34

本帖最后由 CrLf 于 2012-3-19 14:43 编辑

回复 10# qzwqzw


确实比较曲折,因为以往没有意识到两点:
1、for /f 对参数的解析并不是简单判断首尾符号再 jmp 的,它会对参数进行分析再决定如何调用相应模块,这二者有点类似 %str% 与 !str! 的关系
2、字符串模块和命令输入模块调用完毕后会终止 for /f,所以他们可能是通过 jmp 调用的,而从文件读取输入时会将参数挨个解析,可能是用 call 的

另外,一直听人说 usebackq 问题很多,不知 usebackq 和 for /r 的 bug 是什么?可否举两个实例呢?

还有说道内存泄露,想起前两天看到旧帖中 zqz 回复的测试代码,稍微修改下:
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. for /l %%a in (0 1 8192) do (
  4.   set,=a!,!||(echo !,!&echo 长度为 %%a 个字符&pause)
  5. )
  6. pause
复制代码
链接:http://bbs.bathome.net/redirect. ... 8&fromuid=30406

这里让我搞不明白,变量名 , 明明存在于变量表中,为何会多出一个字节的容量,后来发现变量表里,!,! 变量末尾的 nul 消失了,并且跟着一串陌生的内容,以这段陌生内容为关键词搜索内存段,发现是紧跟在 ss:100 之前的内容(debug里看到的几个段寄存器是一样的,所以分不清是数据段还是堆栈段)。
可能因为 echo 命令错误退出的原因,有部分 !,! 的内容没有被填充为 nul,所以还能观察到 echo 的内容后丢失了 0A。
估计设置变量时,内存内容原本应该是 00(填充堆栈剩余内存) 变量内容(反序)=变量名(反序),以 , 为名设置变量时因为错误解析的原因把 00 挤没了(设置变量时是用的堆栈保存数据的吗?感觉有点像),cmd 将数据段字符串照搬到变量表时,却只以 00 为字符串结束的标志,所以 debug 可以看到溢出的内容,而用 set 或 echo 却只能看到变量 0~8192 字符长度的内容
作者: cjiabing    时间: 2012-3-17 20:11

什么也不说,看看以下的东东!~123.txt最好有点东西。
  1. @echo off
  2. set "v=for /f "tokens=2*" %%a in ("
  3.     echo;看见我的的举个手
  4.     NiHaoBatHome
  5.     echo;我从不忏悔!
  6.     123.txt
  7.         自从对预处理和各种命令的理解日深之后,渐渐不再对那些能够用个人理解去解读的代码进行测试了,直接结果就是,当个人理解有所偏差或者出现笔误的时候,写出的代码就是不可行的,因此发了很多误导人的帖子,实在抱歉了,各位坛友...
  8.     ) do echo %%a    0000&pause
  9.     echo;
  10. pause
  11. echo;1111
  12. %v%
  13. 123.txt
  14. pause  ???
  15. ") ddd
  16. for /f "tokens=2" %%a in ('
  17. nihao
  18. echo;hhh
  19. 123.txt
  20. ') do echo %%a              你大爷的,连我都敢动
  21. echo;2222
  22. pause
  23. echo;3333
  24.     %v%
  25.     123.txt
  26.     echo;4444
  27.     pause
复制代码

作者: cjiabing    时间: 2012-3-17 20:27

回复 12# cjiabing


   
  1. @echo off
  2. for /f "tokens=2*" %%a in ("
  3. echo;看见我的的举个手"
  4. ) do echo %%a 0000&pause
  5. echo;1111
  6. pause
复制代码

作者: powerbat    时间: 2012-3-17 20:50

楼上的代码没啥新意吧。
唯一让我留意到的是
行首的“)空格”可以起到注释作用(空格可以为其他有效分隔符)
  1. @echo off
  2. ) echo aaa
  3. );echo aaa
  4. echo bbb
  5. pause
复制代码

作者: cjiabing    时间: 2012-3-17 21:19

回复 14# powerbat

括号()的注释功能似乎有人谈到过。其实上面的for的问题出在()。楼上的那个FOR出现错误时是“此时不应该有&”,而“&”在括号与pause外,批处理这时候却跳过了中间几行而直接找到这个&真是让人奇怪!~
以下可能比较清晰些。
  1. @echo off
  2. ::&setlocal enabledelayedexpansion
  3. for /f "tokens=2*" %%a in ("
  4. 看见我的的举个手"
  5. ) do echo 【%%a】 0000&pause
  6. echo;1111
  7. pause
  8. @echo off
  9. for /f "tokens=2*" %%a in ("
  10. echo;看见我的的举个手"
  11. ) do echo 【%%a】 2222&pause
  12. echo;3333
  13. pause
  14. @echo off
  15. for /f "tokens=*" %%a in ('
  16. echo 你好
  17. :lable
  18. echo 你好吗?
  19. echo 我很好
  20. goto lable
  21. '
  22. ) do echo 【%%a】 4444&pause
  23. echo;5555
  24. pause
  25. @echo off
  26. for /f "tokens=*" %%a in ('
  27. set var=穿过你的长发我的手
  28. echo 注意看变量设置
  29. ::不注意就没办法了
  30. echo str=var神的世界/
  31. goto lable
  32. '
  33. ) do echo 【%%a】 7777&pause
  34. echo %var%[!var!]==%str%[!str!]
  35. echo;8888
  36. pause
复制代码

作者: powerbat    时间: 2012-3-17 22:59

@echo off
for /f "delims=" %%a in (
file1
file2
file3
) do echo [%%a] xxx
rem 如果某个文件找不到,不再遍历后面的文件。
pause

for /f "delims=" %%a in (
"
aaa
bbb
"
) do echo [%%a] xxx
echo 111&pause

for /f "delims=" %%a in (
'
echo aaa
echo bbb
echo ccc
'
) do echo [%%a] xxx
echo 222&pause

多行模式中,换行被预处理成了空格。
蓝色的单双引号位置可以随意,但红色的双引号与反括号不能在一行。
作者: powerbat    时间: 2012-3-17 23:15

本帖最后由 powerbat 于 2012-3-17 23:19 编辑
@echo off
for /f "delims=" %%a in (
"
aaa
bbb
"
) do echo [%%a] xxx
echo 111&pause

for /f "delims=" %%a in ("
aaa
pause
") do echo [%%a] xxx
"
) do echo [%%a] xxx
pause
echo 222&pause


去掉echo off,可以看到预处理后变成了for /F "delims=" %a in (" aaa pause ") do echo [%a] xxx ") do echo [%a] xxx

当"与反括号在同一行时,for以为这是字符串的一部分,在后面如果找不到for语句的逻辑组成部分“) do ”,就会报错。
作者: qzwqzw    时间: 2012-3-19 14:32

本帖最后由 qzwqzw 于 2012-3-19 14:37 编辑

回复 11# CrLf

“莫名奇妙”
未从代码中观察到任何异常的情况发生
倒是注意到“||(echo !a!&”似应是"||(echo !,!&"
也许是环境问题
Microsoft Windows XP [版本 5.1.2600]

usebackq的内存泄漏问题也是早前曾见过
后来想找的时候反而找不到了
给一段Google到的代码
  1. @echo off
  2. for /f "tokens=4 usebackq" %%a in ('a b c d e f g h') do echo %%a
  3. for /f "tokens=4 usebackq" %%b in ('a b c d e f g h') do echo %%b
  4. for /f "tokens=4 usebackq" %%c in ('a b c d e f g h') do echo %%c
  5. for /f "tokens=4 usebackq" %%d in ('a b c d e f g h') do echo %%d
  6. for /f "tokens=4 usebackq" %%e in ('a b c d e f g h') do echo %%e
  7. for /f "tokens=4 usebackq" %%f in ('a b c d e f g h') do echo %%f
  8. for /f "tokens=4 usebackq" %%g in ('a b c d e f g h') do echo %%g
  9. for /f "tokens=4 usebackq" %%h in ('a b c d e f g h') do echo %%h
  10. for /f "tokens=4 usebackq" %%i in ('a b c d e f g h') do echo %%i
  11. for /f "tokens=4 usebackq" %%j in ('a b c d e f g h') do echo %%j
  12. for /f "tokens=4 usebackq" %%k in ('a b c d e f g h') do echo %%k
  13. for /f "tokens=4 usebackq" %%l in ('a b c d e f g h') do echo %%l
  14. for /f "tokens=4 usebackq" %%m in ('a b c d e f g h') do echo %%m
  15. for /f "tokens=4 usebackq" %%n in ('a b c d e f g h') do echo %%n
  16. for /f "tokens=4 usebackq" %%o in ('a b c d e f g h') do echo %%o
  17. for /f "tokens=4 usebackq" %%p in ('a b c d e f g h') do echo %%p
复制代码
for/r倒是没发现内存泄漏问题
你是指for/r+ren时的文件名反复修改的问题?
或者是指for/r文件名解析的问题?
http://bbs.bathome.net/viewthread.php?tid=7629
http://bbs.bathome.net/viewthrea ... amp;page=1#pid29165
作者: CrLf    时间: 2012-3-19 16:02

回复 18# qzwqzw


变量名确实写错了,copy 测试文本时漏改一个。
xp 没有出现问题吗?win7 和 vista 下测试过,变量 !,! 确实能存放 8190 个字符长度的变量,而普通非分隔符 变量名则只能存放 8189 个,debug 部分见附文:
for /r 有问题是 10 楼说的呀...是否 for /f 之误?

附:
昨日的猜测有误,再次尝试 debug,全程如下:
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. for /l %%a in (0 1 8192) do (
  4.   set,=a!,!||(echo !,!&echo 长度为 %%a 个字符&debug&exit)
  5. )
  6. rem 将原测试代码改为达到上限时执行 debug,以便手动观察内存内容
复制代码
以下为进入 debug 后的键入的命令及输出:
  1. -d2cl2
  2. 1EEC:0020                                      51 07
复制代码
可知变量表的数据段地址为 0751
  1. -s751:0 lffff ",=aaa"
  2. 0751:0028
复制代码
得知 !,! 变量在变量表中始于 0028 偏移地址
  1. -h 28 1fff
  2. 2027  E029
复制代码
得知相对于 0028 偏移 1fff(即十进制 8191)的地址为 2027
  1. -d751:20
  2. 0751:0020  41 4E 44 2E 43 4F 4D 00-2C 3D 61 61 61 61 61 61   AND.COM.,=aaaaaa
  3. 0751:0030  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  4. 0751:0040  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  5. 0751:0050  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  6. 0751:0060  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  7. 0751:0070  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  8. 0751:0080  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  9. 0751:0090  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  10. -d751:2020
  11. 0751:2020  61 61 61 61 61 61 61 61-42 4C 41 53 54 45 52 3D   aaaaaaaaBLASTER=
  12. 0751:2030  41 32 32 30 20 49 35 20-44 31 20 50 33 33 30 20   A220 I5 D1 P330
  13. 0751:2040  54 33 00 00 01 00 43 3A-5C 57 49 4E 44 4F 57 53   T3....C:\WINDOWS
  14. 0751:2050  5C 53 59 53 54 45 4D 33-32 5C 44 45 42 55 47 2E   \SYSTEM32\DEBUG.
  15. 0751:2060  45 58 45 00 51 8C 56 1E-E8 2C 00 72 17 8B F7 E8   EXE.Q.V..,.r....
  16. 0751:2070  4D 00 00 5F 07 80 3C 00-75 01 4E E8 70 01 2B CE   M.._..<.u.N.p.+.
  17. 0751:2080  06 1F F3 A4 1F 5E C3 BE-4C 8C EB 03 BE 51 8C E8   .....^..L....Q..
  18. 0751:2090  05 00 72 F2 E9 9C 00 FC-E8 41 00 8E 06 1A 96 26   ..r......A.....&
复制代码
观察变量 !,! 头尾内容,发现末尾处的 00 丢失了,并且其后紧随着一串陌生的变量变量内容,于是在批处理中写入如下代码在内存中搜索,结果存于 s.txt
  1. (
  2. (
  3. for %%a in (0 1 2 3 4 5 6 7 8 9 a b c d e f) do @echo s %%a000:0 lffff "BLASTER=A220 I5 D1 P330"
  4. )
  5. echo q
  6. )|debug>s.txt
复制代码
s.txt 的内容:
  1. -s 0000:0 lffff "BLASTER=A220 I5 D1 P330"
  2. 0000:0C7D
  3. 0000:4522
  4. 0000:53F7
  5. 0000:59B7
  6. 0000:5A92
  7. 0000:5BA2
  8. 0000:A568
  9. 0000:B1D7
  10. 0000:B562
  11. -s 1000:0 lffff "BLASTER=A220 I5 D1 P330"
  12. -s 2000:0 lffff "BLASTER=A220 I5 D1 P330"
  13. -s 3000:0 lffff "BLASTER=A220 I5 D1 P330"
  14. -s 4000:0 lffff "BLASTER=A220 I5 D1 P330"
  15. -s 5000:0 lffff "BLASTER=A220 I5 D1 P330"
  16. -s 6000:0 lffff "BLASTER=A220 I5 D1 P330"
  17. -s 7000:0 lffff "BLASTER=A220 I5 D1 P330"
  18. -s 8000:0 lffff "BLASTER=A220 I5 D1 P330"
  19. -s 9000:0 lffff "BLASTER=A220 I5 D1 P330"
  20. 9000:5F02
  21. 9000:FFA6
  22. -s a000:0 lffff "BLASTER=A220 I5 D1 P330"
  23. -s b000:0 lffff "BLASTER=A220 I5 D1 P330"
  24. -s c000:0 lffff "BLASTER=A220 I5 D1 P330"
  25. -s d000:0 lffff "BLASTER=A220 I5 D1 P330"
  26. -s e000:0 lffff "BLASTER=A220 I5 D1 P330"
  27. -s f000:0 lffff "BLASTER=A220 I5 D1 P330"
  28. -q
复制代码
用 d 逐个查看,找到可疑的地址为 9000:5F02
  1. -d9000:5F02
  2. 9000:5F00        42 4C 41 53 54 45-52 3D 41 32 32 30 20 49     BLASTER=A220 I
  3. 9000:5F10  35 20 44 31 20 50 33 33-30 20 54 33 0D 74 2E 65   5 D1 P330 T3.t.e
  4. 9000:5F20  78 65 0D 61 61 61 61 61-61 61 61 61 61 61 61 61   xe.aaaaaaaaaaaaa
  5. 9000:5F30  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  6. 9000:5F40  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  7. 9000:5F50  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  8. 9000:5F60  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  9. 9000:5F70  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  10. 9000:5F80  B4 0E   
复制代码
果然有关联,但怎么有个 0d 呢?而且 BLASTER=A220  等内容既然是正序排列,为何位于 !,! 内容之前?
用 d9000:5e00 l140 继续观察前后内容(之前没发现,原来刚才那段段数据前面还有内容,汗,那就不是堆栈的原因了),发现这里似乎和变量表的内容不同
  1. -d9000:5e70 l120
  2. 9000:5E70  8C 16 30 00 2E 8E 16 57-22 BC A2 07 E8 80 31 8A   ..0....W".....1.
  3. 9000:5E80  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  4. 9000:5E90  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  5. 9000:5EA0  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  6. 9000:5EB0  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  7. 9000:5EC0  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  8. 9000:5ED0  61 61 61 61 61 61 61 61-61 61 61 61 00 20 20 20   aaaaaaaaaaaa.
  9. 9000:5EE0  20 20 20 20 20 20 20 20-00 00 00 00 00 20 20 20           .....
  10. 9000:5EF0  20 20 20 20 20 20 20 20-00 00 00 00 61 61 61 61           ....aaaa
  11. 9000:5F00  00 0D 42 4C 41 53 54 45-52 3D 41 32 32 30 20 49   ..BLASTER=A220 I
  12. 9000:5F10  35 20 44 31 20 50 33 33-30 20 54 33 0D 74 2E 65   5 D1 P330 T3.t.e
  13. 9000:5F20  78 65 0D 61 61 61 61 61-61 61 61 61 61 61 61 61   xe.aaaaaaaaaaaaa
  14. 9000:5F30  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  15. 9000:5F40  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  16. 9000:5F50  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  17. 9000:5F60  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  18. 9000:5F70  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   aaaaaaaaaaaaaaaa
  19. 9000:5F80  B4 0E CD 21 2E 8E 1E 1A-96 B8 FF FF 87 06 B4 02   ...!............
复制代码
得出的结果实在让我费解,为何这里也多了个 00 0d...
反复试验了一下,和我昨日的猜测不一样,似乎即使变量长度达不到 8190 也能看到同样的内存数据,那究竟是何处发生数据溢出的呢?
昨天没有想周全,所以当时的猜测不正确,看来仍然是个谜(对我来说)
作者: CrLf    时间: 2012-3-19 16:06

回复 18# qzwqzw


    win7 下测试,这段代码显示 16 个 d(一个一行),似乎没有出现问题。
   对 useback 的 bug 向来是只闻其名不见其人(也许是用得少吧),刻意寻找也不知从何下手,看来 for /f 的幽灵很狡猾啊
作者: lllsoslll    时间: 2012-3-19 22:23

回复 17# powerbat


    for /f ... %%a in ("...")do ....
不知道其他系统是什么状况,
我的Microsoft Windows XP [版本 5.1.2600]
圆括号里的双引号若隔行有异常产生(“不到文件”),是否产生异常和双引号的内容有关
作者: sakulamartain    时间: 2012-3-19 23:18

关注中,学习中
作者: powerbat    时间: 2012-3-20 00:10

回复 21# lllsoslll


    17楼的代码在win7中运行正常。
作者: qzwqzw    时间: 2012-3-20 21:51

回复 19# CrLf
18楼的for /f 的bug代码已经在本地测试过了
发生概率是100%,下面是测试效果

for/r确实是for/f的笔误

11楼代码的问题确实有
是我没有注意到
大概分析了下
可能是这样的原因

首先cmd的命令行长度限制是8192个字符
也就是说命令行缓冲区定义的长度是8192个双字节
排除换行符和空字符有效字符是8190个
而环境变量的长度限制8192字符只体现在变量扩展和显示时
在设置时它的变量长度收到命令行缓冲区长度的限制

其次启用变量延迟后
将不在命令行缓冲区存储环境变量的扩展结果
而采用另外一个词法分析缓冲区
这个缓冲区大概是2*8192个双字节的长度
以便于字符串的切分、合并操作的展开

再次cmd支持命令和参数开关之间
除了空格、TAB等普通分隔符之外
也可以有其它的分隔符比如 : . + / [ ] " 等
与普通的分隔符不同
这些分隔符只用于切分命令和参数
不用于参数之间的切分
是cmd为了兼容旧DOS时代的一些用法而做的hack措施
而且它们在词法切分后不会被剔除
所以它们既是分隔符又是操作符
因此在做了命令与参数切分之后
参数串中仍然保留该字符

第四set命令的参数中
很多常见的命令分隔符都会发生转义
TAB、空格、分号、逗号等成为普通字符
等号除了第一个是操作符之外其它也成为普通字符
因此set后的逗号在分隔了set命令和参数之后
继续作为set的参数之一——用作变量名
作者: CrLf    时间: 2012-3-21 05:04

回复 24# qzwqzw


    感觉似乎有道理,但是不知老兄得出的这些结论有何依据?还是仅仅为个人推测呢?没有冒犯的意思,只是想确保论点的“地基”是稳固可靠站得住脚的。
作者: qzwqzw    时间: 2012-3-21 11:00

回复 25# CrLf
这只是对cmd命令行分析过程中的半成品
有些不太确认的结论
我会加注意“可能”、“大致”、“也许”等类似的字样
还有些结论因为需要深入分析和多方面确认
包括set对, / + 等分隔符处理上的不同
所以暂时还没有办法贴出来
作者: CrLf    时间: 2012-3-21 16:10

回复 26# qzwqzw


    大工程啊,建议开个专题来论证,最好能深入浅出讲解一番再附上具体的验证流程,这些溯本追源的命题无论是用于加深本质性的理解还是为日后重写 cmd 提供扎实的理论依据都很有必要
作者: CrLf    时间: 2012-4-2 00:48

本帖最后由 CrLf 于 2012-4-2 00:51 编辑

回复 8# CrLf


zqz 给出了一个帖子的链接:http://www.bathome.net/viewthread.php?tid=3614
点进去一看,觉得很有意思,我这才知道 xp 和 win7 对于 for /f 中输入参数的解析方式是不同的,于是用代码测试看二者差异何在。在此不作长篇大论,概括性地总结一下个人理解:

首先,无论在 xp 还是 win7 下,for /f 的特性都是当参数以设备开头时将触发以类似 shift 的方式对参数挨个解析
  1. set str="bbs.bathome.net" "t e s t ?" "abc*"
  2. for /f "delims=" %%a in (nul %str%) do echo 第一节为:%%a
  3. rem win7 下,顺序解析将终止于用户所指定的最后一个参数或所遇见的第一个字符串参数再或者发生找不到指定文件的错误,所以 win7 下可以用非遍历的方法获取第一节的字符串——虽然比遍历方案方便,但是当然不如以双引号为分隔符来得直接,所以这是个没什么价值的方案
复制代码
  1. set str="bbs.bathome.net" "t e s t ?" "abc*"
  2. for /f "delims=" %%a in (nul %str:" "=" @"%) do echo %%a
  3. rem 而在 xp 下,顺序解析不会因为遇到字符串参数而终止,但是对下一个参数进行解析时,会诡异地吞掉该参数的首字符,所以此处要加个 @ 前缀以免双引号被吞出错,于是实现了 for /f 式的字符串循环,好处是不受 * 与 ? 的干扰,但是格式要求太严格,远不如以换行符为分隔符进行字符串循环来得方便有效,所以也是个没啥用处的技巧...
复制代码

作者: vsax    时间: 2012-12-25 13:38

这个帖子必须收藏,都是高手的讨论啊。
作者: plp626    时间: 2012-12-25 23:02

  1. ->ver
  2. Microsoft Windows XP [版本 5.1.2600]
  3. ->type aaabc.bat
  4. @echo off
  5. set str="bbs.bathome.net" "t e s t ?" "abc*"
  6. for /f "delims=" %%a in (nul %str%) do echo 第一节为:%%a
  7. ->aaabc.bat
  8. 第一节为:bbs.bathome.net
  9. 系统找不到文件 t e s t ?"。
  10. ->
复制代码

作者: CrLf    时间: 2012-12-26 03:12

回复 30# plp626


    这就是 win7 和 xp 的区别...
作者: /zhqsystem/zhq    时间: 2015-11-17 22:27

多谢楼主,一直不知道,原来16进制可以直接换算10进制的,
作者: wanfcl83    时间: 2015-12-10 11:51

人不淫荡枉少年




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