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

[原创] [分享]批处理命令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 不被满足,同理,也可以使用 ^=、^, 或 @ 来代替 ^;,原理大同小异。
2

评分人数

奇技淫巧!

TOP

人不淫荡枉少年

TOP

回复 2# applba


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

评分人数

TOP

  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
复制代码
这个会误判,需要完善。(此法是否可行?)
1

评分人数

    • CrLf: 感谢测试!技术 + 1

TOP

本帖最后由 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 更快,而且也没有什么盲点
复制代码
不过限于非遍历性质的代码思路,对存在多个测或多个试的行没办法进行有效判断

TOP

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

测试没有得到预想的结果
结果输出了file变量的值
而没有输出file对应的文件内容
1

评分人数

    • CrLf: 良师益友,感谢指正错误以及给与启发PB + 10 技术 + 1
天的白色影子

TOP

本帖最后由 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... 再分别对当前项进行判断其类型。

TOP

另外,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% 但也报错
复制代码
测试了好几中组合,结果失灵时不灵,似乎找不到规律

TOP

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

你在9楼提到的for/f的内存泄漏问题值得关注
最近也在搜集有关for/r内存泄漏相关的资料
不知道还有没有其它比较大概率泄漏的代码案例可以提供?
天的白色影子

TOP

本帖最后由 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 字符长度的内容

TOP

什么也不说,看看以下的东东!~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
复制代码
寂寞是黑白的,但黑白不是寂寞,是永恒。BAT 需要的不是可能,而是智慧。

TOP

回复 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
复制代码
寂寞是黑白的,但黑白不是寂寞,是永恒。BAT 需要的不是可能,而是智慧。

TOP

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

TOP

回复 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
复制代码
寂寞是黑白的,但黑白不是寂寞,是永恒。BAT 需要的不是可能,而是智慧。

TOP

返回列表