sed学习总结及实例

来源:互联网 发布:数据新闻博客 编辑:程序博客网 时间:2024/06/05 09:30

sed是简单小巧但功能非常强大的工具。在上一篇博客中自己注解了sed的info文档中那些比较复杂的脚本示例,最近工作中使用到sed,复习了一下,再做一篇总结放在这里:

  • sed有两块缓冲区:pattern space和hold space。pattern space中存放待处理的目标文本,hold space供程序员自己使用。
  • sed处理流程:
    1. 清空pattern space。(如果这是对该输入流的第一次读取,则也将hold space初始化为空)
    2. 从输入流中读取一行,去除末尾的换行符,将其放入pattern space。
    3. 执行sed脚本。sed脚本由一系列命令组成。因为sed是按行处理,所以sed命令的含义主要是“对于什么样的行,则进行什么样的处理”。例如,“把第3行删除”,或者“如果这一行包含this,则把其中的this替换成that”。其中“包含什么样的行”由address指定,“执行什么样的处理”由sed命令序列表达。
    4. 脚本执行完毕,如果输入流中还有内容,转至1,开始下一轮处理。
  • 所以,学习sed,就是学习如何“指定需要处理的行”(address),以及学习我们能使用sed“进行什么样的操作”(command)。
  • 注意的是,sed都是按行处理,即使指定的是一个address range,比如sed -e '3,7s/this/that/g',s命令会被执行5次(从第3行到第7行每行执行一次)而不是只执行一次就将第3到第7行的所有this换成that。可以使用sed的N命令或者其他命令读入多行文本到pattern space。
  • sed使用/M或者/m来切换到"multiple line mode"(即多行处理模式)。所谓“多行处理模式”,是指在这种模式下,元字符^不再表示字符串起始位置,而是表示换行符后面的空字符串,元字符$也不再表示字符串末尾位置,而是表示换行符前面的空字符串。自我感觉这种模式没有任何作用,只会把问题弄得混乱,因为完全可以使用\n来匹配换行符,将其作为普通字符用在正则表达式中。
  • sed中的调试非常简便,可以使用上一篇博客中的那个python脚本来调试sed,也可以使用sed中的l命令或者p命令来输出pattern space中的内容进行调试。在以下的“去除多余空行“的方法二中使用l命令进行调试。

练习几个例子,以便将来温习:准备以下文件:

hbwang@jcwkyl:/home/hbwang$cat testabcde f1     2 345en dint main () {    printf("hello\n");    return 0;}last empty
  1. 去掉所有换行符:
    • 方法一:vim打开文件,执行:1,$s/\n//即可。这里使用sed的方法:读入一行,删除换行符,再读入下一行,直到整个文件处理结束。实现如下:
      hbwang@jcwkyl:/home/hbwang$sed -e :a -e "N;s/\n//;ba" testabcde f1     2 345en dint main () {    printf("hello\n");    return 0;}last empty
      命令解释:-e参数用来添加执行命令。":a"用来设置一个名字为a的label以供后来的goto使用(sed中有两个跳转命令:b和t。b为无条件跳转,t为当上一个s命令替换成功时跳转)。"N;s/\n//;ba"用分号隔开了三条命令。在执行这些命令之前,pattern space中存放着一个输入行(其末尾的换行符已经去掉),N命令在pattern space后面追加一个换行符并从输入流中读入下一行。此时pattern space中的内容就是输入流中的连续两行文本的拷贝,用s命令去掉换行符,然后再用b命令无条件跳转到label ":a"上继续处理后面的文本。执行结果如上所示,成功达到了目标。
      这里有个细节。如果复习一下开头所说的sed的处理流程(execution cycle),会发现每一轮开始sed都会自己删除pattern space的内容并从输入流中读取下一行。照刚才这种方法,pattern space中两行之间的换行符去掉了,但第二行末尾的换行符似乎没有机会被删除。然而实验结果证明这种方法是对的。不妨再验证一下,在每次替换后用l命令查看一下pattern space中的内容(l命令会以raw format来输出pattern space中的内容,其中特殊字符都会以其转义形式输出,例如换行符会被输出成为\n,字符串末尾会输出一个$来标记):
      hbwang@jcwkyl:/home/hbwang$sed -e :a -e "N;s/\n//;l;ba" testabcde f$abcde f1     2 345$abcde f1     2 345en d$abcde f1     2 345en d$abcde f1     2 345en d$abcde f1     2 345en d$abcde f1     2 345en d$abcde f1     2 345en dint main () {$abcde f1     2 345en dint main () {    printf("hello\\n");$abcde f1     2 345en dint main () {    printf("hello\\n");    return \0;$abcde f1     2 345en dint main () {    printf("hello\\n");    return \0;}$abcde f1     2 345en dint main () {    printf("hello\\n");    return \0;}$abcde f1     2 345en dint main () {    printf("hello\\n");    return \0;}$abcde f1     2 345en dint main () {    printf("hello\\n");    return \0;}last empty$abcde f1     2 345en dint main () {    printf("hello\n");    return 0;}last empty
      可以看到,pattern space中的内容一直没有被清除:删除第一、二行之间的换行符后,又读入第三行,删除第二、三行之间的换行符,直到整个文件处理完毕。
      那如果要实现刚才设想的情况,只删除第奇数个换行符而保留第偶数个换行符,只需要让sed去除一个换行符后直接开始下一轮执行即可,用d命令可以做到:
      hbwang@jcwkyl:/home/hbwang$sed -e :a -e "N;s/\n//;p;d" testabcde f1     2 345en dint main () {    printf("hello\n");    return 0;}last empty

      d命令会删除pattern space中的内容,所以在删除前先用p命令把pattern space中的内容输出出来。
    • 方法二:读入整个文件,用一条命令删除所有换行符。
      因为sed在每个cycle开始都会清空pattern space,所以要使用sed读入整个文件,要么在一个cycle中用一个循环把所有行都读入到pattern space,要么利用hold space不会被清空的特性,每读一行就把它添加到hold space中,整个文件读完,hold space中也就保存了整个文件的内容。sed中只支持两种跳转方式,即:
      • if(是目标行) { /* handle */ } else { /*非目标行 */}
      • if(替换成功) { goto label; } else { /* 继续执行 */}
      用第一种跳转方式读取整个文件容易些,实现如下:
      hbwang@jcwkyl:/home/hbwang$sed -e '$!{H;d}' -e '${H;x;s/\n//g;}' testabcde f1     2 345en dint main () {    printf("hello\n");    return 0;}last empty或者:hbwang@jcwkyl:/home/hbwang$sed -n -e '$!H' -e '${H;x;s/\n//gp}' testabcde f1     2 345en dint main () {    printf("hello\n");    return 0;}last empty或者:hbwang@jcwkyl:/home/hbwang$sed -n -e '$!H' -e '${H;g;s/\n//gp}' testabcde f1     2 345en dint main () {    printf("hello\n");    return 0;}last empty

      其中,"$"是最后一行地址,"$!"表示“不是最后一行”。实现1,'$!{H,d}',表示如果不是最后一行,用H命令在hold space上添加一个换行符并把pattern space中的内容append到hold space,然后删除pattern space(如果不删除,因为我们没有指定-n选项,所以pattern space的内容会在进入下一轮cycle前输出出来),'${H;x;s\n//g;}'表示如果是最后一行,用H命令把最后一行添加到hold space,用x命令交换pattern space和hold space(其目的是把hold space内容放到pattern space以便s命令处理),然后用s命令删除所有换行符。实现二,和实现一相同,不过指定了-n选项,所以需要用p命令输出自己希望输出的东西。实现三使用g命令,与x命令不同的是g命令直接用hold space的内容覆盖pattern space的内容。
  2. 去掉所有空行空行是只包含空白字符的行。这个处理非常简单:如果当前pattern space中只包含空白字符,删除它就可以:
    hbwang@jcwkyl:/home/hbwang$sed -e "/^[ \t]*$/d" testabcde f1     2 345en dint main () {    printf("hello\n");    return 0;}last empty
  3. 去掉多余的空行
    • 方法一:将连续多个空行变成一个,如果只有一个空行,则不变。有一个简单思路:如果当前行是空行,则删除当前行后续的所有连续空行。换一种描述:对所有的空行,删除其后续的所有连续空行。第二种描述更容易写出sed实现,因为sed基本功能就是“对什么样的行”进行“什么样的操作”。写出来的脚本如下:
      hbwang@jcwkyl:/home/hbwang$sed -e '/^[ \t]*$/{:a;N;s/^\([ \t]*\n\)\+/\1/;ta}' testabcde f1     2 345en dint main () {    printf("hello\n");    return 0;}last empty
      其中/^[ \t]*$/表示匹配空行,当从输入流中读取到一个空行时,就执行后面{}括起来的命令序列:继续从输入流中读取下一行,如果发现pattern space中是两个连续空行,则把这连续的两个空行替换成一个空行。
    • 方法二:像冒泡排序算法那样,将相邻的两个空行替换成一个空行。实现:
      hbwang@jcwkyl:/home/hbwang$sed -e ":a; N; s/\n\([ \t]*\n\)\1/\n\1/g; ba" testabcde f1     2 345en dint main () {    printf("hello\n");    return 0;}last empty
      如果不清楚这条sed命令是如何完成任务的,可以使用l命令输出每次执行s命令前的pattern space中的内容,以理解s命令为什么写成那样:
      hbwang@jcwkyl:/home/hbwang$sed -n -e ":a ; N; l 200; ba" testabc\nde f$ <===执行到N时,pattern space中是abd$,执行命令N后,pattern space中是abd\nde f$abc\nde f\n1     2 345$abc\nde f\n1     2 345\nen d$abc\nde f\n1     2 345\nen d\n$abc\nde f\n1     2 345\nen d\n\n$abc\nde f\n1     2 345\nen d\n\n\n$abc\nde f\n1     2 345\nen d\n\n\n\n$abc\nde f\n1     2 345\nen d\n\n\n\n\nint main () {$abc\nde f\n1     2 345\nen d\n\n\n\n\nint main () {\n    printf("hello\\n");$abc\nde f\n1     2 345\nen d\n\n\n\n\nint main () {\n    printf("hello\\n");\n    return 0;$abc\nde f\n1     2 345\nen d\n\n\n\n\nint main () {\n    printf("hello\\n");\n    return 0;\n}$abc\nde f\n1     2 345\nen d\n\n\n\n\nint main () {\n    printf("hello\\n");\n    return 0;\n}\n$abc\nde f\n1     2 345\nen d\n\n\n\n\nint main () {\n    printf("hello\\n");\n    return 0;\n}\n\n$abc\nde f\n1     2 345\nen d\n\n\n\n\nint main () {\n    printf("hello\\n");\n    return 0;\n}\n\n\nlast empty$
      以上,"l 200"表示以raw format输出pattern space的内容,200指定一行输出200个字符。
    • 方法三:和方法一类似,比方法一更简单的实现:
      hbwang@jcwkyl:/home/hbwang$sed -e "/^[[:blank:]]*$/{h; s/.*//; :a; N; s/^\n[[:blank:]]*$//; ta; s/^\n//; H; x;}" testabcde f1     2 345en dint main () {    printf("hello\n");    return 0;}last empty
      这里用POSIX标准的方括号表达式[:blank:]表示空格和制表符,和方法一相同,当读到一个空行时,就执行后面的命令序列:首先把读取到的空行原样保存到hold space中去(h命令的作用),然后s/.*//命令用来清空pattern space中的内容(GNU版本的sed另外提供了z命令来清空pattern spac),N在pattern space中append一个换行符然后从输入流中读取下一行append到pattern space,s/^\n[ \t]*$//命令用来删除空行(因为N命令会在append下一行前先在pattern space中append一个换行符,所以s命令中匹配了换行符),如果替换成功,说明方才N命令读取的确实是一个空行,则用t命令跳转到label a去循环执行以删除空行。如果替换失败,则表示N命令读取到了一个非空行,则先去掉pattern space的第一个换行符(这个换行符是被N命令自动添加的),然后用H命令将pattern space中的内容append到hold space,最后用x命令把hold space中的内容放到pattern space做为最终的处理结果,这里的x命令也可以用g命令。