在本文章系列中
Daniel Robbins 将为您演示如何使用功能十分强大(但常被遗忘)
UNIX 流编辑器 sed
sed 是用批处理方式编辑文件或以十分有效
方式创建 shell 脚本以修改现有文件
理想工具
挑选编辑器
8HYfbaiducukNpD在 UNIX 世界中有很多文本编辑器可供我们选择
思考
下 -- vi、emacs 和 jed 以及很多其它工具都会浮现在脑海中
我们都有自己已逐渐了解并且喜爱
编辑器(以及我们喜爱
组合键)
有了可信赖
编辑器
我们可以轻松处理任何数量与 UNIX 有关
管理或编程任务![](/icons/29261dou2.gif)
虽然交互式编辑器很棒
但却有其限制
尽管其交互式特性可以成为强项
但也有其不足之处
考虑
下需要对
组文件执行类似更改
情形
您可能会本能地运行自己所喜爱
编辑器
然后手工执行
组烦琐、重复和耗时
编辑任务
然而
有
种更好
方法![](/icons/29261dou2.gif)
进入 sed
8HYfbaiducukNpD如果可以使编辑文件
过程自动化
以便用“批处理”方式编辑文件
甚至编写可以对现有文件进行复杂更改
脚本
那将太好了
幸运
是
对于这种情况
有
种更好
方法 -- 这种更好
方法称为 "sed"![](/icons/29261dou2.gif)
sed 是
种几乎包括在所有 UNIX 平台(包括 Linux)
轻量级流编辑器
sed 有许多很好
特性
首先
它相当小巧
通常要比您所喜爱
脚本语言小很多倍
其次![](/icons/29261dou.gif)
sed 是
种流编辑器
所以
它可以对从如管道这样
标准输入接收
数据进行编辑
因此
无需将要编辑
数据存储在磁盘上
文件中![](/icons/29261dou2.gif)
可以轻易将数据管道输出到 sed
所以
将 sed 用作强大
shell 脚本中长而复杂
管道很容易
试
下用您所喜爱
编辑器去那样做![](/icons/29261dou2.gif)
GNU sed
8HYfbaiducukNpD对 Linux 用户来说幸运
是
最好
sed 版本之
恰好是 GNU sed
其当前版本是 3.02
每
个 Linux 发行版都有(或至少应该有)GNU sed
GNU sed 之所以流行不仅
可以自由分发其源代码
还
它恰巧有许多对 POSIX sed 标准便利、省时
扩展
另外
GNU 没有 sed 早期专门版本
很多限制
如行长度限制 -- GNU 可以轻松处理任意长度
行![](/icons/29261dou2.gif)
最新
GNU sed
8HYfbaiducukNpD在研究这篇文章之时我注意到:几个在线 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 之后
我就完全准备好了![](/icons/29261dou2.gif)
正确
sed
8HYfbaiducukNpD在本系列中
将使用 GNU sed 3.02.80
在即将出现
本系列后续文章中
某些(但非常少)最高级
示例将不能在 GNU sed 3.02 或 3.02a 中使用
如果您使用
不是 GNU sed
那么结果可能会不同
现在为什么不花些时间安装 GNU sed 3.02.80 呢?那样
不仅可以为本系列
余下部分作好准备
而且还可以使用可能是目前最好
sed![](/icons/29261dou2.gif)
sed 示例
8HYfbaiducukNpDsed 通过对输入数据执行任意数量用户指定
编辑操作(“命令”)来工作
sed 是基于行![](/icons/29261de.gif)
因此按顺序对每
行执行命令
然后
sed 将其结果写入标准输出 (stdout)
它不修改任何输入文件![](/icons/29261dou2.gif)
让我们看
些示例
头几个会有些奇怪![](/icons/29261dou.gif)
我要用它们演示 sed 如何工作
而不是执行任何有用
任务
然而
如果您是 sed 新手
那么理解它们是十分重要![](/icons/29261de.gif)
下面是第
个示例:
$ sed -e 'd' /etc/services
如果输入该命令
将得不到任何输出
那么
发生了什么?在该例中
用
个编辑命令 'd'
sed
sed 打开 /etc/services 文件
将
行读入其模式缓冲区
执行编辑命令(“删除行”)
然后打印模式缓冲区(缓冲区已为空)
然后
它对后面
每
行重复这些步骤
这不会产生输出![](/icons/29261dou.gif)
"d" 命令除去了模式缓冲区中
每
行!
在该例中
还有几件事要注意
首先
根本没有修改 /etc/services
这还是
sed 只读取在命令行指定
文件
将其用作输入 -- 它不试图修改该文件
第二件要注意
事是 sed 是面向行![](/icons/29261de.gif)
'd' 命令不是简单地告诉 sed
下子删除所有输入数据
相反
sed 逐行将 /etc/services
每
行读入其称为模式缓冲区
内部缓冲区![](/icons/29261dou2.gif)
旦将
行读入模式缓冲区
它就执行 'd' 命令
然后打印模式缓冲区
内容(在本例中没有内容)
我将在后面为您演示如何使用地址范围来控制将命令应用到哪些行 -- 但是
如果不使用地址
命令将应用到所有行![](/icons/29261dou2.gif)
第三件要注意
事是括起 'd' 命令
单引号
用法
养成使用单引号来括起 sed 命令
习惯是个好注意
这样可以禁用 shell 扩展![](/icons/29261dou2.gif)
另
个 sed 示例
8HYfbaiducukNpD下面是使用 sed 从输出流除去 /etc/services 文件第
行
示例:
8HYfbaiducukNpD$ sed -e '1d' /etc/services | more
8HYfbaiducukNpD如您所见
除了前面有 '1' 之外
该命令与第
个 'd' 命令十分类似
如果您猜到 '1' 指
是第
行
那您就猜对了
与第
个示例中只使用 'd' 不同
是
这
次使用
'd' 前面有
个可选
数字地址
通过使用地址
可以告诉 sed 只对某
或某些特定行进行编辑![](/icons/29261dou2.gif)
地址范围
8HYfbaiducukNpD现在
让我们看
下如何指定地址范围
在本例中
sed 将删除输出
第 1 到 10 行:
$ sed -e '1,10d' /etc/services | more
8HYfbaiducukNpD当用逗号将两个地址分开时
sed 将把后面
命令应用到从第
个地址开始、到第二个地址结束
范围
在本例中
将 'd' 命令应用到第 1 到 10 行(包括这两行)
所有其它行都被忽略![](/icons/29261dou2.gif)
带规则表达式
地址
8HYfbaiducukNpD现在演示
个更有用
示例
假设要查看 /etc/services 文件
内容
但是对查看其中包括
注释部分不感兴趣
如您所知
可以通过以 '#'
开头
行在 /etc/services 文件中放置注释
为了避免注释
我们希望 sed 删除以 '#' 开始
行
以下是具体做法:
8HYfbaiducukNpD$ sed -e '/^#/d' /etc/services | more
8HYfbaiducukNpD试
下该例
看看发生了什么
您将注意到
sed 成功完成了预期任务
现在
让我们分析发生
情况![](/icons/29261dou2.gif)
要理解 '/^#/d' 命令
首先需要对其剖析
首先
让我们除去 'd' -- 这是我们前面所使用
同
个删除行命令
新增加
是 '/^#/' 部分
它是
种新
规则表达式地址
规则表达式地址总是由斜杠括起
它们指定
种 模式
紧跟在规则表达式地址之后
命令将仅适用于正好与该特定模式匹配
行![](/icons/29261dou2.gif)
因此
'/^#/' 是
个规则表达式
但是
它做些什么呢?很明显
现在该复习规则表达式了![](/icons/29261dou2.gif)
规则表达式复习
8HYfbaiducukNpD可以使用规则表达式来表示可能会在文本中发现
模式
您在 shell 命令行中用过 '*'
吗?这种用法与规则表达式类似
但并不相同
下面是可以在规则表达式中使用
特殊
:
描述
8HYfbaiducukNpD与行首匹配
8HYfbaiducukNpD与行末尾匹配
8HYfbaiducukNpD与任
个
匹配
8HYfbaiducukNpD将与前
个![](/icons/29261zifu.gif)
零或多个出现匹配
8HYfbaiducukNpD[ ] 与 [ ] 之内
所有
匹配
感受规则表达式
最好方法可能是看几个示例
所有这些示例都将被 sed 作为合法地址接受
这些地址出现在命令
左边
下面是几个示例:
规则
8HYfbaiducukNpD表达式 描述
8HYfbaiducukNpD/./ 将与包含至少
个![](/icons/29261zifu.gif)
任何行匹配
8HYfbaiducukNpD/../ 将与包含至少两个![](/icons/29261zifu.gif)
任何行匹配
8HYfbaiducukNpD/^#/ 将与以 '#' 开始
任何行匹配
8HYfbaiducukNpD/^$/ 将与所有空行匹配
8HYfbaiducukNpD/}^/ 将与以 '}'(无空格)结束
任何行匹配
8HYfbaiducukNpD/} *^/ 将与以 '}' 后面跟有零或多个空格结束
任何行匹配
8HYfbaiducukNpD/[abc]/ 将与包含小写 'a'、'b' 或 'c'
任何行匹配
8HYfbaiducukNpD/^[abc]/ 将与以 'a'、'b' 或 'c'开始
任何行匹配
在这些示例中
鼓励您尝试几个
花
些时间熟悉规则表达式
然后尝试几个自己创建
规则表达式
可以如下使用 regexp:
8HYfbaiducukNpD$ sed -e '/regexp/d' /path/to/my/test/file | more
8HYfbaiducukNpD这将导致 sed 删除任何匹配
行
然而
通过告诉 sed打印 regexp 匹配并删除不匹配
内容
而不是与之相反
方法
会更有利于熟悉规则表达式
可以用以下命令这样做:
8HYfbaiducukNpD$ sed -n -e '/regexp/p' /path/to/my/test/file | more
8HYfbaiducukNpD请注意新
'-n' 选项
该选项告诉 sed 除非明确要求打印模式空间
否则不这样做
您还会注意到
我们用 'p' 命令替换了 'd' 命令
如您所猜想
那样
这明确要求 sed 打印模式空间
就这样
将只打印匹配部分![](/icons/29261dou2.gif)
有关地址
更多内容
8HYfbaiducukNpD目前为止
我们已经看到了行地址、行范围地址和 regexp 地址
但是
还有更多
可能
我们可以指定两个用逗号分开
规则表达式
sed 将与所有从匹配第
个规则表达式
第
行开始
到匹配第二个规则表达式
行结束(包括该行)
所有行匹配
例如
以下命令将打印从包含 "BEGIN"
行开始
并且以包含 "END"
行结束
文本块:
8HYfbaiducukNpD$ sed -n -e '/BEGIN/,/END/p' /my/test/file | more
8HYfbaiducukNpD如果没发现 "BEGIN"
那么将不打印数据
如果发现了 "BEGIN"
但是在这之后
所有行中都没发现 "END"
那么将打印所有后续行
发生这种情况是
sed 面向流
特性 -- 它不知道是否会出现 "END"![](/icons/29261dou2.gif)
C 源代码示例
8HYfbaiducukNpD如果只要打印 C 源文件中
![](/icons/29261main.gif)
![](/icons/29261hanshu.gif)
可输入:
8HYfbaiducukNpD$ sed -n -e '/
[[:space:]]*(/,/^}/p' sourcefile.c | more
8HYfbaiducukNpD该命令有两个规则表达式 '/
[[:space:]]*(/' 和 '/^}/'
以及
个命令 'p'
第
个规则表达式将与后面依次跟有任意数量
空格或制表键以及开始圆括号![](/icons/29261de.gif)
串 "
" 匹配
这应该与
般 ANSI C ![](/icons/29261main.gif)
声明
开始匹配![](/icons/29261dou2.gif)
在这个特别
规则表达式中
出现了 '[[:space:]]'
类
这只是
个特殊
关键字
它告诉 sed 与 TAB 或空格匹配
如果愿意
话
可以不输入 '[[:space:]]'
而输入 '['
然后是空格字母
然后是 -V
然后再输入制表键字母和 ']' -- Control-V 告诉 bash 要插入“真正”
制表键
而不是执行命令扩展
使用 '[[:space:]]' 命令类(特别是在脚本中)会更清楚![](/icons/29261dou2.gif)
好
现在看
下第二个 regexp
'/^}' 将与任何出现在新行行首
'}'
匹配
如果代码
格式很好
那么这将与 ![](/icons/29261main.gif)
![](/icons/29261hanshu.gif)
结束花括号匹配
如果格式不好
则不会正确匹配 -- 这是执行模式匹配任务![](/icons/29261de.gif)
件棘手之事![](/icons/29261dou2.gif)
是处于 '-n' 安静方式
所以 'p' 命令还是完成其惯有任务
即明确告诉 sed 打印该行
试着对 C 源文件运行该命令 -- 它应该输出整个 ![](/icons/29261main.gif)
{ } 块
包括开始
"![](/icons/29261main.gif)
" 和结束
'}'![](/icons/29261dou2.gif)