Chinaunix

标题: 文本编辑的一点心得--sed篇 [打印本页]

作者: ly5066113    时间: 2010-08-05 15:43
标题: 文本编辑的一点心得--sed篇
本帖最后由 ly5066113 于 2010-08-14 12:57 编辑

在shell版混了3年多了,文本编辑方面小有心得,写出来与大家分享,黑哥等一笑而过。

读本文之前,需要对sed有一定了解,最好看过sed1line:
http://bbs.chinaunix.net/viewthread.php?tid=336126

本文所有用例的测试环境采用unix-center的ubuntu服务器
http://www.unix-center.net/

具体版本如下:
  1. ly5066113@ubuntu:~$ uname -a
  2. Linux ubuntu 2.6.24-22-generic #1 SMP Mon Nov 24 19:35:06 UTC 2008 x86_64 GNU/Linux
  3. ly5066113@ubuntu:~$ bash --version
  4. GNU bash, version 3.2.39(1)-release (x86_64-pc-linux-gnu)
  5. Copyright (C) 2007 Free Software Foundation, Inc.
  6. ly5066113@ubuntu:~$ sed --version
  7. GNU sed version 4.1.5
  8. Copyright (C) 2003 Free Software Foundation, Inc.
  9. This is free software; see the source for copying conditions.  There is NO
  10. warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE,
  11. to the extent permitted by law.
复制代码
下面结合实例介绍sed应用的几个方面。

一、标签

        b label ,无条件跳转到标签label,如果label没有指定,跳转到命令的结尾
        t label ,如果最后一次输入的最后一个 s/// 子命令执行成功,跳转到标签label,如果label没有指定,跳转到命令的结尾

        例1: 用标签完成 是AA就加上YES,不是AA就加NO
        http://bbs.chinaunix.net/viewthread.php?tid=1632469

        使用t命令:
  1. ly5066113@ubuntu:~$ cat urfile
  2. AA
  3. BC
  4. AA
  5. CB
  6. CC
  7. AA
  8. ly5066113@ubuntu:~$ sed '/^AA/s/$/ YES/;t;s/$/ NO/' urfile
  9. AA YES
  10. BC NO
  11. AA YES
  12. CB NO
  13. CC NO
  14. AA YES
复制代码
如果是AA,执行s/$/ YES/,s命令执行成功,执行t命令,没有标签,跳转到命令的结尾,这样将会跳过后面的s/$/ NO/
        如果不是AA,s/$/ YES/不执行,则t命令也不执行,只执行后面的s/$/ NO/

        使用b命令:
  1. ly5066113@ubuntu:~$ sed '/^AA/ba;s/$/ NO/;b;:a;s/$/ YES/' urfile
  2. AA YES
  3. BC NO
  4. AA YES
  5. CB NO
  6. CC NO
  7. AA YES
复制代码
如果是AA,执行ba,跳转到标签a处,这样会跳过中间的s/$/ NO/;b,只执行后面的s/$/ YES/
        如果不是AA,ba不执行,执行s/$/ NO/,执行b,没有标签,跳转到命令的结尾,这样将会跳过后面的s/$/ YES/


        例2: 合并行:
        http://bbs.chinaunix.net/viewthread.php?tid=1381004
  1. ly5066113@ubuntu:~$ cat urfile
  2. 114.113.144.2:
  3. 19ms
  4. 19ms
  5. 19ms
  6. 36ms
  7. 22ms
  8. 19ms
  9. 18ms
  10. 218.61.204.73:
  11. 0ms
  12. 0ms
  13. 0ms
  14. 0ms
  15. 0ms
  16. 0ms
  17. 0ms
  18. ly5066113@ubuntu:~$ sed ':a;$!N;/ms$/s/\n/ /;ta;P;D' urfile
  19. 114.113.144.2: 19ms 19ms 19ms 36ms 22ms 19ms 18ms
  20. 218.61.204.73: 0ms 0ms 0ms 0ms 0ms 0ms 0ms
复制代码
实现思路:
        1、读入下一行数据
        2、判断是否以ms结尾
        3、如果是,替换\n为空格,跳转到1
        4、如果不是,打印本行数据,删除本行数据,跳转到1
       
        代码实现:
        :a                   #定义标签a
        $!N                  #不是最后一行,执行N命令
        /ms$/s/\n/ /         #如果以ms结尾,将\n替换为空格
        ta                   #如果s///命令执行成功,跳转到标签a处
        P                    #打印pattern space的第一行
        D                    #删除pattern space的第一行,循环
       
        此代码是使用sed进行合并行操作的典型代码,对于不同的情况,只需要将/ms$/替换成需要的正则表达式即可,思路上是通用的。


        sed的标签类似于C语言中的goto,cjaizss兄说过,写代码的时候要有状态机的思想,代码只不过是一种实现。
        下面是他用sed写的加法,里面应用了大量的标签,有兴趣可以研究下:
        http://bbs.chinaunix.net/viewthread.php?tid=1063437


二、记数

        sed不同于awk,内部没有数学运算,是否可以实现记数器,答案是肯定的,因为sed是图灵完备的。

        例3:如何替换文中第2次和第4次出现的指定字符串
        http://bbs3.chinaunix.net/thread-1512243-1-1.html
        本例中采用的是这种方案:
  1. sed ':a ; N ; $!ba ;s/root/mmmm/4'
复制代码
但这个方案有2个弊端:
        1、需要将文本一次性读入到pattern space,如果文件很大,是不行的
        2、如果指定字符串在同一行内出现多次,那么这个方法也是不行的
       
        那么是否有其他的替代方案呢?让我们先从简单的开始。
        将文本中第1次出现a的行替换为b
  1. ly5066113@ubuntu:~$ cat urfile
  2. a
  3. a
  4. a
  5. a
  6. a
  7. a
  8. ly5066113@ubuntu:~$ sed '0,/a/{s//b/}' urfile
  9. b
  10. a
  11. a
  12. a
  13. a
  14. a
复制代码
我们可以利用0,/a/这样的地址区间来定位第一次出现a的行。
        好,现在提升难度,将文本中第2次出现a的行替换为b
  1. ly5066113@ubuntu:~$ sed '0,/a/b;s/a/b/;ta;b;:a;n;ba' urfile
  2. a
  3. b
  4. a
  5. a
  6. a
  7. a
复制代码
继续,将文本中第3次出现a的行替换为b
  1. ly5066113@ubuntu:~$ sed '0,/a/b;/a/ba;b;:a;n;s/a/b/;tb;ba;:b;n;bb' urfile
  2. a
  3. a
  4. b
  5. a
  6. a
  7. a
复制代码
利用0,/a/和标签,也实现了,但同时我们发现,随着次数的增加,代码将会变的越来越烦琐

        是时候另辟蹊径了,下面介绍一下我的方法,我为其命名为“打点记数法”
        主要的思路是这样的:
        利用sed的hold space,当遇到匹配行时,向hold space里面“打一个.”,使用 . 的个数来记录匹配的次数
        如果 . 的个数达到了要求,则执行相应的操作
  1. ly5066113@ubuntu:~$ sed '/a/{x;s/^/./;/^.\{3\}$/{x;s/a/b/;b};x}' urfile
  2. a
  3. a
  4. b
  5. a
  6. a
  7. a
  8. ly5066113@ubuntu:~$ sed '/a/{x;s/^/./;/^.\{4\}$/{x;s/a/b/;b};x}' urfile
  9. a
  10. a
  11. a
  12. b
  13. a
  14. a
  15. ly5066113@ubuntu:~$ sed '/a/{x;s/^/./;/^.\{5\}$/{x;s/a/b/;b};x}' urfile
  16. a
  17. a
  18. a
  19. a
  20. b
  21. a
复制代码
我们可以看到,对于次数的增加,我们只需要调整需要匹配的数值即可。
        /a/{                 #匹配时,开始记数
                x                #交换pattern space与hold space
                s/^/./           #向hold space打一个 .
                /^.\{3\}$/{      #判断 . 的个数是否达到要求
                        x            #如果达到要求,交换hold space与pattern space
                        s/a/b/       #进行替换
                        b            #跳转到代码结束
                }                #
                x                #交换hold space与pattern space
        }                    #


        接下来我们看看“打点记数法”的另一种应用:
        例4:提取函数
        http://bbs.chinaunix.net/viewthread.php?tid=1328688
  1. ly5066113@ubuntu:~$ cat urfile
  2. void
  3. foo(int a, int b) {
  4. //impl. skipped
  5. }


  6. void bigfunction() {
  7. int x1, x2, y1, y2, z1, z2;

  8. // after many lines
  9. bar(); foo (x1, x2);
  10. ly5066113@ubuntu:~$ sed -n 'N
  11. > /foo/{
  12. >         :a
  13. >         /{/{
  14. >                 x
  15. >                 s/^/./
  16. >                 x
  17. >         }
  18. >         /}/{
  19. >                 x
  20. >                 s/.//
  21. >                 /^$/{
  22. >                         x
  23. >                         p
  24. >                         q
  25. >                 }
  26. >                 x
  27. >         }
  28. >         p
  29. >         n
  30. >         ba
  31. > }
  32. > D' urfile
  33. void
  34. foo(int a, int b) {
  35. //impl. skipped
  36. }
复制代码
此代码的主要思路,就是我在原贴中的回复:
        当匹配 { 时,x;s/^/./;x ,向hold space里面放一个 .
        当匹配 } 时,x;s/.//;x ,将hold space里的 . 去掉一个
        就这样,当hold space再次为空时,就表示所有的 { 和 } 都配对了


三、lookup table

        在讲lookup table技术之前,先讲一下“回溯引用”
        回溯引用对于大家来说并不陌生,会经常用于 s/REGEXP/REPLACEMENT/ 中REPLACEMENT部分
        但回溯引用并不是只能用于REPLACEMENT部分,在REGEXP中也是可以使用的。
       
        例5:找出100以内,个位与十位相同的2位数
  1. ly5066113@ubuntu:~$ seq 100 | sed -n '/^\(.\)\1$/p'
  2. 11
  3. 22
  4. 33
  5. 44
  6. 55
  7. 66
  8. 77
  9. 88
  10. 99
复制代码
我们可以看到,这个问题利用回溯引用很容易解决。
        \1表示前面\(.\)中内容,换种说法就是\1与前面\(.\)是相同的,那么就是个位与十位相同了


        在理解了REGEXP中的回溯引用之后,我们来看看lookup table技术
        lookup table就是回溯引用的方式进行前后定位,然后取出我们需要内容

        例6: AIX下怎么用DATE取上月的月份
        http://bbs.chinaunix.net/viewthread.php?tid=995655
        UNIX下一般都没有GNU date,即便有GNU date,在某些时间点(如3月31号)上进行取上月操作(-1 month)的时候也有问题
  1. ly5066113@ubuntu:~$ date +%m
  2. 08
  3. ly5066113@ubuntu:~$ date +%m | sed 's/$/b12a01a02a03a04a05a06a07a08a09a10a11a12/;s/^\(..\)b.*\(..\)a\1.*/\2/'
  4. 07
复制代码
我们来看看这段代码是如何工作的:
        1、构造一个列表,字母a左边的2位数字是右边2位数字的上一个月
        2、利用lookup table取出上一个月

        pattern space初始为:
        08
        第一个s命令处理后pattern space变为:
        08b12a01a02a03a04a05a06a07a08a09a10a11a12
        下面我们重点来看看第二个s命令是怎么工作的:
        s/^\(..\)b.*\(..\)a\1.*/\2/
        将pattern space里面的内容按照上面的正则表达式进行分解
        ^\(..\)              08
        b.*                  b12a01a02a03a04a05a06a
        \(..\)               07
        a\1                  a08
        .*                   a09a10a11a12
        整个过程就是通过第一个括号里面的内容 08 ,定位到后面的 a08 ,从而取出它前面的2位数字 07 ,也就是第二个括号里的内容 \2
        这种方法就称之为 lookup table


        例7:文本处理
        http://bbs.chinaunix.net/viewthread.php?tid=1385995
  1. ly5066113@ubuntu:~$ cat urfile
  2. 172.27.38.0&1=99&2=100
  3. 192.168.9.2&1=100&3=111
  4. 202.96.64.68&1=99&2=1&3=111
  5. 202.96.69.38&1=99&3=111&4=110
  6. 202.77.88.99&1=99&2=111&3=66&4=100&5=44
  7. ly5066113@ubuntu:~$ sed -r 's/&/\n1\n2\n3\n4\n5&/;:a;s/\n(.)(.*)&\1=([^&]+)/\t\3\2/;ta;s/\n./\t0/g' urfile
  8. 172.27.38.0     99      100     0       0       0
  9. 192.168.9.2     100     0       111     0       0
  10. 202.96.64.68    99      1       111     0       0
  11. 202.96.69.38    99      0       111     110     0
  12. 202.77.88.99    99      111     66      100     44
复制代码
整体思路,用原贴中dream3401的描述:
        1、产生1,2,3,4,5的"坐标"
        2、对每天"有坐标的赋值"中的值代入坐标
        3、对没有"赋值的坐标"代入0

        我们以第一行数据为例,看看这段代码是怎么工作的:
        pattern space初始为:
        172.27.38.0&1=99&2=100
        s/&/\n1\n2\n3\n4\n5&/后:
        172.27.38.0\n1\n2\n3\n4\n5&1=99&2=100
        s/\n(.)(.*)&\1=([^&]+)/\t\3\2/后:
        172.27.38.0     99\n2\n3\n4\n5&2=100
        s命令执行成功,t命令执行,跳转到标签a处,再次执行s/\n(.)(.*)&\1=([^&]+)/\t\3\2/:
        172.27.38.0     99      100\n3\n4\n5
        s命令执行成功,t命令执行,跳转到标签a处,再次执行s/\n(.)(.*)&\1=([^&]+)/\t\3\2/,s命令执行失败,无替换
        t命令不执行,执行s/\n./\t0/g:
        172.27.38.0     99      100     0       0       0

        对于以上步骤,第一个s命令和最后一个s命令都不难理解,关键是中间的这句:
        s/\n(.)(.*)&\1=([^&]+)/\t\3\2/
        那我们以第一次的执行为例,将pattern space里面的内容按照上面的正则表达式进行分解
        \n(.)                \n1
        (.*)                 \n2\n3\n4\n5
        &\1=                 &1=
        ([^&]+)              99
        利用第一个括号的数字1,定位到后面&1=中的数字1,从而取出=号后面的数字99
        172.27.38.0                \n1\n2\n3\n4\n5&1=99                &2=100
        172.27.38.0     99\n2\n3\n4\n5                                &2=100
        此正则表达式在工作的过程中,开头的 172.27.38.0 和结尾的 &2=100 都是没有处理的,处理的只是中间的一部分


        例7和例6虽然都是用的lookup table技术,但思路梢有不同。
        例6是构造一个列表,然后从列表中lookup出想要的值
        例7也是构造一个列表,但是将外面的值填充到列表中
        无论是那种方式,lookup table技术的基本做法都是先构造一个列表,然后用回溯引用定位,从而得到我们需要的值


四、GNU拓展

        最后,介绍几个GNU sed的拓展功能,个人认为比较实用:
       
        1、\U \L \u \l
        大小写转换
  1. ly5066113@ubuntu:~$ echo 'abc' | sed 's/.*/\U&/'
  2. ABC
  3. ly5066113@ubuntu:~$ echo 'abc' | sed 's/.*/\u&/'
  4. Abc
  5. ly5066113@ubuntu:~$ echo 'ABC' | sed 's/.*/\L&/'
  6. abc
  7. ly5066113@ubuntu:~$ echo 'ABC' | sed 's/.*/\l&/'
  8. aBC
复制代码
2、/REGEXP/I
        正则匹配忽略大小写
  1. ly5066113@ubuntu:~$ echo 'AbC' | sed -n '/abc/Ip'
  2. AbC
复制代码
3、FIRST~STEP
        取出奇数行或者偶数行
  1. ly5066113@ubuntu:~$ seq 10 | sed '0~2d'
  2. 1
  3. 3
  4. 5
  5. 7
  6. 9
  7. ly5066113@ubuntu:~$ seq 10 | sed '1~2d'
  8. 2
  9. 4
  10. 6
  11. 8
复制代码
每5行合并成1行,类似 xargs -n5
  1. ly5066113@ubuntu:~$ seq 20 | xargs -n5
  2. 1 2 3 4 5
  3. 6 7 8 9 10
  4. 11 12 13 14 15
  5. 16 17 18 19 20
  6. ly5066113@ubuntu:~$ seq 20 | sed ':a;N;s/\n/ /;0~5b;ba'
  7. 1 2 3 4 5
  8. 6 7 8 9 10
  9. 11 12 13 14 15
  10. 16 17 18 19 20
复制代码
4、\%REGEXP%
        进行路径匹配时,使用此方法,可以剩去很多转义符\
  1. 16 17 18 19 20
  2. ly5066113@ubuntu:~$ echo 'a/b/c/d/e/f' | sed -n '/a\/b\/c\/d/p'
  3. a/b/c/d/e/f
  4. ly5066113@ubuntu:~$ echo 'a/b/c/d/e/f' | sed -n '\%a/b/c/d%p'
  5. a/b/c/d/e/f
复制代码
5、ADDR1,+N
        地址 +行数,可以实现类似 grep -A N 的功能
  1. ly5066113@ubuntu:~$ seq 10 | grep -A2 '5'
  2. 5
  3. 6
  4. 7
  5. ly5066113@ubuntu:~$ seq 10 | sed -n '/5/,+2p'
  6. 5
  7. 6
  8. 7
复制代码
文笔有限,如果有描述不清的地方请大家见谅。希望本文会对大家学习sed有所帮助。




        关于sed记数,补充几个例子

        例8:实现 awk '{print NR,$0}' 的功能
        来自info sed中的examples(略有改动)
  1. ly5066113@ubuntu:~$ head sed.info | awk '{print NR,$0}'
  2. 1 File: sed.info,  Node: Top,  Next: Introduction,  Up: (dir)
  3. 2
  4. 3 sed, a stream editor
  5. 4 ********************
  6. 5
  7. 6 This file documents version 4.1.5 of GNU `sed', a stream editor.
  8. 7
  9. 8    Copyright (C) 1998, 1999, 2001, 2002, 2003, 2004 Free Software
  10. 9 Foundation, Inc.
  11. 10
  12. ly5066113@ubuntu:~$ head sed.info | sed -nf test.sed
  13. 1 File: sed.info,  Node: Top,  Next: Introduction,  Up: (dir)
  14. 2
  15. 3 sed, a stream editor
  16. 4 ********************
  17. 5
  18. 6 This file documents version 4.1.5 of GNU `sed', a stream editor.
  19. 7
  20. 8    Copyright (C) 1998, 1999, 2001, 2002, 2003, 2004 Free Software
  21. 9 Foundation, Inc.
  22. 10
  23. ly5066113@ubuntu:~$ cat test.sed
  24. #! /usr/bin/sed -f
  25. x
  26. 1s/^/1/
  27. G
  28. s/\n/ /p
  29. s/ .*//
  30. /^9*$/s/^/0/
  31. s/.9*$/x&/
  32. h
  33. s/.*x//
  34. y/0123456789/1234567890/
  35. x
  36. s/x.*//
  37. G
  38. s/\n//
  39. h
复制代码
x                              #交换pattern space与hold space,保存读入的内容
        1s/^/1/                        #如果是第一行,初始化行号1
        G                              #将保存的内容追加回pattern space
        s/\n/ /p                       #将换行替换为空格,并打印
        s/ .*//                        #去处空格以后的所有内容,pattern space只剩下行号
        /^9*$/s/^/0/                   #如果行号都为9,在前面补0
        s/.9*$/x&/                     #用x分隔不需要改变和需要改变的数字
        h                              #将pattern space中的内容保存到hold space
        s/.*x//                        #删除不需要改变的数字
        y/0123456789/1234567890/       #对数字进行 +1 的操作
        x                              #交换pattern space与hold space
        s/x.*//                        #删除需要改变的数字
        G                              #将改变后的数字追加回pattern space
        s/\n//                         #删除换行,得到新的行号
        h                              #保存新行号到hold space

        整体的思路:
        每读入一行记录,将保存在hold space中的行号(如果是第一行,需要初始化),和本行记录合并输出
        然后将行号 +1 ,保存至hold space

        代码的核心部分就是实现“行号 +1”,具体做法:
        将行号分为2个部分,一部分保持不变(此部分可能没有),一部分进行改变,然后将需要改变的部分进行处理
        将处理后的结果与前面不变的部分拼接在一起,形成新的行号,但这里有个情况需要考虑,就是进位
        我们已实际的例子来看看
        如果行号是 123
        那么首先将其分成2个部分变为  12x3
        x左边的12不需要改变,x右边的3需要变成4
        那么就对3进行y/0123456789/1234567890/的操作,将其变成4
        然后拼上前面的12变为 124
        如果行号是129(需要进位)
        那么首先将其分成2个部分变为  1x29
        x左边的1不需要改变,x右边的29需要变成30
        那么就对29进行y/0123456789/1234567890/的操作,将其变成30
        然后拼上前面的1变为 130
        这里面有个特殊情况需要考虑,就是如果行号都为9
        9, 99, 999 之类的情况,那么我们需要在前面加数字0用做进位用

        例9:实现 wc -c 的功能
        来自info sed中的examples(略有改动)
  1. ly5066113@ubuntu:~$ wc -c urfile
  2. 254 urfile
  3. ly5066113@ubuntu:~$ sed -nf test.sed urfile
  4. 254
  5. ly5066113@ubuntu:~$ cat test.sed
  6. #! /usr/bin/sed -f
  7. s/./a/g
  8. H
  9. x
  10. s/\n/a/
  11. : a;  s/aaaaaaaaaa/b/g; t b; b done
  12. : b;  s/bbbbbbbbbb/c/g; t c; b done
  13. : c;  s/cccccccccc/d/g; t d; b done
  14. : d;  s/dddddddddd/e/g; t e; b done
  15. : e;  s/eeeeeeeeee/f/g; t f; b done
  16. : f;  s/ffffffffff/g/g; t g; b done
  17. : g;  s/gggggggggg/h/g; t h; b done
  18. : h;  s/hhhhhhhhhh//g
  19. : done
  20. $! {
  21.         h
  22.         b
  23. }
  24. : loop
  25. /a/! s/[b-h]*/&0/
  26. s/aaaaaaaaa/9/
  27. s/aaaaaaaa/8/
  28. s/aaaaaaa/7/
  29. s/aaaaaa/6/
  30. s/aaaaa/5/
  31. s/aaaa/4/
  32. s/aaa/3/
  33. s/aa/2/
  34. s/a/1/
  35. y/bcdefgh/abcdefg/
  36. /[a-h]/ b loop
  37. p
复制代码
此段代码虽然看起来烦琐,但思路却比上一例好理解。
        每读一行数据,将里面所有的字符都替换成字母a,因为sed读数据时会将换行符(\n)去掉
        所以我们利用H命令产生的\n将其补充回来,也替换成字母a,统一做字符统计
        为了节省内存开销,提高效率,这里做了进位的处理,就是将10个a替换成1个b,10个b替换成1个c 。。。
        这样到最后,字母a的个数就代表个位数字,字母b的个数就代表十位数字,字母c的个数代表百位数字。。。
        如果最后剩下是这样一串字符:
        ccbbbbbaaaa
        那么就表示总共的字符数为:254
        本段代码的统计是有上限的,如果字符数量超过1亿,将无法得到正确结果
        可以通过增加替换的次数来增加统计上限,如 s/hhhhhhhhhh/i/g , s/iiiiiiiiii/j/g 。。。

        例10:实现 awk '{sum+=$1}END{print sum}'
        http://sed.sourceforge.net/grabbag/scripts/add_decs.sed
  1. ly5066113@ubuntu:~$ seq 100 | awk '{sum+=$1}END{print sum}'
  2. 5050
  3. ly5066113@ubuntu:~$ seq 100 | sed -nf test.sed
  4. 5050
  5. ly5066113@ubuntu:~$ seq 1000 | awk '{sum+=$1}END{print sum}'
  6. 500500
  7. ly5066113@ubuntu:~$ seq 1000 | sed -nf test.sed
  8. 500500
  9. ly5066113@ubuntu:~$ cat test.sed
  10. #! /usr/bin/sed -f

  11. # This is an alternative approach to summing numbers,
  12. # which works a digit at a time and hence has unlimited
  13. # precision.  This time it is done with lookup tables,
  14. # and uses only 10 commands.

  15. G
  16. s/\n/-/
  17. s/$/-/
  18. s/$/;9aaaaaaaaa98aaaaaaaa87aaaaaaa76aaaaaa65aaaaa54aaaa43aaa32aa21a100/

  19. :loop
  20. /^--[^a]/!{
  21.         # Convert next digit from both terms into analog form
  22.         # and put the two groups next to each other
  23.         s/^\([0-9a]*\)\([0-9]\)-\([^-]*\)-\(.*;.*\2\(a*\)\2.*\)/\1-\3-\5\4/
  24.         s/^\([^-]*\)-\([0-9a]*\)\([0-9]\)-\(.*;.*\3\(a*\)\3.*\)/\1-\2-\5\4/

  25.         # Back to decimal, but keeping the carry in analog form
  26.         # \2 matches an `a' if there are at least ten a's, else nothing
  27.         #
  28.         #    1-------------                       3-      4----------------------
  29.         #                        2                                                5----
  30.         s/-\(aaaaaaaaa\(a\)\)\{0,1\}\(a*\)\([0-9]*;.*\([0-9]\)\3\5\)/-\2\5\4/
  31.         b loop
  32. }
  33. s/^--\([^;]*\);.*/\1/
  34. h
  35. $p
复制代码
这段代码很巧妙,先是将需要相加的2个数字替换成对应的字母a的个数,然后将字母a合并在一起,
        在替换回相加后的结果数字,如果超过10就保留一个a表示进位。
        例如:123 + 456
        那么就先将3和6替换成aaa和aaaaaa,然后合并aaaaaaaaa
        这样在利用lookup table将aaaaaaaaa替换成9,就完成了加法的操作
        如果 125 + 456
        5和6相加最后就会变为a1,字母a回会在计算2加5的时候一并计算
        这样也就实现了进位。
作者: ywlscpl    时间: 2010-08-05 15:46

等到TIM的大作了

作者: welcome008    时间: 2010-08-05 15:47
先抢楼再说
作者: luyi1983    时间: 2010-08-05 15:48
tim 发帖
貌似必须顶
作者: lkk2003rty    时间: 2010-08-05 15:49
搬个板凳慢慢看。。。。。
作者: ywlscpl    时间: 2010-08-05 15:50
精彩啊!
我awk入门是tim带的
为了此帖要好好把sed水平提高一下
作者: 好看的附件    时间: 2010-08-05 15:50
先顶再看。
作者: bbgg1983    时间: 2010-08-05 15:54
不会是三轮哥挖出来的吧?挖出来的也要顶
作者: luyi1983    时间: 2010-08-05 15:55
精彩啊!
我awk入门是tim带的
为了此帖要好好把sed水平提高一下
ywlscpl 发表于 2010-08-05 15:50



    确实 还有tim的打点计数法。。。
作者: blackold    时间: 2010-08-05 15:58
回复 1# ly5066113


    走了那么远的路,才发觉身边没有你;看了那么多的风景,原来你在我心里。

    终于看到Tim老师的原创了,赞!顶!

   马上拜读!
作者: bbgg1983    时间: 2010-08-05 16:03
热切期待blackold tim ywlscpl等大师推出自己的作品
作者: wtuter    时间: 2010-08-05 16:03
回复 10#


几天没上cu,tim的大作都来晚了,

作者: blackold    时间: 2010-08-05 16:06
回复 12# wtuter


    您老人家再忙也不要把大家忘了,可要注意身体!
作者: ly5066113    时间: 2010-08-05 16:09
呵呵,谢谢大家支持!
黑哥常说“授之以鱼 不如授之以渔!”
可是如何“授之以渔”?
现在再看十三问,觉得網中人真的很了不起,因为他可以作到授之以渔。
前些天看了一个故事:
  1. 总经理要求秘书安排次日上午九点开一个会议。在这件事下,什么是任务?什么是结果?通知到所有参会的人员,想要的结果是什么呢?下面是一至九段秘书的不同做法。

  2.   一段秘书的做法:发通知——用电子邮件或在黑板上发个会议通知,然后准备相关会议用品,并参加会议。

  3.   二段秘书的做法:抓落实——发通知后,再打一通电话与参会的人确认,确保每个人被及时通知到。

  4.   三段秘书的做法:重检查——发通知,落实到人后,第二天在会前30分钟提醒与会者参会,确定有没有变动,对临时有急事不能参加会议的人,立即汇报给总经理,保证总经理在会前知悉缺席情况,也给总经理确定缺席的人是否必须参加会议留下时间。

  5.   四段秘书的做法:勤准备——发通知,落实到人,会前通知后,去测试可能用到的投影、电脑等工具是否工作正常,并在会议室门上贴上小条:此会议室明天几点到几点有会议。

  6.   五段秘书的做法:细准备——发通知,落实到人,会前通知,也测试了设备,还先了解这个会议的性质是会?总裁的议题是什么?然后给与会者发去过去与这个议题相关的资料,供他们参考(领导通常都是很健忘的,否则就不会经常对过去一些决定了的事,或者记不清的事争吵)。

  7.   六段秘书的做法:做记录——发通知,落实到人,会前通知,测试了设备,也提供了相关会议资料,还在会议过程中详细做好会议记录(在得到允许的情况下,做一个录音备份)。

  8.   七段秘书的做法:发记录——会后整理好会议记录(录音)给总经理,然后请求总经理是否发给参加会议的人员,或者其他人员。

  9.   八段秘书的做法:定责任——将会议上确定的各项任务,一对一地落实到相关责任人,然后经当事人确认后,形成书面备忘录,交给总经理与当事人一人一份,并定期跟踪各项任务的完成情况,并及时汇报总经理。

  10.   九段秘书的做法:做流程——把上述过程帮成标准化的“会议”流程,让任何一个秘书都可以根据这个流程,把会议服务的结果做到九段,形成不依赖于任何人的会议服务体系!
复制代码
如果本文能让大家从中找到处理某一类问题的方法,那么目的就达到了。
作者: davidbeckham921    时间: 2010-08-05 16:21
认真学习!~~留座!~~
学习是多么令人兴奋的一件事情啊!~~
黑哥都搬凳子学习了,我有什么接口不学呢。
作者: expert1    时间: 2010-08-05 16:24
好贴留名
作者: blackold    时间: 2010-08-05 16:29
已阅,好贴,应该加精。
作者: blackold    时间: 2010-08-05 16:42
太穷了,无法评分。

大家先给我评吧,以后我给大家评。
作者: r2007    时间: 2010-08-05 16:45
太穷了,无法评分。

大家先给我评吧,以后我给大家评。
blackold 发表于 2010-08-05 16:42



    不信章鱼的后果
作者: BangBull    时间: 2010-08-05 16:47
前辈来了!!!         马上收藏此贴
作者: bbgg1983    时间: 2010-08-05 16:48
怎么评分?
作者: Shell_HAT    时间: 2010-08-05 16:48
向Tim哥致敬
作者: expert1    时间: 2010-08-05 16:49
回复 21# bbgg1983


    积分够2K以上就可以了。
作者: blackold    时间: 2010-08-05 16:51
回复 19# r2007


    别往我伤口上撒盐啊。
作者: ywlscpl    时间: 2010-08-05 16:55
回复 24# blackold


经世界杯一役,黑哥在CU富人榜从前20名直接跌出榜单
作者: bbgg1983    时间: 2010-08-05 16:55
回复 23# expert1

后悔世界杯的时候没下狠手,tim哥,你等着,马上就2000了
作者: iori809    时间: 2010-08-05 16:56
互联网的精神又一次得到了印证
作者: blackold    时间: 2010-08-05 17:00
回复 25# ywlscpl


    你忘了哥一夜净赚几W吗?
作者: ywlscpl    时间: 2010-08-05 17:03
回复 28# blackold


够狠,得押多少进去啊
作者: xborant    时间: 2010-08-05 17:05
mark下,晚上看
作者: mr_caoke    时间: 2010-08-05 17:31
狂顶,收藏
作者: blackold    时间: 2010-08-05 17:42
本帖最后由 blackold 于 2010-08-05 17:49 编辑

回复 14# ly5066113


    好故事!

   遗憾的是,往往没有几个人真正看得懂。否则,大家会进步很快,问题自然也很少了。


  同样的贴子,有的人学到很多(渔,思想),有的人只看到了一堆乱七八糟的代码(鱼,还是鱼)。
作者: 122285969    时间: 2010-08-05 17:47
好文章,好故事!收了。
初看一下,觉得好新鲜啊,好多用法都没见过,看来还得加快速度学习了,谢谢TIM大哥!
发现玩SHELL就是在玩益智游戏一样,
作者: jiwang1980    时间: 2010-08-05 18:13
好贴,Mark
回家再看~
作者: chenfeng10000    时间: 2010-08-05 20:10
回复 1# ly5066113


    不错,不知道能Mark住不
作者: lxh3603    时间: 2010-08-05 20:27
赞!顶!
作者: beyondfly    时间: 2010-08-05 21:06
好东西,一定要顶啊
作者: xiaobaibbb    时间: 2010-08-05 21:50
谈到文本格式化,Tim是为数不多的高手之一 ,思路活跃又不死板,为了你上线一次
作者: ubuntuer    时间: 2010-08-05 23:41
可以和 netman的
shell 13问媲美了
作者: Shell_HAT    时间: 2010-08-06 09:20
斑竹在哪里?出来盖个章吧
作者: xiaocongwjb123    时间: 2010-08-06 09:26
sed命令在处理宽字节字符和长宽字节字符方面显得比较吃力。

可以这样说:这一点是sed的一块短板。类似sed命令处理宽字节字

符和长宽字节字符显得比较吃力的命令还有rev命令。

    如果遇到百万级别的处理记录。例如一份500W条记录的邮箱地

址要求剔除里面含有非法字符的邮箱地址,保留正确合法的邮箱地址,

并把被剔除的非法邮箱地址也统计出来。如果这个500W条记录的文

件里面不包含有宽字节字符或者长宽字节字符,则用sed和rev还算勉

强可以处理。如果这个500W条记录的文件里面包含有宽字节和长宽

字节字符的记录,用sed和rev一处理就会报错。处理结果也不够理想,

有些包含有宽字节字符和长宽字节字符的非法邮箱记录根本就没有被

筛选出来。
作者: Shell_HAT    时间: 2010-08-06 09:32
前段时间要处理一个几千万行的文本,vi根本打不开,多亏了有sed,嘿嘿。
作者: unixgiant    时间: 2010-08-06 09:55
好崇拜TIM!!!!!!
作者: G65535    时间: 2010-08-06 10:20
路过帮顶
作者: li2002    时间: 2010-08-06 11:41
学习之
作者: cakaluote    时间: 2010-08-06 15:00
正在努力把awk和sed搞熟悉中!!!
作者: ly5066113    时间: 2010-08-06 15:19
感谢斑竹加精。


关于sed记数,补充2个例子,均来自info sed中的examples(略有改动)

        例8:实现 awk '{print NR,$0}' 的功能
  1. ly5066113@ubuntu:~$ head sed.info | awk '{print NR,$0}'
  2. 1 File: sed.info,  Node: Top,  Next: Introduction,  Up: (dir)
  3. 2
  4. 3 sed, a stream editor
  5. 4 ********************
  6. 5
  7. 6 This file documents version 4.1.5 of GNU `sed', a stream editor.
  8. 7
  9. 8    Copyright (C) 1998, 1999, 2001, 2002, 2003, 2004 Free Software
  10. 9 Foundation, Inc.
  11. 10
  12. ly5066113@ubuntu:~$ head sed.info | sed -nf test.sed
  13. 1 File: sed.info,  Node: Top,  Next: Introduction,  Up: (dir)
  14. 2
  15. 3 sed, a stream editor
  16. 4 ********************
  17. 5
  18. 6 This file documents version 4.1.5 of GNU `sed', a stream editor.
  19. 7
  20. 8    Copyright (C) 1998, 1999, 2001, 2002, 2003, 2004 Free Software
  21. 9 Foundation, Inc.
  22. 10
  23. ly5066113@ubuntu:~$ cat test.sed
  24. #! /usr/bin/sed -f
  25. x
  26. 1s/^/1/
  27. G
  28. s/\n/ /p
  29. s/ .*//
  30. /^9*$/s/^/0/
  31. s/.9*$/x&/
  32. h
  33. s/.*x//
  34. y/0123456789/1234567890/
  35. x
  36. s/x.*//
  37. G
  38. s/\n//
  39. h
复制代码
x                              #交换pattern space与hold space,保存读入的内容
        1s/^/1/                        #如果是第一行,初始化行号1
        G                              #将保存的内容追加回pattern space
        s/\n/ /p                       #将换行替换为空格,并打印
        s/ .*//                        #去处空格以后的所有内容,pattern space只剩下行号
        /^9*$/s/^/0/                   #如果行号都为9,在前面补0
        s/.9*$/x&/                     #用x分隔不需要改变和需要改变的数字
        h                              #将pattern space中的内容保存到hold space
        s/.*x//                        #删除不需要改变的数字
        y/0123456789/1234567890/       #对数字进行 +1 的操作
        x                              #交换pattern space与hold space
        s/x.*//                        #删除需要改变的数字
        G                              #将改变后的数字追加回pattern space
        s/\n//                         #删除换行,得到新的行号
        h                              #保存新行号到hold space

        整体的思路:
        每读入一行记录,将保存在hold space中的行号(如果是第一行,需要初始化),和本行记录合并输出
        然后将行号 +1 ,保存至hold space

        代码的核心部分就是实现“行号 +1”,具体做法:
        将行号分为2个部分,一部分保持不变(此部分可能没有),一部分进行改变,然后将需要改变的部分进行处理
        将处理后的结果与前面不变的部分拼接在一起,形成新的行号,但这里有个情况需要考虑,就是进位
        我们已实际的例子来看看
        如果行号是 123
        那么首先将其分成2个部分变为  12x3
        x左边的12不需要改变,x右边的3需要变成4
        那么就对3进行y/0123456789/1234567890/的操作,将其变成4
        然后拼上前面的12变为 124
        如果行号是129(需要进位)
        那么首先将其分成2个部分变为  1x29
        x左边的1不需要改变,x右边的29需要变成30
        那么就对29进行y/0123456789/1234567890/的操作,将其变成30
        然后拼上前面的1变为 130
        这里面有个特殊情况需要考虑,就是如果行号都为9
        9, 99, 999 之类的情况,那么我们需要在前面加数字0用做进位用

        例9:实现 wc -c 的功能
  1. ly5066113@ubuntu:~$ wc -c urfile
  2. 254 urfile
  3. ly5066113@ubuntu:~$ sed -nf test.sed urfile
  4. 254
  5. ly5066113@ubuntu:~$ cat test.sed
  6. #! /usr/bin/sed -f
  7. s/./a/g
  8. H
  9. x
  10. s/\n/a/
  11. : a;  s/aaaaaaaaaa/b/g; t b; b done
  12. : b;  s/bbbbbbbbbb/c/g; t c; b done
  13. : c;  s/cccccccccc/d/g; t d; b done
  14. : d;  s/dddddddddd/e/g; t e; b done
  15. : e;  s/eeeeeeeeee/f/g; t f; b done
  16. : f;  s/ffffffffff/g/g; t g; b done
  17. : g;  s/gggggggggg/h/g; t h; b done
  18. : h;  s/hhhhhhhhhh//g
  19. : done
  20. $! {
  21.         h
  22.         b
  23. }
  24. : loop
  25. /a/! s/[b-h]*/&0/
  26. s/aaaaaaaaa/9/
  27. s/aaaaaaaa/8/
  28. s/aaaaaaa/7/
  29. s/aaaaaa/6/
  30. s/aaaaa/5/
  31. s/aaaa/4/
  32. s/aaa/3/
  33. s/aa/2/
  34. s/a/1/
  35. y/bcdefgh/abcdefg/
  36. /[a-h]/ b loop
  37. p
复制代码
此段代码虽然看起来烦琐,但思路却比上一例好理解。
        每读一行数据,将里面所有的字符都替换成字母a,因为sed读数据时会将换行符(\n)去掉
        所以我们利用H命令产生的\n将其补充回来,也替换成字母a,统一做字符统计
        为了节省内存开销,提高效率,这里做了进位的处理,就是将10个a替换成1个b,10个b替换成1个c 。。。
        这样到最后,字母a的个数就代表个位数字,字母b的个数就代表十位数字,字母c的个数代表百位数字。。。
        如果最后剩下是这样一串字符:
        ccbbbbbaaaa
        那么就表示总共的字符数为:254
        本段代码的统计是有上限的,如果字符数量超过1亿,将无法得到正确结果
        可以通过增加替换的次数来增加统计上限,如 s/hhhhhhhhhh/i/g , s/iiiiiiiiii/j/g 。。。
作者: li2002    时间: 2010-08-06 15:27
再学习
作者: xiaobaibbb    时间: 2010-08-06 15:41
回复 47# ly5066113


    其实你应该开新帖,否则只有1/4的人看得到
作者: leiing    时间: 2010-08-07 10:22
好文,学习sed
作者: notnumb    时间: 2010-08-07 11:01
提示: 作者被禁止或删除 内容自动屏蔽
作者: rick.zhao    时间: 2010-08-07 11:16
来混shell板,先留下个记号
作者: haokanwk    时间: 2010-08-07 21:10
没想到,Tim也在unix-center上!
作者: Shell_HAT    时间: 2010-08-07 22:35
回复 53# haokanwk


很多人都在呢,呵呵。
作者: etoux    时间: 2010-08-07 22:35
顶!!!!今天累了,明天来仔细看!
作者: rick.zhao    时间: 2010-08-08 12:11
做个记号,好来学习
作者: Tim_tsang    时间: 2010-08-08 16:48
顶起来
作者: obsd178    时间: 2010-08-08 18:25
回复 1# ly5066113


    正在学习sed,感谢分享,继续学习!
作者: yuloveban    时间: 2010-08-10 09:53
回复 1# ly5066113


    非常感谢法师同学的无私奉献,弱弱地问一句“法师同学是长春的吗?”
作者: csonic    时间: 2010-08-10 10:35
Tim 必顶!
作者: kwokcn    时间: 2010-08-10 18:08
Tim大作,拜读。:)
作者: EZWORD    时间: 2010-08-11 19:21
马上摆渡
作者: ckf513728912    时间: 2010-08-12 09:27
好东西 谢谢分享
作者: aplah    时间: 2010-08-12 12:10
好贴留名

虽然技术重点已经不在shell这块,但有时间还是要重新学习
作者: t_stones    时间: 2010-08-12 14:37
膜拜
作者: fllintel    时间: 2010-08-12 16:08
我插..... 我终于可以有时间来逛逛shell 了... 一来就见TIM 大师的力作啊 娃哈哈 收藏收藏
作者: expert1    时间: 2010-08-19 09:41

大牛,膜拜之
作者: ashlv    时间: 2010-08-19 10:02
拜得牛多自有牛庇佑
作者: 皓皓    时间: 2010-08-23 13:46
先看再顶
作者: 好看的附件    时间: 2010-08-23 13:49
再次学习,顶
作者: lucash    时间: 2010-08-23 14:36
膜拜 Tim
作者: xunminggang    时间: 2010-09-06 16:18
回复 1# ly5066113


    分享,谢谢
作者: chinaboywg    时间: 2010-09-07 09:45
{:3_189:} 顶!
作者: chinaboywg    时间: 2010-09-07 09:47
顶!
chinaboywg 发表于 2010-09-07 09:45



谁能告诉我,为什么我发的表情都变成字符了
作者: brokensword1983    时间: 2010-09-07 21:08
支持!同样支持Unix-Center!
作者: 98189    时间: 2010-09-15 22:39
mark
作者: jiannma    时间: 2010-09-20 19:18
好文,好好学习-ing,thank you
作者: cheungjustin    时间: 2010-09-20 23:06
tim哥应该补充
sed
pattern space & hold space
方面的内容。
作者: blueskysee    时间: 2010-09-21 10:14
提示: 作者被禁止或删除 内容自动屏蔽
作者: davidbeckham921    时间: 2010-09-21 13:34
前段时间要处理一个几千万行的文本,vi根本打不开,多亏了有sed,嘿嘿。
Shell_HAT 发表于 2010-08-06 09:32



    我也是没经验,上回用vi打开一个30多M的文件,大概3-400w行,就删了第一行,保存退出后再数就少了一半。教训惨痛啊!~~直接导致很多错误!~~~

    我是记住vi了!~~
作者: su32fn    时间: 2010-09-21 14:57
先膜拜一个,在开始学习! TIM老师~~~
作者: dove-young    时间: 2010-09-21 23:26
强贴留名
作者: jing494485758    时间: 2010-09-22 04:03
服了
作者: keke00    时间: 2010-09-22 12:57
ding礼~~
作者: 思绪纷飞    时间: 2010-09-24 23:26
SED....用的不多 遇到问题就想awk  没想到sed也可以这么强大~顶
作者: leetaedong    时间: 2010-09-25 12:44
说实话,我是从Tim的回贴开始学shell的。
拜读Tim的大作。
作者: seufy88    时间: 2011-07-11 14:15
真的很不错阿。不过太长了,还没完全看
作者: SimpleLittle    时间: 2011-12-06 14:59
云里雾里了一通啊,慢慢消化
作者: linuxwt    时间: 2011-12-07 07:52
强大啊
作者: wuxiaobo_2009    时间: 2012-07-17 09:28
回复 1# ly5066113


打点法就不对:

文本:
aaaa
a
a
a
a
a
a
a
[root@localhost ~]# sed '/a/{x;s/^/./;/^.\{3\}$/{x;s/a/b/;b};x}' urfile
aaaa
a
b
a
a
a
a
a

不是初衷是解决一个匹配“字符串/字符”在文章中的出现次数替换掉,现在没有做到,这个么有意义。

   
作者: peterdocter    时间: 2012-07-17 11:16
同一样的教程,不同时候与水平段看有不同感觉...
初看:有点火星文,很神秘很无语...
中级:由于多实战与使用,发现部分道理与使用..
高级:一看一目了然(本人没有到这阶段,没有得到认证)
反正不管什么样,先收藏到有需要时再不断尝试找出个人所能理解与运行用..
作者: psp2008xxx    时间: 2012-08-02 18:00
太强大了!mark一下慢慢看!回复 1# ly5066113


   
作者: freedos520    时间: 2012-12-06 15:13
路过,马克之后再学习。
作者: udevu    时间: 2012-12-06 15:26
学习中,后排高跷
作者: doufu300    时间: 2012-12-19 07:01
回复 9# luyi1983


    打点计数法是什么?麻烦发个链接
作者: LikeLx    时间: 2013-10-16 17:20
这个打点的方法用的好“风骚”啊
作者: java_html    时间: 2013-12-31 18:56
大神,拜读啊
作者: hhdzhu    时间: 2014-02-20 16:05
请问sed模仿wc命令中,:号的作用是什么?我知道;号是分割命令的吧?还有$! { h b }这个怎么理解?还有里面那个h是标签还是转换模式空间和保留空间的命令,谢谢!
作者: jeffreyst    时间: 2014-02-20 20:29
这么经典的贴子,我才发现!
需要好好研究下,感谢分享!
作者: xudongyun1578    时间: 2014-02-23 20:54
师父,你徒弟根本看不懂~




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2