示范sed指定某行插入 追加和全局替换

有时候会有这样的需求,在指定的行后面或者是前面追加一行,这个时候可以使用sed来完成,具体用法如下

  • a 在指定的行后面追加一行

  • b 在指定的行前面追加一行

使用指定的行号追加内容,在使用行号的过程中,需要注意的问题有以下

  • N;后面只能使用偶数,且不可以为0

  • a表示在指定的行后面追加一行

  • i表示在当前行插入一行,如果指定行为4,其实最终的结果插入行的位置是第三行。

sed -i 'N;2anewline' 1.txt

sed -i 'N;2inewline' 1.txt
[root@lanmp shell]# cat << eof > 1.txt

> a

> b

> c

> d

> eof

[root@lanmp shell]# sed -i 'N;2a2222' 1.txt

[root@lanmp shell]# cat 1.txt

a

b

2222

c

d

[root@lanmp shell]# sed -i 'N;2i2222' 1.txt

[root@lanmp shell]# cat 1.txt

2222

a

b

2222

c

d
[root@RS2 shell]# cat 1.txt

1111

3333

[root@RS2 shell]# sed -i '/^1111$/a2222' 1.txt ; cat 1.txt

1111

2222

3333

[root@RS2 shell]# sed -i '/^1111$/i000' 1.txt ; cat 1.txt

0000

1111

2222

3333

下面是把所有匹配的字符都替换为指定的字符

[root@SLAVE ~]# cat << eof > 1.txt

> 1111

> 222333333

> 44444444445

> eof

[root@SLAVE ~]# sed -i 2{s/2/3/g} 1.txt

[root@SLAVE ~]# cat 1.txt

1111

333333333

44444444445

[root@SLAVE ~]# sed -i 3{s/4/5/g} 1.txt

[root@SLAVE ~]# cat !$

cat 1.txt

1111

333333333

55555555555

使用sed对nginx配置文件进行删除和列出虚拟主机操作

带着需要使用sed来对nginx配置文件进行操作的强烈需求,于是开始了学习sed的高级应用。虽然之前也一直在用sed,但也只是接触到了s替换命令,其它高级的命令没用到,所以没有动力去学。一直觉得要学到点东西,前提是你现在有一问题,需要用到这个技术来解决,而且有强烈的渴望要把这个问题解决,这时候你学习这项技术会事半功倍。否则学习起来会非常的枯燥无味,效率低,甚至会放弃。下面是我最近学sed得出的成果,备忘一下,以防失忆。
一、列出所有虚拟的server_name和对应的root

  1. sed -n "
  2. /servers*{/{
  3. H
  4. :loop
  5. n;H
  6. /}/{
  7. s/}//;x;H
  8. s/.*server_names*([^;]*);.*/1/p
  9. x
  10. s/.*roots*([^;]*);.*/1/p
  11. s/.*//g;x
  12. }
  13. /{/!b loop
  14. :loop1
  15. n;H
  16. /}/!b loop1
  17. b loop
  18. }
  19. " nginx-devops.webres.wang.conf

二、删除指定server_name的虚拟主机

  1. domain="devops.webres.wang"
  2. sed -i -n "
  3. /servers*{/{
  4. H
  5. :loop
  6. n;H
  7. /}/{
  8. s/}//;x
  9. /.*server_names*$domain.*/d
  10. p;d
  11. }
  12. /{/!b loop
  13. :loop1
  14. n;H
  15. /}/!b loop1
  16. b loop
  17. }
  18. p
  19. " nginx-devops.webres.wang.conf

代码有点多,就不具体分析了,具体说下如何找出server {} 代码块。

代码分析:
1、首先先关闭自动打印功能,即-n选项,因为下面会用到n命令,会自动打印,所以添加-n之后,使用n命令就不会自动打印了,需要打印时,使用p命令。
2、匹配 server {,如果匹配到,则把pattern space的内容添加到hold space,然后进入loop循环,执行n命令(n命令不必进入新的循环读取下一步到pattern space),再用H附加到hold space
3、判断是否找到代码块结束符},如果找到,进入server {}代码块处理阶段,处理完之后,进入下一个循环,继续找出下一个server{}。
4、如果找不到,再判断是否找到{,如果没有找到,进入loop循环
5、如果找到了{,表示存在if {},或者location {},则必要找到两个}才算是完整的server代码块。此时进入loop1循环,n命令读取下一行,附加到hold space。
6、再判断是否找到},如果没有找到,继续loop1循环,如果找到了,还需要一个},所以跳到loop循环。
7、如此循环下来,直接找到完整的server代码块。

关键的几点提示:
1、n命令是直接清空pattern space,并读取下一行到pattern space,而不需要进入新的循环。在这里的作用是,因为要把pattern space的内容附加到hold space,所以每读取一行,必需先清空pattern space,然后再附加到hold space,这样hold space才不会出现重复内容。要达到清空pattern space,读取下一行,进入下一循环可以做到,但进入下一循环后,必需重新匹配server {,而下一行肯定无法匹配,所以就达不到累积的效果。
2、hold space在这里很重要,它的内容来自pattern space对其进行附加操作。累积到完整的server代码块时,再与pattern space进行交换,交换之前必需把pattern space清空,这样交换后,hold space才能为空,为累积出下一个server代码块做准备。
最后还是建议使用sed的debug工具sedsed查看pattern space和hold space的变化。http://aurelio.net/projects/sedsed/

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/