最近需要使用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关联,如/www.centos.bz/a\\hello,/www.centos.bz/是搜索包括www.centos.bz的pattern space,然后再执行a\\hello增加hello字符操作,/www.centos.bz/即为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/\2\1/
代码分析:
- 读取第一行时,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/\2\1/命令:用正则把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/^\(_*\)$/1\1/; tn
- s/8\(_*\)$/9\1/; tn
- s/7\(_*\)$/8\1/; tn
- s/6\(_*\)$/7\1/; tn
- s/5\(_*\)$/6\1/; tn
- s/4\(_*\)$/5\1/; tn
- s/3\(_*\)$/4\1/; tn
- s/2\(_*\)$/3\1/; tn
- s/1\(_*\)$/2\1/; tn
- s/0\(_*\)$/1\1/; 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/^\(_*\)$/1\1/; tn
- s/8\(_*\)$/9\1/; tn
- s/7\(_*\)$/8\1/; tn
- 执行了以上命令,pattern space还是6
- s/6\(_*\)$/7\1/; tn
- 6替换成了7,此时pattern space为7
- s/5\(_*\)$/6\1/; tn
- s/4\(_*\)$/5\1/; tn
- s/3\(_*\)$/4\1/; tn
- s/2\(_*\)$/3\1/; tn
- s/1\(_*\)$/2\1/; tn
- s/0\(_*\)$/1\1/; 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
- :x
- s/\(\n.\)\(.*\)\(.\n\)/\3\2\1/
- tx
- # Remove the newline markers
- s/\n//g
代码分析(以输入abcdef为例):
- 读取abcdef,
- pattern space为abcdef
- /../! b:如果只有一个字符,直接打印,中止此循环,进入下一循环。
- s/^.*$/\n&\n/:用两个换行符包围abcdef,此刻pattern space为\nabcdef\n。
- :x
- s/\(\n.\)\(.*\)\(.\n\)/\3\2\1/
- tx
- 经过s命令后,\1为\na,\2为bcde,\3为f\n,pattern space变为f\nbcde\na,此刻首尾字符换了位置。
- 执行tx命令,发现pattern space已经改变,跳转到x子命令,继续替换操作。
- 再次执行s命令后,换行符中间的字符又首尾调换了一次,pattern space为fe\ncd\nba
- 再一次s命令,pattern space为fed\n\ncba,
- 再一次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
代码分析:
以文件内容为:
www.centos.bz
www.baidu.com
www.qq.com
为例:
- 读取第一行:
- pattern space为www.centos.bz
- 1! G,如果不是第一行,执行G,附加一换行符到pattern space,再附加hold space内容到pattern space。此刻pattern space还是www.centos.bz
- $ p 如果到文件尾,则打印。
- h命令:用pattern space内容替换hold space内容,现在pattern space和hold space都为www.centos.bz。
- 读取第二行
- pattern space为bb,hold space为www.centos.bz
- 1! G: pattern space为www.baidu.com\nwww.centos.bz
- h命令:pattern space和hold space都为www.baidu.com\nwww.centos.bz
- 读取第三行
- pattern space为www.qq.com,hold space为www.baidu.com\nwww.centos.bz
- 1! G:pattern space为www.qq.com\nwww.baidu.com\nwww.centos.bz
- h命令:pattern space和hold space都为www.qq.com\nwww.baidu.com\nwww.centos.bz
- $ p:已到文件尾,执行打印paatern space操作,结果为:
- www.qq.com
- www.baidu.com
- www.centos.bz
gnu关于sed教程还有很多示例涉及到sed的高级功能,时间有限,先分析到这里。有空再继续。下篇日志贴出并分析使用sed解析nginx配置文件中的server {}代码块,实现列出虚拟主机的server_name root等信息,以及指定server_name的虚拟主机删除操作。
最后介绍一个很好用的sed debug工具,它可以显示出所有pattern space和hold space实时状态。http://aurelio.net/projects/sedsed/