Sed:流编辑器的学习与使用

Reference and Resources:

https://www.grymoire.com/Unix/Sed.html#uh-0

Sed的全名叫Ultimate Stream Editer,终极流编辑器。

Sed最重要的指令:s for substitution

sed最令人熟知的指令就是s了,这个替换指令可以将所有正则表达式出现到的匹配字符串变为一个新值。以下是一个简单的引例:

1
echo day | sed 's/day/night/'

上面这个指令就会将正则表达式检索到的day替换为night了。

另一个重要的概念在于,sed是行向的。也就是说,如果你的输入文件有如下内容:

1
2
3
one two three, one two three
four three two one
one hundred

接着输入该命令在bash中:

1
sed 's/one/ONE/' < file

最后输出的结果如下所示:

1
2
3
ONE two three, one two three
four three two ONE
ONE hundred

注意,上面做的替换内,每一行只被进行了一次替换。第一行有两个one,但是只有第一个出现的one被替换成了大写的ONE。这也是sed默认的行为。如果我们需要一些不同,那么就需要加上一些选项来进行改动。

一般来说,sed的替换命令有如下的结构:

1
2
3
s :赛表我们的替换命令substitution command
/../../ :对应限定符
sed -e 's/REGEX-PATTERN/SUBSTITUTION-CONTENT/'

一把来说,除了反斜杠,还有许多其他的符号可以作为限定符。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
The character after the s is the delimiter. It is conventionally a slash, because this is what ed, more, and vi use. It can be anything you want, however. If you want to change a pathname that contains a slash - say /usr/local/bin to /common/bin - you could use the backslash to quote the slash:

sed 's/\/usr\/local\/bin/\/common\/bin/' <old >new
Gulp. Some call this a 'Picket Fence' and it's ugly. It is easier to read if you use an underline instead of a slash as a delimiter:

sed 's_/usr/local/bin_/common/bin_' <old >new
Some people use colons:

sed 's:/usr/local/bin:/common/bin:' <old >new
Others use the "|" character.

sed 's|/usr/local/bin|/common/bin|' <old >new
Pick one you like. As long as it's not in the string you are looking for, anything goes. And remember that

注意,在我们设计正则表达式的时候,有的时候会用到+号。此时,因为+代表一个或更多次重复,是在扩展正则表达式extended regular expressions里面的。所以,sed默认会将其当作一个默认的字符,我们需要在命令行选项中显式添加参数来实现该部分的内容。后面的两个&分别代表regex匹配到的部分的内容引用,类似\1, \2

1
2
-E/-r 选项,将使用扩展正则表达式
echo "123 abc" | sed -r 's/[0-9]+/& &/'

使用\1来捕捉模式获取的内容

在正则表达式中,可以用捕获组()来对文本中的内容进行捕获。在sed编辑中,通常使用“The escaped parenthese”,也即

1
\( ... \)

的形式,在正则表达式中对应捕获组,它可以用来记忆符合正则表达式匹配的子串的内容。此时,使用\1就代表了第一个匹配的模式,使用\2就代表了第二个记忆的匹配的模式。下面通过一些例子来讲解使用捕获模式来进行替换的一些技巧:

  1. 如果我们想要在捕获替换的同时,保留文本某行的第一个单词,并且删掉除了这个单词以外该行的剩余内容:
1
sed -r 's/\([a-zA-Z]*\).*/\1/'

正则表达式是贪婪的,上面的捕获组中,[a-z]*或匹配0个或更多的字母,并且尝试去尽可能多的匹配字符,直到遇到了一个delimier。这是,后面的. *就包含了该行剩余的所有内容。接着,将这一行替换为第一个单词。

  1. 下面的例子替换了两个单词的顺序:
1
sed -r 's/\([a-zA-Z]*\)  \([a-zA-Z]*\)/\2 \1/'

上例在只有一个单词或空行的时候,不会有任何改变。

模式的标志:

我们可以在最后一个分隔符后面加上一系列额外的模式符号,如p、g

/g: global replacement 全局替换。在UNIX应用中使用的最多,一次读取一行。sed默认的工作流只会对每一行第一个匹配的内容进行处理,但是当我们要替换全局的内容是,就需要使用/g参数了。以下是使用/g参数的一个例子。下面的命令将所有单词的外面都加上了一组括号。注意,单词如won’t也是存在的,不能用[a-zA-Z]+这种来代表所有的单词。

1
sed -r 's/[^ ]+/(&)/g'

以上内容是sed用处最大的部分,一般占了通用的80%,更多内容不再赘述,可以自行查看手册。