最近需要使用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两行。
- #!/usr/bin/sed -f
- # Put 80 spaces in the buffer
- 1 {
- x
- s/^$/ /
- s/^.*$/&&&&&&&&/
- x
- }
- # del leading and trailing spaces
- y/t/ /
- s/^ *//
- s/ *$//
- # add a newline and 80 spaces to end of line
- G
- # keep first 81 chars (80 + a newline)
- s/^(.{81}).*$/1/
- # 2 matches half of the spaces, which are moved to the beginning
- s/^(.*)n(.*)2/21/
代码分析:
- 读取第一行时,pattern space为aabb,hold space为空。
- 以下命令分析:
- 1 {
- x
- s/^$/ /
- s/^.*$/&&&&&&&&/
- x
- }
- 匹配第一行,执行如下命令,
- 执行x命令:交换pattern space和hold space的内容,结果是,pattern space内容为空,hold space为aabb。
- 执行s/^$/ /命令:pattern space为8个空格,hold space不变。
- 执行s/^.*$/&&&&&&&&/命令:现在pattern space的空格为80个,hold space不变。
- 执行x命令:交换它们的内容,pattern space内容为aabb,hold space为80个空格。
- 继续执行如下命令:
- y/t/ /:替换tab为一个空格
- s/^ *//:删除行尾空格
- s/ *$//:删除行首空格
- 执行G命令:pattern space附加一换行符,并附加hold space内容到pattern space,结果是,pattern space为aabb+n+80个空格,hold space保持不变。
- s/^(.{81}).*$/1/命令:用s命令从行首至行尾取81个字符,包括了换行符。
- s/^(.*)n(.*)2/21/命令:用正则把pattern space后面的空格分半,并移至行首,这样就实现了80列宽度中间对齐。
- 继续下面的行读取时,hold space的内容会一直保持不变。
示例二
下面的例子实现了为数字加1的效果,比如一个文件number.txt,文件内容为:
- 6
sed代码:
- #!/usr/bin/sed -f
- /[^0-9]/ d
- # replace all leading 9s by _ (any other character except digits, could
- # be used)
- :d
- s/9(_*)$/_1/
- td
- # incr last digit only. The first line adds a most-significant
- # digit of 1 if we have to add a digit.
- #
- # The tn commands are not necessary, but make the thing
- # faster
- s/^(_*)$/11/; tn
- s/8(_*)$/91/; tn
- s/7(_*)$/81/; tn
- s/6(_*)$/71/; tn
- s/5(_*)$/61/; tn
- s/4(_*)$/51/; tn
- s/3(_*)$/41/; tn
- s/2(_*)$/31/; tn
- s/1(_*)$/21/; tn
- s/0(_*)$/11/; tn
- :n
- y/_/0/
代码分析:
- 读取第一行6,放到pattern space,
- 执行/[^0-9]/ d:删除不是纯数字的行,pattern space为6
- 执行:d :标记下面的命令为子命令,用于跳转
- s/9(_*)$/_1/:替换9为_,此时pattern space 6不变。
- td :测试label d的子命令是否更改pattern space,如果更改,则跳回d标记处,否则继续往下执行。
- s/^(_*)$/11/; tn
- s/8(_*)$/91/; tn
- s/7(_*)$/81/; tn
- 执行了以上命令,pattern space还是6
- s/6(_*)$/71/; tn
- 6替换成了7,此时pattern space为7
- s/5(_*)$/61/; tn
- s/4(_*)$/51/; tn
- s/3(_*)$/41/; tn
- s/2(_*)$/31/; tn
- s/1(_*)$/21/; tn
- s/0(_*)$/11/; tn
- :n
- y/_/0/
- 执行以上几个命令,pattern space还是7,打印出7,继续下一循环。
示例三
此例子实现了每行倒序的效果。
- #!/usr/bin/sed -f
- /../! b
- # Reverse a line. Begin embedding the line between two newlines
- s/^.*$/n&n/
- # Move first character at the end. The regexp matches until
- # there are zero or one characters between the markers
- tx
- 😡
- s/(n.)(.*)(.n)/321/
- tx
- # Remove the newline markers
- s/n//g
代码分析(以输入abcdef为例):
- 读取abcdef,
- pattern space为abcdef
- /../! b:如果只有一个字符,直接打印,中止此循环,进入下一循环。
- s/^.*$/n&n/:用两个换行符包围abcdef,此刻pattern space为nabcdefn。
- 😡
- s/(n.)(.*)(.n)/321/
- tx
- 经过s命令后,1为na,2为bcde,3为fn,pattern space变为fnbcdena,此刻首尾字符换了位置。
- 执行tx命令,发现pattern space已经改变,跳转到x子命令,继续替换操作。
- 再次执行s命令后,换行符中间的字符又首尾调换了一次,pattern space为fencdnba
- 再一次s命令,pattern space为fednncba,
- 再一次s命令,pattern space不变,
- tx检测pattern space不变,于是往下执行,
- s/n//g:全局替换n,于是pattern space为fedcba,打印出fedcba。
示例四
下面的示例实现了以行为单位对文件进行倒序查看,相当于linux下的tac命令。
- #!/usr/bin/sed -nf
- # reverse all lines of input, i.e. first line became last, …
- # from the second line, the buffer (which contains all previous lines)
- # is *appended* to current line, so, the order will be reversed
- 1! G
- # on the last line we’re done — print everything
- $ p
- # store everything on the buffer again
- h
代码分析:
以文件内容为:
devops.webres.wang
www.baidu.com
www.qq.com
为例:
- 读取第一行:
- pattern space为devops.webres.wang
- 1! G,如果不是第一行,执行G,附加一换行符到pattern space,再附加hold space内容到pattern space。此刻pattern space还是devops.webres.wang
- $ p 如果到文件尾,则打印。
- h命令:用pattern space内容替换hold space内容,现在pattern space和hold space都为devops.webres.wang。
- 读取第二行
- pattern space为bb,hold space为devops.webres.wang
- 1! G: pattern space为www.baidu.comndevops.webres.wang
- h命令:pattern space和hold space都为www.baidu.comndevops.webres.wang
- 读取第三行
- pattern space为www.qq.com,hold space为www.baidu.comndevops.webres.wang
- 1! G:pattern space为www.qq.comnwww.baidu.comndevops.webres.wang
- h命令:pattern space和hold space都为www.qq.comnwww.baidu.comndevops.webres.wang
- $ p:已到文件尾,执行打印paatern space操作,结果为:
- www.qq.com
- www.baidu.com
- 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/