Board logo

标题: [文本处理] [分享]批处理利用读取输出时间差忽略文本行 [打印本页]

作者: weichenxiehou    时间: 2011-2-23 11:12     标题: [分享]批处理利用读取输出时间差忽略文本行

本帖最后由 weichenxiehou 于 2011-10-12 22:16 编辑

前言:开贴的前一晚,看到了论坛的一篇求助帖,大致意思是将文本中指定字符后的所有内容删除,抱着练练手试一试的心态(新手嘛,再简单的题也该做做)用常规设置标志的方法写出了一个程序到qq群里讨论,正好batman前辈也在,他就这个代码本身可以提高效率的角度给了我一些指导,改出了一个比较简洁高效的代码,这种方法不是开这个帖要讨论的内容,就此略过。batman指出,那篇帖子的最终目的也很简单,就是消灭掉文本文件最后一行,在前辈的引导下,接触到一种新的思路,如标题所示"利用读取输出时间差忽略文本行",从忽略文本最后一行、两行到通用的忽略文本最后n行,一边写一边问费了九牛二虎之力得到一个比较通用的代码,batman鼓励在下在此开一贴,谈谈整个过程的心得体会,供新手共勉,由于能力有限,很多写得不好的地方请老鸟们海涵,总之,菜鸟吃菜,老鸟莫怪!

首先我在命令提示符窗口用命令for /l %i in (1 1 20) do @echo %i>>a.txt在当前test目录下建立了一个测试文本a.txt,内容为:
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
复制代码
batman给了一个忽略文本最后一行的代码如下:
  1. @echo off&setlocal enabledelayedexpansion
  2. for /f "delims=" %%i in (a.txt) do (
  3.     if defined str echo !str!
  4.     set "str=%%i"
  5. )
  6. pause
复制代码
执行后果然只显示了前面19行的内容,最后一行的"20"果然没有成功显示,当时没大看明白是怎么回事,始终想不通第一行是怎么显示出来的,最后一行又是怎么被忽略掉的呢?因为我的新手思维一直停留在程序执行到哪一行就干哪一行的事,没注意到环境变量的变化情况,batman前辈又耐心地把原理具体讲解了一下,抽象出一个"时间差"的概念,根本原因就是显示代码在前,变量赋值代码在后,也就是一个顺序问题而已,慢慢想一想我也明白了,读取第一行的时候,因为str没有定义,所以没有此时没显示第一行内容,然后将第一行的内容赋值给str,读取第二行的时候,因为str前面定义过了,所以首先显示它的内容,当然显示的是第一行的内容了!然后在把第二行的内容复制给str,造成的结果是:读取第二行时显示第一行的内容,读取第三行时显示第二行的内容,……,读取最后一行时显示倒数第二行的内容,也就达到了忽略最后一行的目的。
       作为练习,batman叫在下写出忽略最后两行的代码,可能鄙人资质有限,开窍慢了点儿,忙碌十几分钟还是无果而终,于是batman只好无奈地给出了答案:
  1. @echo off&setlocal enabledelayedexpansion
  2. for /f "delims=" %%i in (a.txt) do (
  3.     if defined str if defined var echo !var!
  4.     set "var=!str!"&set "str=%%i"
  5. )
复制代码
咋一看,我好像又明白了:读取第一行时,两个变量都没定义,所以没显示,随后将第一行内容赋值给str;读取第二行时,str定义了但var没定义,所以还是没显示,随后将str的值赋值给var,也就是var中为第一行内容,再将第二行内容赋值给str;读取第三行时,因为两个变量都定义了,所以显示var的值,也就是显示第一行的内容,随后var获得第二行的内容,str获得第三行的内容;以此类推,读取第四行时显示第二行内容;读取第五行时,显示第三行内容;……;读取最后一行时显示倒数第三行的内容,也就是忽略掉了最后两行。
       哎呀!很有成就感!于是我自告奋勇要写忽略掉最后三行内容的代码,可是,时不与我啊,batman直接加大难度说忽略掉最后n行的内容,我然后就被雷倒了,于是没学会走就直接开始跑了,n的值靠用户输入,我一想,妈呀~要是按上面写,那for命令的主体那两句代码该有多长啊?脑袋灵光一闪,何不用for /l把这两句语句生成到两个变量中呢?然后把这两个变量放到for语句中就ok了,思路有了,马上开启defanive他们开发的batproject.exe开始编程。
       生成第一条语句倒还好办,直接用
  1. for /l %%a in (1 1 %%a) do set "line1=!line1! if defined str%%a"
  2. set "line1=!line1! echo !str%n%!"
复制代码
想法虽好,可是开启变量延迟的情况下电脑它打死不认!为一个字符,用转义字符^和引号都不行,想到第二条语句中也含有这诸多讨厌死了的!,我决定还是把他们放到for命令中,放弃了原来的思路,折腾近一个小时,于是有了我觉得可行的半成品代码:
  1. @echo off&setlocal enabledelayedexpansion
  2. ::没有考虑输入n值不符合要求的情况
  3. set /p n=the value of varable n is:
  4. set line1=
  5. for /l %%b in (1 1 %n%) do (
  6.    set "line1=!line1! if defined str%%b"
  7. )
  8. for /f "delims=" %%a in (a.txt) do (
  9. %line1% echo !str%n%!
  10. for /l %%b in (%n% -1 2) do set /a val=%%b-1&for %%c in (!val!) do set str%%b=!str%%c!
  11. set str1=%%a
  12. )
  13. pause>nul
复制代码
看看表,已经凌晨两点多了,于是终于怀着点儿成就感把代码发给batman,顺便感叹一声:“想成批处理熟手,任重而道远啊!洗洗睡吧……”。
       早上睡醒起来打开电脑,登上qq,收到了batman的回复,我收到了batman的进一步改良版,的确是大家之作:
  1. @echo off&setlocal enabledelayedexpansion
  2. set /p n=请输入要忽略的行数:
  3. for /f "delims=" %%a in (a.txt) do (
  4.     if defined _%n% echo !_%n%!
  5.     for /l %%b in (%n% -1 2) do (
  6.         set /a str=%%b-1
  7.         for %%c in (!str!) do set "_%%b=!_%%c!"
  8.     )
  9.     set "_1=%%a"
  10. )
  11. pause>nul
复制代码
刚看我还有点儿震惊,咋代码比我那玩意儿少这么多呢?细看之下,茅塞顿开!我苦心打造的变量line1原来是花拳绣腿,中看不中用,直接判断str%n%是否存在不就完了?因为下面的代码的意思就是只有前一个变量定义了后一个变量才能定义啊!于是我就灰溜溜心悦诚服地跑到这儿来发帖子了……

总结一下收获:
1、单纯就批处理而言,学会了一种方法。
2、有了一次难忘的经历,原来多走弯路可能会让以后的路更平直,我等菜鸟就应该怀着虚心的精神向前辈请教,多练,多想,定能在批处理之家快乐成长。

最后真心感谢batman给予的帮助和指导,祝您工作顺利,生活幸福!祝愿批处理之家越走越好,未来一片坦途!

[ 本帖最后由 weichenxiehou 于 2011-2-23 11:16 编辑 ]
作者: batman    时间: 2011-2-23 11:48

这篇学习心得写得不错,将自己整个学习思索的过程描述得很清楚,可以为新手学习所借鉴。
学习就是要有这样的态度,这样才能学到真东西,希望新手们都能学习楼主这种认真扎实的学
习精神,如此是批处理之幸也。
作者: Batcher    时间: 2011-2-23 12:18

楼主是不是再考虑一下如何处理包含感叹号的行?
作者: weichenxiehou    时间: 2011-2-23 14:15     标题: 回复 3楼 的帖子

batcher管理员,你好!如何处理感叹号,我想了半天,还是不行啊!不开启变量延迟用call语句改了半天没改出来,局部开启变量延迟,可变量赋值那条语句中又必须要使用变量延迟啊?开启多个递归层的话如果n值大了又不行,实在想不出咋办了……如果您有时间,希望指点一下,就题解题就可以了。谢谢

[ 本帖最后由 weichenxiehou 于 2011-2-23 14:18 编辑 ]
作者: CrLf    时间: 2011-2-23 15:07

处理大文件的办法:
  1. @echo off
  2. findstr /n .* 1.txt|sort /r >2.txt
  3. for /f "skip=1 tokens=1* delims=:" %%a in (2.txt) do echo %%b
复制代码
当然了,小文件用这个不合算
作者: Batcher    时间: 2011-2-23 17:21     标题: 回复 5楼 的帖子

1、这样不是改变了文本的原始顺序吗,恐怕无法满足实际需求;
2、即使不考虑是否打乱原始顺序,sort命令也不会按照你的想法去工作,它无法保证你删掉的是最后一行,用这个数据试试就知道:
  1. BatHome0000000011
  2. BatHome0000000002
  3. BatHome0000000003
  4. BatHome0000000004
  5. BatHome0000000005
  6. BatHome0000000006
  7. BatHome0000000007
  8. BatHome0000000008
  9. BatHome0000000009
  10. BatHome0000000001
  11. BatHome0000000012
复制代码

作者: batman    时间: 2011-2-23 17:32

用以下办法兼容文本中有!的情况:
  1. @echo off
  2. set /p n=请输入要忽略的行数:
  3. for /f "delims=" %%a in (a.txt) do (
  4.     if defined _%n% call,echo %%_%n%%%
  5.     for /l %%b in (%n% -1 2) do (
  6.         set /a str=%%b-1
  7.         setlocal enabledelayedexpansion
  8.         for %%c in (!str!) do endlocal&call,set "_%%b=%%_%%c%%"
  9.     )
  10.     set "_1=%%a"
  11. )
  12. pause>nul
复制代码

作者: batman    时间: 2011-2-23 17:36

或者干脆这样:
  1. @echo off
  2. set /p n=请输入要忽略的行数:
  3. for /f "delims=" %%a in (a.txt) do (
  4.     if defined _%n% call,echo %%_%n%%%
  5.     for /l %%b in (%n% -1 2) do (
  6.         set /a str=%%b-1
  7.         call,call,set "_%%b=%%%%_%%str%%%%%%"
  8.     )
  9.     set "_1=%%a"
  10. )
  11. pause>nul
复制代码

作者: batman    时间: 2011-2-23 17:47

但以上两种方法都是以降低代码效率为代价来兼容!字符的,个人建议如要兼容特殊字符还是采用别的办法吧!
作者: Batcher    时间: 2011-2-23 18:14     标题: 回复 4楼 的帖子

可以先考虑一下如果文本中有空行,该怎么处理。
作者: weichenxiehou    时间: 2011-2-23 18:31     标题: 回复 7楼 的帖子

原来是在这一段代码开启变量延迟,只让!%%c!这两个感叹号被当成特殊字符,受教了。8楼的代码用了两个call,好像是会预处理3次?这两个地方都是我没突破的地方,看来还是经验不够。
作者: weichenxiehou    时间: 2011-2-23 18:39     标题: 回复 10楼 的帖子

  1. @echo off&setlocal enabledelayedexpansion
  2. set /p n=请输入要忽略的行数:
  3. for /f "delims=" %%a in ('findstr /n .* a.txt') do (
  4.     if defined _%n% echo,!_%n%:*:=!
  5.     for /l %%b in (%n% -1 2) do (
  6.         set /a str=%%b-1
  7.         for %%c in (!str!) do set "_%%b=!_%%c!"
  8.     )
  9.     set "_1=%%a"
  10. )
  11. pause>nul
复制代码
考虑空行的情况可不可以这样?测试了一下,好像还行。

[ 本帖最后由 weichenxiehou 于 2011-2-23 18:44 编辑 ]
作者: liion631818    时间: 2011-2-23 21:40

batman果然厉害啊~~~
call用法学习了~~
作者: CrLf    时间: 2011-10-12 23:57

不考虑特殊字符时可以简化到只用一个 if:
  1. @echo off&setlocal enabledelayedexpansion
  2. for /f "delims=" %%i in (desktop.ini) do (
  3.     if defined var echo !var!
  4.     set var=!str!&set str=%%i
  5. )
  6. pause
复制代码
考虑特殊字符时就麻烦多了,烦...:
  1. @echo off&setlocal enabledelayedexpansion
  2. for /f "delims=" %%i in (%~s0) do (
  3.     if defined var echo !var!
  4.     for /f "delims=" %%j in ("var=!str!") do (
  5.         endlocal
  6.         set %%j
  7.     )
  8.     set str=%%i
  9.     setlocal enabledelayedexpansion
  10. )
  11. pause
复制代码





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