sed高级应用示例

最近需要使用sed来解析nginx配置文件,而之前使用sed仅限制于对文件的替换及添加文本,不过也基本能满足平时的bash shell脚本的编写工作。但这次需要解析nginx配置文件来对虚拟主机的代码块进行处理,比如对指定虚拟主机的删除,以及列出所有虚拟主机的信息,比如根目录是哪个。单靠简单的匹配是无法满足这个需求了,于是重读了一遍http://www.gnu.org/software/sed/manual/sed.htmlsed的教程,开始渐渐懂得sed的工作原理以及如何使用sed的高级功能。在分析高级应用的例子之前,我们来了解下sed的工作原理,这至关重要。

sed工作原理

sed是一个流文本处理工具,从文件头到文件尾读取,一次只读取一行,并完成一系列操作才继续读取下一行。sed维护两个数据缓冲区,一个是pattern space,一个是hold space。它们初始都为空。pattern space是活跃缓冲区,每一次循环都会清空再存入下一行内容。hold space一个辅助的空间,不会在完成一个循环后清空,会一直保持,它的内容来自使用h,H,g,G命令得来。

sed读取输入流的一行,在读取下一行之前,需要做如下操作(完成这些操作视为完成一个循环):sed从输入流读取一行,删除换行符,并把内容放到pattern space。然后命令开始对pattern space进行操作。每个命令可以有address关联,如/devops.webres.wang/a\hello,/devops.webres.wang/是搜索包括devops.webres.wang的pattern space,然后再执行a\hello增加hello字符操作,/devops.webres.wang/即为address,a为命令。只有address为真时,即匹配成功时,才执行后面的命令。

除非使用特殊的命令,如”D”,否则pattern space会在两个循环之间被清空。而hold space则会保持不变,hold space的内容可以使用‘h’, ‘H’, ‘x’, ‘g’, ‘G’的命令来操作。

高级应用示例分析

下面的例子来自http://www.gnu.org/software/sed/manual/sed.html
示例一
下面的脚本实现了每行80列宽中间对齐,假如文件中有aabb和ccccdddd两行。

  1. #!/usr/bin/sed -f
  2.     
  3.      # Put 80 spaces in the buffer
  4.      1 {
  5.        x
  6.        s/^$/          /
  7.        s/^.*$/&&&&&&&&/
  8.        x
  9.      }
  10.     
  11.      # del leading and trailing spaces
  12.      y/t/ /
  13.      s/^ *//
  14.      s/ *$//
  15.     
  16.      # add a newline and 80 spaces to end of line
  17.      G
  18.     
  19.      # keep first 81 chars (80 + a newline)
  20.      s/^(.{81}).*$/1/
  21.     
  22.      # 2 matches half of the spaces, which are moved to the beginning
  23.      s/^(.*)n(.*)2/21/

代码分析:

  1. 读取第一行时,pattern space为aabb,hold space为空。
  2. 以下命令分析:
  3. 1 {
  4.    x
  5.    s/^$/          /
  6.    s/^.*$/&&&&&&&&/
  7.    x
  8.    }
  9. 匹配第一行,执行如下命令,
  10. 执行x命令:交换pattern space和hold space的内容,结果是,pattern space内容为空,hold space为aabb。
  11. 执行s/^$/          /命令:pattern space为8个空格,hold space不变。
  12. 执行s/^.*$/&&&&&&&&/命令:现在pattern space的空格为80个,hold space不变。
  13. 执行x命令:交换它们的内容,pattern space内容为aabb,hold space为80个空格。
  14.  
  15. 继续执行如下命令:
  16. y/t/ /:替换tab为一个空格
  17. s/^ *//:删除行尾空格
  18. s/ *$//:删除行首空格
  19.  
  20. 执行G命令:pattern space附加一换行符,并附加hold space内容到pattern space,结果是,pattern space为aabb+n+80个空格,hold space保持不变。
  21.  
  22. s/^(.{81}).*$/1/命令:用s命令从行首至行尾取81个字符,包括了换行符。
  23.  
  24. s/^(.*)n(.*)2/21/命令:用正则把pattern space后面的空格分半,并移至行首,这样就实现了80列宽度中间对齐。
  25.  
  26. 继续下面的行读取时,hold space的内容会一直保持不变。

示例二
下面的例子实现了为数字加1的效果,比如一个文件number.txt,文件内容为:

  1. 6

sed代码:

  1. #!/usr/bin/sed -f
  2.     
  3.      /[^0-9]/ d
  4.     
  5.      # replace all leading 9s by _ (any other character except digits, could
  6.      # be used)
  7.      :d
  8.      s/9(_*)$/_1/
  9.      td
  10.     
  11.      # incr last digit only.  The first line adds a most-significant
  12.      # digit of 1 if we have to add a digit.
  13.      #
  14.      # The tn commands are not necessary, but make the thing
  15.      # faster
  16.     
  17.      s/^(_*)$/11/; tn
  18.      s/8(_*)$/91/; tn
  19.      s/7(_*)$/81/; tn
  20.      s/6(_*)$/71/; tn
  21.      s/5(_*)$/61/; tn
  22.      s/4(_*)$/51/; tn
  23.      s/3(_*)$/41/; tn
  24.      s/2(_*)$/31/; tn
  25.      s/1(_*)$/21/; tn
  26.      s/0(_*)$/11/; tn
  27.     
  28.      :n
  29.      y/_/0/

代码分析:

  1. 读取第一行6,放到pattern space,
  2. 执行/[^0-9]/ d:删除不是纯数字的行,pattern space为6
  3. 执行:d :标记下面的命令为子命令,用于跳转
  4. s/9(_*)$/_1/:替换9为_,此时pattern space 6不变。
  5. td :测试label d的子命令是否更改pattern space,如果更改,则跳回d标记处,否则继续往下执行。
  6.  
  7. s/^(_*)$/11/; tn
  8. s/8(_*)$/91/; tn
  9. s/7(_*)$/81/; tn
  10. 执行了以上命令,pattern space还是6
  11. s/6(_*)$/71/; tn
  12. 6替换成了7,此时pattern space为7
  13.  
  14. s/5(_*)$/61/; tn
  15. s/4(_*)$/51/; tn
  16. s/3(_*)$/41/; tn
  17. s/2(_*)$/31/; tn
  18. s/1(_*)$/21/; tn
  19. s/0(_*)$/11/; tn
  20. :n
  21. y/_/0/
  22. 执行以上几个命令,pattern space还是7,打印出7,继续下一循环。

示例三
此例子实现了每行倒序的效果。

  1. #!/usr/bin/sed -f
  2. /../! b
  3.  
  4. # Reverse a line.  Begin embedding the line between two newlines
  5. s/^.*$/n&n/
  6.  
  7. # Move first character at the end.  The regexp matches until
  8. # there are zero or one characters between the markers
  9. tx
  10. 😡
  11. s/(n.)(.*)(.n)/321/
  12. tx
  13.  
  14. # Remove the newline markers
  15. s/n//g

代码分析(以输入abcdef为例):

  1. 读取abcdef,
  2. pattern space为abcdef
  3. /../! b:如果只有一个字符,直接打印,中止此循环,进入下一循环。
  4.  
  5. s/^.*$/n&n/:用两个换行符包围abcdef,此刻pattern space为nabcdefn。
  6.  
  7. 😡
  8. s/(n.)(.*)(.n)/321/
  9. tx
  10.  
  11. 经过s命令后,1为na,2为bcde,3为fn,pattern space变为fnbcdena,此刻首尾字符换了位置。
  12. 执行tx命令,发现pattern space已经改变,跳转到x子命令,继续替换操作。
  13. 再次执行s命令后,换行符中间的字符又首尾调换了一次,pattern space为fencdnba
  14. 再一次s命令,pattern space为fednncba,
  15. 再一次s命令,pattern space不变,
  16. tx检测pattern space不变,于是往下执行,
  17. s/n//g:全局替换n,于是pattern space为fedcba,打印出fedcba。

示例四
下面的示例实现了以行为单位对文件进行倒序查看,相当于linux下的tac命令。

  1. #!/usr/bin/sed -nf
  2.  
  3. # reverse all lines of input, i.e. first line became last, …
  4.  
  5. # from the second line, the buffer (which contains all previous lines)
  6. # is *appended* to current line, so, the order will be reversed
  7. 1! G
  8.  
  9. # on the last line we’re done — print everything
  10. $ p
  11.  
  12. # store everything on the buffer again
  13. h

代码分析:
以文件内容为:
devops.webres.wang
www.baidu.com
www.qq.com
为例:

  1. 读取第一行:
  2. pattern space为devops.webres.wang
  3.  
  4. 1! G,如果不是第一行,执行G,附加一换行符到pattern space,再附加hold space内容到pattern space。此刻pattern space还是devops.webres.wang
  5.  
  6. $ p 如果到文件尾,则打印。
  7.  
  8. h命令:用pattern space内容替换hold space内容,现在pattern space和hold space都为devops.webres.wang。
  9.  
  10. 读取第二行
  11. pattern space为bb,hold space为devops.webres.wang
  12.  
  13. 1! G: pattern space为www.baidu.comndevops.webres.wang
  14. h命令:pattern space和hold space都为www.baidu.comndevops.webres.wang
  15.  
  16. 读取第三行
  17. pattern space为www.qq.com,hold space为www.baidu.comndevops.webres.wang
  18.  
  19. 1! G:pattern space为www.qq.comnwww.baidu.comndevops.webres.wang
  20. h命令:pattern space和hold space都为www.qq.comnwww.baidu.comndevops.webres.wang
  21.  
  22. $ p:已到文件尾,执行打印paatern space操作,结果为:
  23. www.qq.com
  24. www.baidu.com
  25. devops.webres.wang

gnu关于sed教程还有很多示例涉及到sed的高级功能,时间有限,先分析到这里。有空再继续。下篇日志贴出并分析使用sed解析nginx配置文件中的server {}代码块,实现列出虚拟主机的server_name root等信息,以及指定server_name的虚拟主机删除操作。
最后介绍一个很好用的sed debug工具,它可以显示出所有pattern space和hold space实时状态。http://aurelio.net/projects/sedsed/