shell脚本编程笔记(九)—— 初识流编辑器 sed

一、 流编辑器

sed编辑器被称作流编辑器(stream editor)。在交互式文本编辑器中(比如vim),你可以用键盘命令来交互式地插入、删除或替换数据中的文本。流编辑器则基于预先提供的一组命令来编辑数据流,这些命令要么从命令行中输入,要么存储在一个命令文本文件中。

sed编辑器每次会执行下列操作:

  • 一次从输入中读取一行数据(每次以一行为单位,这个很重要)
  • 根据所提供的编辑器命令匹配数据
  • 按照命令修改流中的数据(默认不会修改源数据)
  • 将新的数据输出到STDOUT
  • 将所有命令与一行数据匹配完毕后,它会读取下一行数据并重复这个过程。处理完所有数据行后,它就会终止。

由于命令是按顺序逐行执行的, sed编辑器只需对数据流进行一遍处理就可以完成编辑。这使得sed编辑器要比交互式编辑器快得多,可以快速完成对数据的自动修改。在处理每行数据的同时,结果也显示出来了,可以sed编辑器处理完整个文件之前就开始观察结果。

sed命令的格式如下:

sed options script file

shell脚本编程笔记(九)—— 初识流编辑器 sed_第1张图片

script参数指定了应用于流数据上的命令。如果需要用多个命令,要么使用-e选项在命令行中指定,要么使用-f选项在单独的文件中指定。

二、 sed常见用法

1. 在命令行定义sed命令

默认情况下, sed编辑器会将指定的命令应用到STDIN输入流上。这样你可以直接将数据通过管道输入sed编辑器处理。

echo "This is a test" | sed 's/test/big test/'  # big test替换test
This is a big test

s命令默认会用斜线间指定的第二个字符串来替换每行中的第一个匹配字符串

cat data1.txt
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.

sed 's/dog/cat/' data1.txt
#输出
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy cat.
The quick brown fox jumps over the lazy cat.

#sed编辑器并不会修改源数据,只会将修改后的数据发送到STDOUT。
#如果你查看原来的文本文件,它仍然保留着原始数据。
cat data1.txt
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.

2. 在命令行使用多个sed命令

要在sed命令行上执行多个命令时,只要用-e选项就可以。

sed -e 's/brown/green/; s/dog/cat/' data1.txt

The quick green fox jumps over the lazy cat.
The quick green fox jumps over the lazy cat.
The quick green fox jumps over the lazy cat.
The quick green fox jumps over the lazy cat.

两个命令都作用到文件中的每行数据上。命令之间必须用分号隔开,并且在命令末尾和分号之间不能有空格。

如果不想用分号,也可以用bash shell中的次提示符来分隔命令。

sed -e '
> s/brown/green/
> s/fox/elephant/
> s/dog/cat/' data1.txt

The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.

3. 从文件中读取sed命令

如果有大量要处理的sed命令,将它们放进一个单独的文件中通常会更方便一些。可以在sed命令中用-f选项来指定命令文件。

cat script1.sed
#文件内容
s/brown/green/
s/fox/elephant/
s/dog/cat/

sed -f script1.sed data1.txt
#输出
The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.
The quick green elephant jumps over the lazy cat.

三、 更多的替换选项

你已经懂得了如何用s命令(substitute)来在行中替换文本。这个命令还有另外一些选项能让事情变得更为简单。

1. 替换标记

前面提到过,替换命令默认情况下只替换每行中匹配的第一处文本,来看这个例子:

cat data4.txt
This is a test of the test script.
This is the second
test of the test script.

sed 's/test/trial/' data4.txt
This is a trial of the test script.
This is the second
trial of the test script.

要让替换命令能够替换一行中不同地方出现的文本,必须使用替换标记(substitution flag)。替换标记在替换命令字符串之后设置。

s/pattern/replacement/flags

有4种可用的替换标记:

  • 数字:新文本将替换每行第几处匹配的文本
  • g:新文本将会替换每行所有匹配的文本
  • p:打印匹配行内容
  • w file:输出替换后所有行结果,并仅将匹配行内容写入到指定文件

来分别看对应的例子:

  • 将替换标记指定为2,sed编辑器只替换每行中第2次出现的匹配文本。

sed 's/test/trial/2' data4.txt
This is a test of the trial script.
This is the second
test of the trial script.

  • g替换标记 替换文本中匹配的每处文本。

sed 's/test/trial/g' data4.txt
This is a trial of the trial script.
This is the second
trial of the trial script.

  • p替换标记 打印匹配的行,通常和sed的-n选项(禁止sed编辑器输出)一起使用。
cat data5.txt
This is a test line.
This is a different line.

先看看如果不加 -n 是什么效果

sed 's/test/trial/p' data5.txt

sed命令默认输出所有行,因此两行都会被输出,其中test被替换为trial。p替换标记输出匹配行,因此test被替换为trial的那行又出现了一遍。另外由于sed是逐行处理,处理完立即显示结果,因此第二行 This is a different line. 最后输出。

再看看加 -n 是什么效果

sed -n 's/test/trial/p' data5.txt

-n选项禁止sed编辑器输出,p替换标记输出匹配行,二者配合使用的效果就是只输出被替换命令修改过的行。

  • w替换标记,sed正常输出在STDOUT,不过会将匹配的行保存到指定文件中。
sed 's/test/trial/w test.txt' data5.txt
#输出
This is a trial line.
This is a different line.

cat test.txt
#输出
This is a trial line.


2. 自定义替换分隔符

有时会在文本字符串中遇到一些不太方便在替换模式中使用的字符。Linux中一个常见的例子就是正斜线 /,需要使用转义符号反斜线 \,这通常会带来困惑和错误。

#用C shell替换/etc/passwd文件中的bash shell
sed 's/\/bin\/bash/\/bin\/csh/' /etc/passwd

为了解决这个问题,sed允许选择其他字符来作为替换命令中的字符串分隔符,例如使用!,这样路径名就更容易阅读和理解了。

sed 's!/bin/bash!/bin/csh!' /etc/passwd


四、 行寻址(过滤)

sed中使用的命令默认会作用于所有行,如果只想将命令作用于特定行,则必须用行寻址(line addressing)。

sed支持两种形式的行寻址:

  • 以数字表示行区间
  • 文本模式过滤行

两种形式都使用相同的格式来指定地址:

[address]command

也可以将特定地址的多个命令分组:

address {
command1
command2
command3
}


1. 以数字表示行区间

 sed会将文本流中的第一行编号为1,然后继续按顺序为接下来的行分配行号。在命令中指定的地址可以是单个行号,或是 [起始行号,结尾行号] 指定的区间内的行。

  • 只修改指定的第二行的文本

sed '2s/dog/cat/' data1.txt
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy cat
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog

  • 修改地址[2,3]行的文本

sed '2,3s/dog/cat/' data1.txt
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy cat
The quick brown fox jumps over the lazy cat
The quick brown fox jumps over the lazy dog

  • 修改地址[2,$]行的文本,即第二行至最后一行

sed '2,$s/dog/cat/' data1.txt
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy cat
The quick brown fox jumps over the lazy cat
The quick brown fox jumps over the lazy cat

2. 文本模式过滤行

sed编辑器允许指定文本模式来过滤出命令要作用的行。格式如下:

/pattern/command

举个例子,如果你想只修改用户Samantha的默认shell,可以使用sed命令。

grep Samantha /etc/passwd
Samantha:x:502:502::/home/Samantha:/bin/bash

sed '/Samantha/s/bash/csh/' /etc/passwd
root:x:0:0:root:/root:/bin/bash
...
Samantha:x:502:502::/home/Samantha:/bin/
csh
Timothy:x:503:503::/home/Timothy:/bin/bash
 

该命令只作用到匹配文本模式的行上。虽然使用固定文本模式能帮你过滤出特定的值,但其作用难免有限。 sed在文本模式中还支持正则表达式(详情参考下篇)来帮助你创建匹配效果更好的模式。

3. 命令组合

如果需要在单行上执行多条命令,可以用花括号将多条命令组合在一起。 sed编辑器会处理地址行处列出的每条命令。

sed '2{
> s/fox/elephant/
> s/dog/cat/
> }' data1.txt
The quick brown fox jumps over the lazy dog.
The quick brown elephant jumps over the lazy cat.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.

当然,也可以在一组命令前指定一个地址区间。

sed '3,${
> s/brown/green/
> s/lazy/active/
> }' data1.txt
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick green fox jumps over the active dog.
The quick green fox jumps over the active dog.

五、 删除行

文本替换s不是sed支持的唯一命令,如果需要删除文本流中的特定行,可以用删除命令d。

删除命令d名副其实,会删除匹配指定寻址模式的所有行。使用该命令时要特别小心,如果你忘记加入寻址模式的话,流中的所有行都会被删除(当然默认也不会删原文件内容)。

cat data1.txt
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog

sed 'd' data1.txt  #没有输出,删完了

当和指定地址一起使用时,删除命令显然能发挥出最大的功用。

cat data6.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.

sed '3d' data6.txt
This is line number 1.
This is line number 2.
This is line number 4.

# 通过特定行区间指定
sed '2,3d' data6.txt
This is line number 1.
This is line number 4.

sed '3,$d' data6.txt
This is line number 1.
This is line number 2.

sed的模式匹配特性也适用于删除命令。

sed '/number 1/d' data6.txt
This is line number 2.
This is line number 3.
This is line number 4.

也可以使用两个文本模式来删除某个区间内的行,但这么做时要小心。你指定的第一个模式打开行删除功能,第二个模式会关闭行删除功能。 sed编辑器会删除两个指定行之间的所有行(包括指定的行)。

sed '/1/,/3/d' data6.txt
This is line number 4.

你要特别小心,因为只要sed在数据流中匹配到了开始模式,删除功能就会打开,这可能会导致意外的结果。

cat data7.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.
This is line number 1 again.
This is text you want to keep.
This is the last line in the file.

sed '/1/,/3/d' data7.txt
This is line number 4.

第二个出现数字1的行再次触发了删除命令,因为没有找到停止模式,就将数据流中的剩余行全部删除了。当然,如果你指定了一个从未在文本中出现的停止模式,显然会出现另外一个问题。因为删除功能在匹配到第一个模式的时候打开了,但一直没匹配到结束模式,所以整个数据流都被删掉了。

$ sed '/1/,/5/d' data7.txt
$

六、 插入和附加文本

sed编辑器允许向数据流插入和附加文本行。

  • 插入(insert)命令(i)会在指定行前增加一个新行
  • 附加(append)命令(a)会在指定行后增加一个新行

这两条命令的费解之处在于它们的格式,它们不能在单个命令行上使用。你必须指定是要将行插入还是附加到另一行。格式如下:

sed '[address]command\
new line'
# new line中的文本将会出现在sed输出中你指定的位置。
echo "Test Line 2" | sed 'i\Test Line 1'
Test Line 1
Test Line 2

echo "Test Line 2" | sed 'a\Test Line 1'
Test Line 2
Test Line 1

或者分多行执行

echo "Test Line 2" | sed 'i\
> Test Line 1'
Test Line 1
Test Line 2

如果要向数据流内部添加文本呢?你必须用寻址来告诉sed编辑器你想让数据插入在什么位置。可以匹配行号或文本模式,但不能用区间。这合乎逻辑,因为你只能将文本插入或附加到单个行的前面或后面,而不是行区间的前面或后面。

sed '3i\
> This is an inserted line.' data6.txt
This is line number 1.
This is line number 2.
This is an inserted line.
This is line number 3.
This is line number 4.

sed '3a\
> This is an appended line.' data6.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is an appended line.
This is line number 4.

如果你有一个多行数据流,想要将新行附加到数据流的末尾,只要用代表数据最后一行的$就可以了。

$ sed '$a\
> This is a new line of text.' data6.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.
This is a new line of text.

同理,要在数据流起始位置增加一个新行,只要在第一行之前插入新行即可。要插入或附加多行文本,必须对其中的每一行使用反斜线,直到最后一行。

sed '1i\
> This is one line of new text.\
> This is another line of new text.' data6.txt
This is one line of new text.
This is another line of new text.
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.


七、 修改行

修改(change)命令允许修改数据流中整行文本的内容。它跟插入和附加命令的工作机制一样,你必须在sed命令中单独指定新行。

在下面这个例子中, sed编辑器会修改第三行中的文本。

sed '3c\
> This is a changed line of text.' data6.txt
This is line number 1.
This is line number 2.
This is a changed line of text.
This is line number 4.

也可以用文本模式来寻址,文本模式修改命令会修改它匹配的数据流中的所有行。

cat data8.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.
This is line number 1 again.
This is yet another line.
This is the last line in the file.

sed '/number 1/c\
> This is a changed line of text.' data8.txt
This is a changed line of text.
This is line number 2.
This is line number 3.
This is line number 4.
This is a changed line of text.
This is yet another line.
This is the last line in the file.

你可以在修改命令中使用地址区间,但结果未必如愿。sed会用这一行文本替换数据流中的两行文本,而不是逐一修改这两行文本。

sed '2,3c\
> This is a new line of text.' data6.txt
This is line number 1.
This is a new line of text.
This is line number 4.


八、 转换命令

转换命令是唯一可以处理单个字符的sed命令,格式如下:

[address]y/inchars/outchars/

转换命令会对incharsoutchars值进行一对一的映射一直持续到处理完指定字符。如果incharsoutchars的长度不同,sed会报错。

sed 'y/123/789/' data8.txt
This is line number 7.
This is line number 8.
This is line number 9.
This is line number 4.
This is line number 7 again.
This is yet another line.
This is the last line in the file.

转换命令是一个全局命令,它会每行中找到的所有指定字符进行转换,而不会考虑它们出现的位置,并且你无法限定只转换在特定地方出现的字符。

echo "This 1 is a test of 1 try." | sed 'y/123/456/'
This 4 is a test of 4 try.


九、 更多打印命令

我们在最开始学习了s命令的p标记显示sed编辑器修改过的行。这里再介绍3个相关命令:

  • p命令用来打印文本行(注意这个p是命令,文章开头那个是s命令中的p标记)
  • 等号(=)命令用来打印行号
  • l(小写的L)命令用来列出行

1. 打印行 p

如果只用这个命令,它所做的就是打印已有的数据文本,也没什么特别的。

echo "this is a test" | sed 'p'
this is a test
this is a test
# sed本身输出一次,p命令又输出一次,因此一共两行

p命令最常见的用法还是结合 -n选项,只打印包含匹配的行。

cat data6.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.

sed -n '/number 3/p' data6.txt
This is line number 3.

sed -n '2,3p' data6.txt
This is line number 2.
This is line number 3.

如果需要在修改之前查看行,也可以使用打印命令,比如与替换或修改命令一起使用。

sed -n '/3/{
> p
> s/line/test/p
> }' data6.txt
This is line number 3.
This is test number 3.


2. 打印行号 =

=命令会打印行在数据流中的当前行号(在实际的文本行出现前打印行号),行号由数据流中的换行符决定。

cat data1.txt
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.

sed '=' data1.txt
1
The quick brown fox jumps over the lazy dog.
2
The quick brown fox jumps over the lazy dog.
3
The quick brown fox jumps over the lazy dog.
4
The quick brown fox jumps over the lazy dog.

也可以结合 -n选项 过滤数据行。

sed -n '/number 4/{
> =
> p
> }' data6.txt
4
This is line number 4.


3. 列出行 l

l命令可以打印数据流中的文本和不可打印的ASCII字符。任何不可打印字符要么在其八进制值前加一个反斜线,要么使用标准C风格的命名法,比如\t代表制表符。

cat data9.txt
#中间是制表符Tab
This    line    contains    tabs.

sed -n 'l' data9.txt
This\tline\tcontains\ttabs.$

制表符的位置使用\t来显示,行尾的$表示换行符。如果数据流包含了转义字符,会在必要时候用八进制码来显示。

cat data10.txt
This line contains an escape character.

sed -n 'l' data10.txt
This line contains an escape character. \a$

data10.txt文本文件包含了一个转义控制码来产生铃声。用cat命令来显示文本文件时,你看不到转义控制码,只能听到声音。但是,利用列出命令,你就能显示出所使用的转义控制码。


十、 使用 sed 处理文件

替换命令s包含一些可以用于文件的标记,还有一些sed命令也可以实现同样的目标,不必非要替换文本。

1. 写入文件

w命令用来向文件写入行。该命令的格式如下:

[address]w filename

filename可以使用相对路径或绝对路径,但不管是哪种,运行sed编辑器的用户都必须有文件的写权限。地址可以是sed中支持的任意类型的寻址方式,例如单个行号、文本模式、行区间或文本模式。

下面的例子将数据流中的前两行写入到一个文本文件中。

sed '1,2w test.txt' data6.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.

cat test.txt
This is line number 1.
This is line number 2.

当然,如果你不想让行显示到STDOUT上,可以用sed命令的-n选项。

$ cat data11.txt
Blum, R Browncoat
McGuiness, A Alliance
Bresnahan, C Browncoat
Harken, C Alliance

$ sed -n '/Browncoat/w Browncoats.txt' data11.txt
$

$ cat Browncoats.txt
Blum, R Browncoat
Bresnahan, C Browncoat

2. 从文件读取数据

读取(read)命令(r)允许你将一个独立文件中的数据插入到数据流中。格式如下:

[address]r filename

读取命令只能指定单独一个行号或文本模式地址,sed会将文件中的所有文本插入到指定地址后。

cat data12.txt
This is an added line.
This is the second added line.

sed '3r data12.txt' data6.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is an added line.
This is the second added line.
This is line number 4.

sed '/number 2/r data12.txt' data6.txt
This is line number 1.
This is line number 2.
This is an added line.
This is the second added line.
This is line number 3.
This is line number 4.

#在数据流的末尾添加文本
sed '$r data12.txt' data6.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.
This is an added line.
This is the second added line.

读取命令的另一个很酷的用法是和删除命令配合使用:利用另一个文件中的数据来替换文件中的占位文本。

举例来说,假定你有一份套用信件保存在文本文件中:

cat notice.std
Would the following people:
LIST
please report to the ship's captain.

套用信件将通用占位文本LIST放在人物名单的位置。要在占位文本后插入名单,就可以用读取命令。但这样的话,占位文本仍然会留在输出中,要删除占位文本,可以用删除命令。结果如下:

sed '/LIST/{
> r data11.txt
> d
> }' notice.std
#输出
Would the following people:
Blum, R Browncoat
McGuiness, A Alliance
Bresnahan, C Browncoat
Harken, C Alliance
please report to the ship's captain.

现在占位文本已经被替换成了数据文件中的名单

你可能感兴趣的:(shell,脚本&命令,编辑器,vim,linux)