转自 ibm.com/developerworks
-----------------------------------------------
通用线程 — sed 实例,第 1 部分
挑选编辑器
在 UNIX
世界中有很多文本编辑器可供我们选择。思考一下 — vi、emacs 和 jed
以及很多其它工具都会浮现在脑海中。我们都有自己已逐渐了解并且喜爱的编辑器(以及我们喜爱的组合键)。有了可信赖的编辑器,我们可以轻松处理任何数量与
UNIX 有关的管理或编程任务。
如果可以使编辑文件的过程自动化,以便用“批处理”方式编辑文件,甚至编写可以对现有文件进行复杂更改的脚本,那将太好了。幸运的是,对于这种情况,有一种更好的方法 — 这种更好的方法称为 "sed"。
sed 是一种几乎包括在所有 UNIX 平台(包括 Linux)的轻量级流编辑器。sed 有许多很好的特性。首先,它相当小巧,通常要比您所喜爱的脚本语言小很多倍。其次,因为 sed 是一种
流编辑器,所以,它可以对从如管道这样的标准输入接收的数据进行编辑。因此,无需将要编辑的数据存储在磁盘上的文件中。因为可以轻易将数据管道输出到 sed,所以,将 sed 用作强大的 shell 脚本中长而复杂的管道很容易。试一下用您所喜爱的编辑器去那样做。
GNU sed
对
Linux 用户来说幸运的是,最好的 sed 版本之一恰好是 GNU sed,其当前版本是 3.02。每一个 Linux
发行版都有(或至少应该有)GNU sed。GNU sed 之所以流行不仅因为可以自由分发其源代码,还因为它恰巧有许多对 POSIX sed
标准便利、省时的扩展。另外,GNU 没有 sed 早期专门版本的很多限制,如行长度限制 — GNU 可以轻松处理任意长度的行。
最新的 GNU sed
在研究这篇文章之时我注意到:几个在线 sed 爱好者提到 GNU sed 3.02a。奇怪的是,在
ftp.gnu.org(有关这些链接,请参阅
参考资料)上找不到 sed 3.02a,所以,我只得在别处寻找。我在
alpha.gnu.org的 /pub/sed 中找到了它。于是我高兴地将其下载、编译然后安装,而几分钟后我发现最新的 sed 版本却是 3.02.80 — 可在
alpha.gnu.org上 3.02a 源代码旁边找到其源代码。安装完 GNU sed 3.02.80 之后,我就完全准备好了。
正确的 sed
在
本系列中,将使用 GNU sed 3.02.80。在即将出现的本系列后续文章中,某些(但非常少)最高级的示例将不能在 GNU sed 3.02
或 3.02a 中使用。如果您使用的不是 GNU sed,那么结果可能会不同。现在为什么不花些时间安装 GNU sed 3.02.80
呢?那样,不仅可以为本系列的余下部分作好准备,而且还可以使用可能是目前最好的 sed。
sed 通过对输入数据执行任意数量用户指定的编辑操作(“命令”)来工作。sed 是基于行的,因此按顺序对每一行执行命令。然后,sed 将其结果写入标准输出 (stdout),它不修改任何输入文件。
|
如果输入该命令,将得不到任何输出。那
么,发生了什么?在该例中,用一个编辑命令 ‘d’ 调用 sed。sed 打开 /etc/services
文件,将一行读入其模式缓冲区,执行编辑命令(“删除行”),然后打印模式缓冲区(缓冲区已为空)。然后,它对后面的每一行重复这些步骤。这不会产生输
出,因为 "d" 命令除去了模式缓冲区中的每一行!
在该例中,还有几件事要注意。首先,根本没有修改
/etc/services。这还是因为 sed 只读取在命令行指定的文件,将其用作输入 — 它不试图修改该文件。第二件要注意的事是 sed
是面向行的。’d’ 命令不是简单地告诉 sed 一下子删除所有输入数据。相反,sed 逐行将 /etc/services
的每一行读入其称为模式缓冲区的内部缓冲区。一旦将一行读入模式缓冲区,它就执行 ‘d’
命令,然后打印模式缓冲区的内容(在本例中没有内容)。我将在后面为您演示如何使用地址范围来控制将命令应用到哪些行 –
但是,如果不使用地址,命令将应用到 所有行。
另一个 sed 示例
下面是使用 sed 从输出流除去 /etc/services 文件第一行的示例:
|
如您所见,除了前面有 ‘1′
之外,该命令与第一个 ‘d’ 命令十分类似。如果您猜到 ‘1′ 指的是第一行,那您就猜对了。与第一个示例中只使用 ‘d’
不同的是,这一次使用的 ‘d’ 前面有一个可选的数字地址。通过使用地址,可以告诉 sed 只对某一或某些特定行进行编辑。
地址范围
现在,让我们看一下如何指定地址
范围。在本例中,sed 将删除输出的第 1 到 10 行:
|
带规则表达式的地址
现在演示一个更有用的示例。假设要查看 /etc/services 文件的内容,但是对查看其中包括的注释部分不感兴趣。如您所知,可以通过以
‘#’ 字符开头的行在 /etc/services 文件中放置注释。为了避免注释,我们希望 sed 删除以 ‘#’ 开始的行。以下是具体做法:
|
要理解 ‘/^#/d’ 命令,首先需要对其剖析。首先,让我们除去 ‘d’ — 这是我们前面所使用的同一个删除行命令。新增加的是 ‘/^#/’ 部分,它是一种新的
规则表达式地址。规则表达式地址总是由斜杠括起。它们指定一种
模式,紧跟在规则表达式地址之后的命令将仅适用于正好与该特定模式匹配的行。
可以使用规则表达式来表示可能会在文本中发现的模式。您在 shell 命令行中用过 ‘*’ 字符吗?这种用法与规则表达式类似,但并不相同。下面是可以在规则表达式中使用的特殊字符:
|
字符 |
描述 |
| ^ |
与行首匹配 |
| $ |
与行末尾匹配 |
| . |
与任一个字符匹配 |
| * |
将与 前一个字符的零或多个出现匹配 |
| [ ] | 与 [ ] 之内的所有字符匹配 |
|
规则 表达式 |
描述 |
| /./ | 将与包含至少一个字符的任何行匹配 |
| /../ | 将与包含至少两个字符的任何行匹配 |
| /^#/ | 将与以 ‘#’ 开始的任何行匹配 |
| /^$/ | 将与所有空行匹配 |
| /}^/ | 将与以 ‘}’(无空格)结束的任何行匹配 |
| /} *^/ | 将与以 ‘}’ 后面跟有 零或多个空格结束的任何行匹配 |
| /[abc]/ | 将与包含小写 ‘a’、’b’ 或 ‘c’ 的任何行匹配 |
| /^[abc]/ | 将与以 ‘a’、’b’ 或 ‘c’ 开始的任何行匹配 |
|
这将导致 sed 删除任何匹配的行。然而,通过告诉 sed
打印regexp 匹配并删除不匹配的内容,而不是与之相反的方法,会更有利于熟悉规则表达式。可以用以下命令这样做:
|
有关地址的更多内容
目前为止,我们已经看到了行地址、行范围地址和 regexp 地址。但是,还有更多的可能。我们可以指定两个用逗号分开的规则表达式,sed
将与所有从匹配第一个规则表达式的第一行开始,到匹配第二个规则表达式的行结束(包括该行)的所有行匹配。例如,以下命令将打印从包含 "BEGIN"
的行开始,并且以包含 "END" 的行结束的文本块:
|
C 源代码示例
如果只要打印 C 源文件中的 main() 函数,可输入:
|
该命令有两个规则表达式
‘/main[[:space:]]*(/’ 和 ‘/^}/’,以及一个命令
‘p’。第一个规则表达式将与后面依次跟有任意数量的空格或制表键以及开始圆括号的字符串 "main" 匹配。这应该与一般 ANSI C
main() 声明的开始匹配。
在这个特别的规则表达式中,出现了 ‘[[:space:]]’
字符类。这只是一个特殊的关键字,它告诉 sed 与 TAB 或空格匹配。如果愿意的话,可以不输入 ‘[[:space:]]’,而输入
‘[',然后是空格字母,然后是 -V,然后再输入制表键字母和 ']‘ — Control-V 告诉 bash
要插入“真正”的制表键,而不是执行命令扩展。使用 ‘[[:space:]]’ 命令类(特别是在脚本中)会更清楚。
"Everyday I discover more and more beautiful things, it's enough to drive one mad.
I have such desire to do everything and my heart is bursting with it."
--------Cloude Monet