本文为谁而写?

  • 想要高效率文本处理操作的程序员或非程序员
  • 对 sed 一知半解或蒙蒙懂懂,希望更进一步学习的程序员或非程序员(我自认为本文是个不错的起点)
  • 深刻的认识到作为个体的“人”的文本操作效率的重要地位

谁与本文无缘?

  • 那些从七八十年代就开始捣鼓 Unix 的一脸大胡子秃顶不刷牙不洗脸不洗澡衣服脏了反穿然后再脏了脱下来看看哪面更干净一点继续穿的 hacker 们,你们根本不需要看我这个小屁孩儿写的东西,简直浪费生命(当然如果你是其中一个,我 非常 十分 特别 desperately 的希望你能够用最恶毒的语言给我整点建议,让我也有机会在若干个十年后一脸大胡子秃顶不刷牙.......)
  • 从没用过 shell 的程序员,实惠点说,你只能在山脚下望望,sed 中有些东西相当晦涩,如果你连 shell 都没用过,本文会让你作呕,为您的健康着想,请迅速关闭网页
  • 热爱 windows ,深深的认可 Microsoft 哲学、喜欢鼠标点来点去、拖动这个窗口到那边、听经典开机铃声咚(-)咚(-)咚(\/)咚(-)、看杀毒软件忙得不可开交并且热衷于借着给妹纸修复 windows 的机会然后约会的帅锅们,请绕行。TMD(注1)
  • 认为人的“效率”是个无关痛痒的话题,不值得为“它”做点什么,嗯,请出门左拐,注意脚下
  • 认为我说的就是一派胡言,或许...你是对的,不过当你羡慕别人高效的文本操作时,请重新评估自己的位置(注2)
  • 对“中文完整叙述一句话后面不带句号结束的洁癖症患者”敬而远之的人,请.......给我捐点钱让我继续治疗吧

最后,任何有信用卡的人,还有那些没有信用卡的人,都可能成为最上面那栏或次上面那栏中的一个,请对号入座吧(注3)

acknowledgapologize(致歉)

  • 至少到目前为止,图灵社区还没有帖子分享关于 sed 的相关积累,这让我有机可乘
  • 恰巧我最近系统的学习了下 sed ,而且恰巧我看到“图灵最新书目(2014.07).pdf P41 左下角”即将出版的《Software Design 中文版 03》中将 sed 和 awk 表述为“文本处理的金刚钻”,恰巧我又是一个从小学到大学经常考试打小抄的屡教不改的惯犯,毕业后再也没有机会实践了,如今天赐良缘,所以本文标题可以说是旧病复发也。如果我的帖子让图灵即将出版的《Software Design 中文版 03》滞销的话,绝非我愿,在此提前致以我十二万分的歉意;如果我的帖子能够使更多的人关注 sed 并且恰巧有能拉动《Software Design 中文版 03》的销量的话,我就再写一篇(可能需要好几篇)关于 awk 的(awk 的东西更多,啊“A”我“W”靠“K”!这个行业里的那些死老头子们...)(注4),以达到我不可告人的利益诉求

知识体系基础

  1. 你需要对 shell 有一定程度的掌握,不只是 cd, ls 这样的简单的命令,我说的是 grep, find 这个级别的
  2. 你需要对正则表达式有着扎实的掌握,sed(和 awk)强烈依赖正则表达式,当然,对于通用编程语言中的环视功能是可选的(虽然除了 perl 其它语言对环视的支持也是多多少少半残),但是至少应该掌握 Unix 中的 BRE 和 ERE ,这样我们才有的聊。如果觉得有欠缺,强烈推荐 Jeffrey E.F. Friedl 的《Mastering Regular Expressions》和余晟的《正则指引》(注5)
  3. 最好会用一个牛逼的文本编辑器,例如 GNU/Emacs(注6) 或者 vim

sed 版本

我的 Ubuntu 上 sed 的版本是 GNU sed 4.2.1 ,并不是 POSIX sed ,我认为看这篇帖子的大部分人用的都是 GNU sed ,所以这篇帖子的内容对大部分人都适用。GNU sed 对 POSIX sed 做了一些扩展,所以有些 option 和命令对于 POSIX sed 是不可用的,所以,当你用帖子中的内容做实验时,请事先确认你的 sed 版本(sed --version),如果你不幸用的是 POSIX sed 或是其他别的什么我都没听过的各种奇葩的 GNU/Linux Distribution 上的各种奇葩的 sed 版本,请去搞一份对应的 manual 吧!如果你恰巧用的也是 GNU sed ,那么这篇帖子就是为你写的,对,就是你,只有你会看,你好啊!(注7)

强烈建议!!!

  • 了解你的输入!在使用 grep 之前,仔细检查你的输入文件
  • 从测试文件中出现的小示例开始。在示例上运行你的脚本并确信能正常工作。重要的是确保脚本在你不想让它工作的时候不能工作
  • 做之前要仔细考虑
  • 要实用

心理准备

如果你以前没有用过 sed ,那我告诉你,本文的余下部分很可能会让你丧失一些信心,这是一场不逊于奥马哈滩头的硬仗(注8)!不过你得挺住(你一定能的,连我这德性的都挺过来了),挺过来了就有机会向那些不刷牙不洗脸的死老头子们看齐....

生理准备

别等到口渴了再喝水,要使劲喝水,使劲上厕所(注9)。多吃点核桃这样不饱和脂肪酸含量高的食物,有助于提高智商(如果吃多了便秘那就再多吃点香蕉)。再有,别吃太饱,否则大脑忙不过来,单核 CPU 的 context 切换的成本很高。别着急,还没到上路的时候呢!

上路喽!

请问 sed : 你是如何看待你自己的?
sed :I'm the special one!!!(注10)

的确,sed 很另类,或者经历过那个时代的老头子们看我们也很另类。sed 是 stream editor 的缩写,与现在广泛应用的交互式的编辑器不同,它是“非交互式”的、面向字符流的编辑器。我并不确定 sed 这个单词(严谨地说是 s , e, d 这三个字母)怎么读,根据我对英语的惯用发音方式的理解和搜索引擎的帮助,/sed/是一个被广泛接受的读法,在此,我遵循大多数人(注11)

sed 用途

  • 在一个或多个文件上自动实现编辑操作
  • 简化对多个文件执行相同的编辑处理工作
  • 编写转换程序

sed 的学习障碍

  • 如何使用 sed ,换句话说就是学习使用 sed 的语法,习惯它的工作方式
  • 正则
  • 如何与 shell 交互
  • 编写 sed 程序的技巧。这个是最难的,你需要经验,经验来自于大量充分的实践,而且实践就难免会犯错,当你见过各种大场面、犯过足够多的错误之后,于是乎啥也不说了,你就具备了这个牛力(系统运维流传这样一个段子:没把系统搞死过的运维不是好运维)(注12)

(请确保理解 sed 的工作原理、pattern space 和 hold space ,否则后面会很晦涩)

工作原理

sed 是面向字符流的编辑器,而且考虑到 sed 是“那个硬件坑爹的年代”发明出来的玩意儿,你猜也能猜得到:sed 不会一口气把一个文件的全部内容都 load 到内存中的(额...GNU/Emacs 就是这么干的...实际上 GNU/Emacs 的最早可工作版本是 1985 年初,而 sed 早在 10 年前就出生了,是 Unix V7 最早引入的,肯大爷(注13)还是很有先见之明的!嗯,就这么勉强理解吧...),sed 的正常工作流程是每次从 file(s) 读取一行至 pattern space ,然后对 pattern space 中的内容依次执行每一个命令,最后输出 pattern space 中的内容(所以,sed 不会更改原文件 file(s) 的,尽管放心“食用”——当然是在你没有指定 -i option 的情况下)

pattern space

啥是 pattern space? TMD(注14),咋解释呢?你可以把 pattern space 想象成一个大洗脚盆,sed 把巧克力、酱油和辣椒粉依次(每行文本)倒进去,你负责(各种 sed 命令)加工,随着汗水、鼻涕、眼泪啥的各种液体一起搅拌(sed 命令执行),最后产生重口味沙拉(输出),你喜欢这个味道吗?对,.*(注15),就是这个味儿,美好的事物总是青睐大胆尝试的人...........

正经点,pattern space 就是一个 sed 负责维护的缓冲区,当应用编辑命令时在那里存储单个输入行,并依次执行每一个命令,当应用了所有命令后,当前行被输出并把 file(s) 的下一行读入,循环往复。pattern space 中的内容是动态的,任何命令都可以为应用下一个命令而改变 pattern space 的内容。一些命令会改变 sed 的工作流程,别着急,蛋疼的地儿在这儿呢!

(有些书把 pattern space 翻译成模式空间,我认为“模式空间”这种译法并不能帮助我很好的理解这个概念,相反,我为了理解 pattern space ,大脑还需要多一次额外的翻译,所以,在这我保留了英文的标准称谓。如果你跟我一样,喜欢直接的东西,你真幸福,很少有帖子能够像我这样照顾到我这样的读者,我只能自己满足自己了...下面的 hold space 同样保留标准称谓不翻译)

hold space

孙行者来了,行者孙也来了,现在又来了个者行孙,TMD ,你让我说点啥?(注16)你可以把 hold space 理解成与 pattern space 很相似的一个预留(set-aside)缓冲区。pattern space 的内容可以复制到 hold space ,同样,hold space 的内容也可以复制到 pattern space 。有一组命令专门用于在 pattern space 和 hold space 之间移动数据。hold space 用于临时存储,比如你有这么一个蛋疼...用过的词就不爱再用了...乳酸的需求:命令 y 用于文本转换,比如大小写转换,但是这个命令会影响整个 pattern space 中的内容,如果想要在输入行上转换某个单词,使用 hold space 就可以完成——简单点说,输出要更改单词的那一行之前的所有行,删除这些行,将单词后面的行复制到 hold space ,在 pattern space 中转换这个单词,然后将 hold space 中的内容追加到 pattern space 中

(该聊聊 fuck fruit 了)(注17)

命令基本格式

sed [options] 'program' file(s) 

或者

sed [options] -f sed_script file(s)

第一种不解释了,第二种也很容易理解,是吧?毕竟我写的这么清晰......你可以把 sed 的命令写进一个纯文本文件 sed_script ,然后使用 -f option 引用这段程序来对 file(s) 操作(注18)

sed_script 中可以用下列的语法形式:

address{  
program1  
program2  
program3  
..  
}  

毕竟对一个 address 的操作很多时候是需要多个命令的,而且这样写的好处是不必像第一种那样,每次都得用单引号把 program 括起来(要把 program 顶到一行的开头写,否则会出现错误)

也可以选择其他字符作为定界符,例如分号:

sed 's;/home/yeshuai;/home/yes;'   

/home/yeshuai 字符串替换成 /home/yes 字符串

address

sed 不需要必须指定 address ,如果没有 address ,sed 就会在 file(s) 的每一行上执行 sed_script 。address 后面紧跟 ! 感叹号匹配非 address 的行

number 只匹配特定行
first~step 匹配以 first 行起始,step 为递增量的 address 。例如:sed -n 1~2p file 只会会打印奇数行。first 可以为 0 ,sed 就会认为从第 step 行递增。GNU 扩展
$ 匹配文件的最后一行
/regexp/ 正则匹配,不解释了
\cregexpc 同上。c 可以为任意字符
0,addr2 从 file(s) 第 1 行开始,直到匹配到 addr2 的行。这相当于 1,addr2 ,但是有一点区别:0,addr2 会在地址区间的结尾,而 1,addr2 会在地址区间的起始。只有当 addr2 是一个正则表达式时才有效
addr1,+N 匹配从 addr1 的行开始,直到下面的 N 行
addr1,~N 匹配从 addr1 的行开始,直到为 N 的倍数的行

options (有些 options 有短选项,有些没有,有长选项的我会以逗号分隔显示)

-n, --quite, --slient 阻止默认输出。默认情况下,sed 会将 file 的内容每行都输出,-n 可以抑制默认输出。通常和 p 命令一起使用,只输出 pattern space 中改变了的行
-e 'program', --expression='program' 如果你有多个 program 需要执行,可以指定 -e 参数。例如:sed -e 'program1' -e 'program2' -e 'program3'... file(s)
-f sed_script, --file=sed_script 刚解释过了,就上面那几行
--follow-symlinks 处理符号链接(没用过,感觉很牛逼的样子)
-i[SUFFIX], --in-place=[SUFFIX] 对 file(s) 进行更改。如果提供了 SUFFIX ,则对原文件 file(s) 先进行备份,例如:sed -i_bak 'program' xxx 会先生成 xxx_bak 这个内容与文件 xxx 一模一样的文件,然后对 xxx 文 件进行更改
-l N, --line-length=N 对“l 命令”(l 命令稍后讲解)指定自动换行的字符数
--posix 禁用所有 GNU sed 扩展。这样,你的 sed 就成了 POSIX sed(真惨啊...)
-r, --regexp-extended 使用 ERE 。POSIX sed 只能使用 BRE ,GNU sed 的这个扩展很实用,没那么多的转义了
-s, --separate 将 file(s) 是为单个独立的文件,而不是将它们视为整个连续的字符流。其实 -s 很符合直觉,只不过 POSIX sed 并不这么认为
-u, --unbuffered 从 file(s) 加载尽可能少量的数据并且更频繁的 flush(说实话,这个也不怎么理解,没用过)
--help 不解释了
--version 不解释了

命令

s (替换命令)

[address1[,address2]]s/pattern/replacement/flag

用 replacement 替代每个寻址行的 pattern

flag(通常组合使用)
n :1 ~ 512的一个数字。表示对匹配模式的每行中第 n 次出现的情况进行替换
g :全局替换。没有 g 时通常只对第一次的出现进行替换
p :打印 pattern space 的内容
w file :将 pattern space 的内容写到文件 file 中

replacement
& :用正则表达式匹配的内容进行替换
\n :匹配第 n 个分组子串。
\ :当在替换部分包含 & 符号时,反斜杠 “\” 和替换命令的定界符可用 \ 转义它们。另外,它用于转义换行符并创建多行 replacement 字符串(除了 正则表达式 可以有元字符外,replacement 部分也可以有元字符)

replacement 元字符
\ :反斜杠一般用于转义其它元字符 例如:用换行符取代每行上的第二个制表符(注意:制表符用 * 演示):(反斜杠后面不允许有空格)
sed 's/*/\
/2'

& :同 replacement 节的 & ,用于引用分组,要想输出 & 字符的字面值,用反斜线转义
\n :引用分组(最多 9 个)

sed 还会记得最后一个正则表达式:
sed 's/foo/bar/3' 替换第三个 foo
sed 's//quux/' 再替换第一个 foo

# (注释命令)
与 ruby 的单行注释非常像,GNU sed 可以将注释放到任何地方,但是最好放在单独的一行

d (删除命令)

[address1[,address2]]d

从 pattern space 中删除行。因此行没有被传递到标准输出。一个新的输入行被读入,并用第一个 program 来编辑

D (多行删除命令)

[address1[,address2]]D

删除由命令 N 创建的多行 pattern space 中的第一部分(直到嵌入的换行符),并且用 sed_script 的第一个 program 来编辑。如果这个命令使 pattern space 为空,那么将读取一个新的输入行,与 d 命令一样

a (追加命令)

[address]a\
text

在与 address 匹配的每行后面追加 text 。如果 text 多于一行,必须用反斜杠将这些行前面的换行符隐藏起来。text 将被没有用这种方法隐藏起来的第一个换行符结束。text 在 pattern space 中不可用并且后续 program 不能应用于它。当所有 program 执行完后,追加命令的结果将被送到标准输出,而不管在 pattern space 中的当前行发生了什么

i (插入命令)

[address]i\
text

将 text 插入到每个和 address 匹配的行的前面。其它行为与 a 命令相同

c (更改命令)

[address1[,address2]]c\
text

用 text 来替代(改变)由地址选定的行。当指定的是一个行范围时,将所有这些行作为一个组并用 text 取代。每个 text 的行后面的换行符必须用反斜杠将其转义,最后一行除外。实际上,pattern space 中的内容将被删除,后续的 program 不能应用于 text

l (列表命令)

[address1[,address2]]l

列出 pattern space 中的内容,将不可打印字符表示为 ASCII 码。长的行将被折行

l width (列表命令)GNU 扩展

[address1[,address2]]l width

列出 pattern space 中的内容,将不可打印字符表示为 ASCII 码。长的行以 width 规定的字符数折行

y (转换命令)

[address1[,address2]]y/abc/xyz

按位置将字符串 abc 中的字符转换成字符串 xyz 的相应位置替换根据字符的位置来进行。因此,它没有“词”的概念,这样,在改行上的任何 a 都被替换成了 x ,而不管 a 后面是不是 b 。这个命令的一个可能的用途就是大小写字母的转换 y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/

p (打印命令)

[address1[,address2]]p

打印所寻址的行。注意,这将导致重复的输出,除非指定了 -n option 。它既不清除 pattern space 中的内容,也不改变 sed_script 中的控制流。它频繁的用在改变控制流的命令(d, N, b)之前。通常这样使用:sed -n '/pattern/replacement/p'

= (打印行号命令)

[line-address]=

这个命令不能应用与地址范围,只能作用于单行。

n (next 命令)

[address1[,address2]]n

首先输出 pattern space 的内容,然后读取下一行,继续执行 sed_script 中余下的 program ,说白了,就是 sed_script 中剩余的命令对替换后的行继续执行

r (读命令)

[line-address]r file

读取 file 的内容并追加到 pattern space 的后面。后续的命令不会对新读入的行起作用

R (读命令)GNU 扩展

[line-address]R file

每次从 file 读取一行内容并追加到 pattern space 的后面。后续的命令不会对新读入的行起作用

w (写命令)

[address1[,address2]]w file

将 pattern space 中内容追加到 file 。如果 file 不存在,将直接创建 file 。如果 file 存在,则每次 sed_script 执行时都会改写 file 的内容。在一个 sed_script 内,多次 w file 会追加内容至 file

W (写命令)GNU 扩展

[address1[,address2]]W file

只将 pattern space 中的第一行内容追加到 file 。如果 file 不存在,将直接创建 file 。如果 file 存在,则每次 sed_script 执行时都会改写 file 的内容。在一个 sed_script 内,多次 w file 会追加内容至 file

q (退出命令)

[line-address]q

当遇到 line-address 时退出,sed_script 终止执行。寻址的行首先被写到输出(如果没有 -n option),包括前面的 a 命令和 r 命令为它追加的文本

Q (退出命令)GNU 扩展

[line-address]Q

当遇到 line-address 时退出,sed_script 终止执行。

N (多行 next 命令)

[address1[,address2]]N

将下一个输入行的内容追加到 pattern space 之后,追加的行与 pattern space 中的行用换行符分割。这个命令用于实现跨两行的模式匹配。这个换行符可以用 \n 来匹配。

D (多行删除)

[address1[,address2]]D

删除由 N 命令创建的多行 pattern space 中的第一部分(直到换行符为止,换行符也一起删除)。并且用 sed_script 继续编辑。如果这个命令使 pattern space 为空,则继续读取下一个输入行,和执行了 d 命令一样

P (多行打印)

[address1[,address2]]P

打印有 N 命令创建的 pattern space 的第一部分(直到第一行为止)。如果没有将 N 用于某一行则与 p 相同

hold space 的内容到了....

h 将当前 pattern space 中的内容复制到 hold space ,hold space 原来的内容被清除
H 将一个换行符 + pattern space 的内容追加到 hold space ,即使 hold space 为空,H 命令也会添加换行符

g 将 hold space 中的内容复制到 pattern space ,pattern space 中原来的内容被清除
G 将行符 + hold space 中的内容追加到 pattern space ,如果 hold space 的内容为空,则则相当于只追加了一个换行符

x

[address1[,address2]]x

交换 pattern space 和 hold space 的内容

来吧,看看乳酸的那个需求吧!

$ cat > U
find the Match statement
Consult the Get statement
using the Read statement to retrieve the data

$ cat > U.sed
/the .*statement/{
h
s/.*the \(.*\)statement.*/\1/
y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
G
s/\(.*\)\n\(.*the \).*\(statement.*\)/\2\1\3/
}

$ sed -f U.sed U
find the MATCH statement
Consult the GET statement
using the READ statement to retrieve the data

不解释了,慢慢读吧

:lable (标签命令)
定义一个分支使用 :mylable

b (分支命令)

[address]b[mylable]

如果没有指定 mylable ,则跳转到 sed_script 结尾。否则无条件的跳转到 mylable 处

(肯定执行 cmd1 和 cmd2)
:top
cmd1
cmd2
/pattern/b top
cmd3

很像一个循环吧?还有下面:
(肯定执行 cmd1 和 cmd3)
cmd1
/pattern/b end
cmd2
:end
cmd3

(cmd2 或 cmd3 执行一个)
cmd1
/pattern/b dottree
cmd2
b
:dottre
cmd3

t (测试命令)

[address]t[mylable]

(其实 t 很像 shell 的 case 语句)通常和 s 命令一起使用,当 s 命令成功执行替换后,t 命令才会起作用

/pattern/{
cmd1
t mylable
cmd ...
cmd ...
cmd ...
:mylable
cmd ...
cmd ...
}

T (测试命令)GNU 扩展

[address]T[mylable]

与 t 命令正好相反,如果 s 命令没有执行替换,T 命令才会起作用

/pattern/{
cmd1
T mylable
cmd ...
cmd ...
cmd ...
:mylable
cmd ...
cmd ...
}  

未完待续...

  • 没有解释 BRE 和 ERE 的知识,本文假设读者已经具备相关知识
  • 没有相关的代码片段。说实话,太长了,而且如果为了讲解知识点硬憋几段文本出来,恐怕读者认为 sed 的能力不过如此(注19)
  • 有些需要 sed 费尽九牛二虎之力才能解决的问题只需要 awk 一行就搞定,其实我不想写 awk 的东西了,拦不住嘴贱...no zuo no die...改天我一定蛋定的写一篇
  • 本文一定有错误,针对错误,我也不想掩饰,该骂就骂,你不骂,我下回写的还一定有错误;你骂了,估计也还是有错误,但是你骂完舒服了,我也没啥事,这才是我所关注的

  1. 是指“挺美的” —— 《嗨翻 C 语言》
  2. 来自于给我影响最大的一本书 —— Andy Hunt 和 Dave Thomas 的《The Pragmatic Programmer》中 chapter3.16小节 —— 强力编辑
  3. 来自 O'Reilly《Head First》系列图书
  4. POSIX awk 的作者 Alfred Aho, Peter Weinberger 和 Brian Kernighan
  5. 《正则指引》的作者余晟正是《Mastering Regular Expressions》的译者
  6. 事实上本文就是在 GNU/Emacs 中完成的,然后 copy 到图灵社区 markdown 排版的
  7. 句式来自于我非常喜欢的一名程序员 Steve Yagge 的一篇博客《巴别塔》,如果你愿意的话,可以花点钱把他的书《程序员的呐喊》(说白了就是他的博客精选集)买来看看,不愿意的话,我在这里给一个这篇博客的链接(不保证永久有效):http://www.oschina.net/news/28301/tour-de-babel
  8. 诺曼底登陆最惨烈的一战
  9. 同样来自 O'Reilly《Head First》系列
  10. 04/05 赛季穆里尼奥登陆斯坦福桥时首次新闻发布会的自我评价。随后,他带领切尔西获得联赛三连冠。不过后来 07/08 赛季伊始被老板阿布炒鱿鱼,弗格森爵士率领曼联重夺联赛冠军,并于那个赛季的欧洲冠军杯决赛点球战胜切尔西夺得冠军,C罗获得职业生涯的第一个金球奖和第一座世界足球先生奖杯
  11. 如果你因为错误的发音导致被某些人嘲笑甚至重大的利益损失,我在这里郑重声明我不负任何责任
  12. 如果你是一名运维,确保这句话别让你的老板看到
  13. Unix 作者 Ken Thompsen
  14. 是指“甜蜜的” —— 《嗨翻 C 语言》
  15. 此处 .* 为正则表达式:匹配除换行符外的 0 个或多个任意字符。为了避免广告嫌疑,请读者自行想象
  16. 是指“塔玛德” —— 自创人名(灵感来自于 劳伦斯·阿尔玛-塔德玛 爵士 —— 英国维多利亚时代的知名画家,他的作品以豪华描绘古代世界(中世纪前)而闻名)。如果你觉得名字有点儿怪怪的(其实你不应该感觉到怪,你应该知道本届世界杯巴西的头号球星 Neymar 吧?),可以把这句话理解成“元芳,你怎么看?”
  17. 有些商家居然把干货翻译成 fuck fruit!!!真是莫名其妙!这实在是太业余的行为了
  18. 我个人喜欢把 sed_script 的文件名起成 xxx.sed 的格式,让人一眼就能看明白,至于你,随便,我管不着
  19. 其实就是懒