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

[文本处理] 批处理去除字符串行首空格及for /f 机制的进一步发现

去除字符串行首空格(即字符串左边的空格,前导空格),能兼容特殊字符,且保留空行。
部分思路来源于for /f 的机制。
  1. @echo off
  2. if "%~1" neq "" goto LTrimInit
  3. for /f "eol= tokens=* delims= " %%a in ('"%~f0" LTrimInit') do echo=%%a
  4. pause&exit/b
  5. :LTrimInit
  6. for /f "delims=" %%a in ('findstr /n .* "%~f0"') do (
  7.     set var=%%a
  8.     setlocal enableDelayedExpansion
  9.     set var=!var:*:= !
  10.     echo=!var!
  11.     endlocal
  12. )
  13. goto :eof
复制代码
for /f 选项的机制:(不想写分析过程了,得出的结论虽简单,但花了不少时间探索,走了不少弯路)
解析文件(命令输出、字符串)时选取非空行作为初始循环集(to 新手:空行不是指以空格组成的行,而是该行中只有一个回车),
先用delims指定的分隔符进行分割(把分隔符去掉,得到其他部分),得到一个新循环集,这个循环集才是其他选项的依据。
eol选项看这个循环集是否以它指定的字符开头决定是否过滤掉此循环集(此行)。(过滤掉即直接跳过,后续操作直接忽视,继续处理一下行)
for根据tokens选项在这个循环集能否取到它指定的令牌(部分),决定do后面的子句是否运行。
如果无法取到令牌,则子句不会执行。只要能取到一部分,子句就可以运行。
对于tokens=*,这里*相当于0个或0个以上的匹配,所以即使循环集为空(经delims分割后初始循环集变成空字符串),do子句也会执行(因为正好匹配0个),只是令牌(for的变量)为空。

for /f "delims=." %a in (".") do echo 令牌=[%a],echo命令不执行
for /f "tokens=* delims=." %a in (".") do echo 令牌=[%a],echo命令执行了
不指定tokens时,默认取1。第一句中由于循环集为空,所以echo没有运行。而第二句如上所述,可以运行echo命令。

再如:
for /f "tokens=2" %a in ("hello") do echo 令牌=[%a]
for /f "tokens=1,2" %a in ("hello") do echo 令牌=[%a],[%b]

可以用"delims="指定不要分隔符,但无法设置不要eol的过滤符,即使是用"eol=",也会以引号"为分隔符。
也就是说,无论如何,for /f 总是要包含一个行首过滤符(默认的分隔是分号“;”)。这算得上是一个不小的隐患。


以前一直以为是先用eol过滤,将过滤后的结果再递交给for进行分割,没想到竟然是这样。

有了上面的结论,下面的执行结果就好解释了:
for /f "delims=a" %a in ("aaa;hello") do echo %a

=================================
20090818 23:08 修订。
3

评分人数

    • CrLf: 蛋疼的 eol技术 + 1
    • skuny: for /f "delims=a" %a in (&quotPB + 5
    • tireless: 我也一直以为是先用eol过滤...PB + 5
命令行参考:hh.exe ntcmds.chm::/ntcmds.htm
求助者请拿出诚心,别人才愿意奉献热心!
把查看手册形成条件反射!

这个方法应该能兼容特殊字符了,而且效率还可以。

以前写的逐字替换法:
  1. @echo off
  2. for /f "delims=" %%a in ('findstr /n .* "%~f0"') do (
  3.     set str=%%a
  4.     setlocal enableDelayedExpansion
  5.     set "str=!str:*:=!"
  6.     call :Trim
  7.     echo=!str!
  8.     endlocal
  9. )
  10. echo.&pause&exit/b
  11. :LTrim 去掉左边空格
  12. if "!str:~,1!"==" " (set "str=!str:~1!"&goto LTrim)
  13. goto :eof
  14. :RTrim 去掉右边空格
  15. if "!str:~-1!"==" " (set "str=!str:~,-1!"&goto RTrim)
  16. goto :eof
  17. :Trim 去掉两边空格
  18. if "!str:~,1!"==" " (set "str=!str:~1!"&goto Trim)
  19. if "!str:~-1!"==" " (set "str=!str:~,-1!"&goto Trim)
  20. goto :eof
复制代码
http://www.bathome.net/viewthread.php?tid=3830&page=1#pid24384

去除行尾的空格,还没找到既兼容特殊字符又能保证效率的方法。
希望大家一起来完成。
命令行参考:hh.exe ntcmds.chm::/ntcmds.htm
求助者请拿出诚心,别人才愿意奉献热心!
把查看手册形成条件反射!

TOP

zqz现身。。。
eol该怎么处理?如果要严谨(即使没有什么必要)的话,应该将eol设置成为一个不会出现的字符。。。
Tab?蜂鸣?退格?
第三方命令行工具编程
Http://Hi.Baidu.Com/Console_App

TOP

对于tokens=*,这里*相当于0个或0个以上的匹配,所以即使循环集为空,do子句也会执行(因为正好匹配0个),只是令牌(for的变量)为空。


这句似乎有点问题

TOP

回复 3楼 的帖子

可以设置eol为delims中的一个分隔符。
如果没有指定delims,虽然可以设置eol成为一个不会出现的字符(但你如何确定哪个字符不会出现?1-31号ascii字符也许是个不错的选择,但也不能保证),但更严谨的做法是通过findstr中转,还能保留空行(http://www.bathome.net/viewthread.php?tid=4580)。
1

评分人数

命令行参考:hh.exe ntcmds.chm::/ntcmds.htm
求助者请拿出诚心,别人才愿意奉献热心!
把查看手册形成条件反射!

TOP

回复 4楼 的帖子

可能不太好表述,但就是那个意思:)
可以对照下面那两个例子自己揣摩。
命令行参考:hh.exe ntcmds.chm::/ntcmds.htm
求助者请拿出诚心,别人才愿意奉献热心!
把查看手册形成条件反射!

TOP

先用delims指定的分隔符进行分割(把分隔符去掉,得到其他部分),得到一个新循环集,这个循环集才是其他选项的依据。
eol选项看这个循环集是否以它指定的字符开头决定是否过滤掉此循环集(此行)。(过滤掉即直接跳过,后续操作直接忽视,继续处理一下行)
zqz0012005 发表于 2009-8-16 23:43


此处也即此帖论述的核心似乎值得推敲
虽然给定的示例很有说服力
但我想这样的设计原则有违常理
因为对文本行分割必然牵涉字符串遍历和令牌分配
而这是一个“高耗能”的过程
如果说for/f在花费了很大性能完成分割之后
才发现文本行有eol字符
然后选择放弃所有分割成果和令牌
显然是一个不“节能”的设计思路

这么明显的性能浪费
微软的开发人员会看不出来吗?
显然,微软应该有更为合理的设计

因此我猜测得到一个更为合理的解释——
for/f在对文本文件进行完前期的处理之后(比如skip)
会首先对当前文本行行首进行delims的过滤
而这是一个低耗能的工作
因为很可能行首没有分隔符或只有很少的分隔符
之后再判断剩余的文本行是否以eol开头
如果是则跳过这一行继续下一行
否则再进行真正的delims分割和tokens分配

至于for的设计者为什么要先做行首过滤
我猜测这是延续了高级语言的分析特性
因为在很多高级语言中的行注释字符
是允许放在空格、tab等分隔符之后的
编译器为了兼容这种用法
就必然会对每行代码先做行首过滤
再做语法分析看是否是注释行

一家之言,仅供参考!
天的白色影子

TOP

头疼,真的很头疼。

TOP

返回列表