24. 文本处理工具之sed

Date:

2018-09-02

24.1. sed简介

sed是linux文本处理工具的三件套之一。

sed介绍

stream editor for filtering and transforming text,sed是流编辑器。一般sed更擅长文本的行操作。

sed 全名为 stream editor,流编辑器,用程序的方式来编辑文本,功能相当的强大。是贝尔实验室的 Lee E.McMahon 在 1973 年到 1974 年之间开发完成,目前可以在大多数操作系统中使用,sed 的出现作为 grep 的继任者。与vim等编辑器不同,sed 是一种非交互式编辑器(即用户不必参与编辑过程),它使用预先设定好的编辑指令对输入的文本进行编辑,完成之后再输出编辑结构。sed 基本上就是在玩正则模式匹配,

sed工作原理

sed会一次处理一行内容。处理时,把当前处理的行存储在临时缓冲区中,成为”模式空间”,接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有改变,除非你使用重定向存储输出。

24.2. sed详解

24.2.1. 命令格式

sed [选项]… {脚本(如果没有其他脚本)} [输入文件]…

24.2.2. 命令参数

注意

重点常用参数:
  • -n 结合子命令 p

  • -i 用来处理文本内容替换。用这个参数才能替换文件中指定内容并写入文件。

  1. 参数

参数

含义作用

-n

只打印模式匹配的行

-e

直接在命令行模式上进行sed动作编辑,此为默认选项

-f

将sed的动作写在一个文件内,用–f filename 执行filename内的sed动作

-r

支持扩展表达式

-i

直接修改文件内容

  1. 子命令

命令

作用

a

在当前行下面插入文本。

i

在当前行上面插入文本。

c

把选定的行改为新的文本。

d

删除,删除选择的行。

D

删除模板块的第一行。

s

替换指定字符

h

拷贝模板块的内容到内存中的缓冲区。

H

追加模板块的内容到内存中的缓冲区。

g

获得内存缓冲区的内容,并替代当前模板块中的文本。

G

获得内存缓冲区的内容,并追加到当前模板块文本的后面。

l

列表不能打印字符的清单。

n

读取下一个输入行,用下一个命令处理新的行而不是用第一个命令。

N

追加下一个输入行到模板块后面并在二者间嵌入一个新行,改变当前行号码。

p

打印模板块的行。

P(大写)

打印模板块的第一行。

q

退出Sed。

b

lable分支到脚本中带有标记的地方,如果分支不存在则分支到脚本的末尾。

r

file从file中读行。

t

label if分支,从最后一行开始,条件一旦满足或者T,t命令,将导致分支到带有标号的命令处,或者到脚本的末尾。

T

label错误分支,从最后一行开始,一旦发生错误或者T,t命令,将导致分支到带有标号的命令处,或者到脚本的末尾。

w

file写并追加模板块到file末尾。

W

file写并追加模板块的第一行到file末尾。

!

表示后面的命令对所有没有被选定的行发生作用。

=

打印当前行号码。

#

把注释扩展到下一个换行符以前。

  1. 替换标记

g

表示行内全面替换。

p

表示打印行。

w

表示把行写入一个文件。

x

表示互换模板块中的文本和缓冲区中的文本。

y

表示把一个字符翻译为另外的字符(但是不用于正则表达式)

1

子串匹配标记

&

已匹配字符串标记

24.3. sed实例

24.3.1. 参数实例

  1. 参数 -n

1[root@zzjlogin ~]# echo -e 'hello world\nnihao' | sed 's/hello/A/'
2A world
3nihao
4[root@zzjlogin ~]# echo -e 'hello world\nnihao' | sed -n 's/hello/A/'
5[root@zzjlogin ~]# echo -e 'hello world\nnihao' | sed -n 's/hello/A/p'
6A world

小技巧

参数 -n 是只打印匹配的行,后面是替换操作, 所以 sp 是替换命令,如果没有替换后的打印命令 p 也是不能打印的。

  1. 参数 -e

使用参数 -e 可以在多次处理,而不用每次处理结果用管道连接后再处理。

参考下面实例:

1[root@zzjlogin ~]# echo -e 'hello world' | sed -e 's/hello/A/' -e 's/world/B/'
2A B
3[root@zzjlogin ~]# echo -e 'hello world' | sed 's/hello/A/;s/world/B/'
4A B
  1. 参数 -i

sed默认会把输入行读取到模式空间,简单理解就是一个内存缓冲区,sed子命令处理的内容是模式空间中的内容, 而非直接处理文件内容。因此在sed修改模式空间内容之后,并非直接写入修改输入文件,而是打印输出到标准输出。 如果需要修改输入文件,那么就可以指定-i选项。

注意

参数 -i 替换文件内容时,替换标记命令的 p 不可以一起用,否则会出现替换文件中有两行。

1[root@zzjlogin ~]# cat test.txt
2hello world
3[root@zzjlogin ~]# sed 's/hello/A/' test.txt
4A world
5[root@zzjlogin ~]# cat test.txt
6hello world
7[root@zzjlogin ~]# sed -i 's/hello/A/' test.txt
8[root@zzjlogin ~]# cat test.txt
9A world

小技巧

默认匹配替换每行第一次匹配的地方,如果把文件中所有匹配的都替换,需要用替换标记命令 g

 1[root@zzjlogin ~]# cat test.txt
 2hello world
 3hello world
 4hello world
 5hello world hello
 6
 7[root@zzjlogin ~]# sed -i 's/hello/A/' test.txt
 8[root@zzjlogin ~]# cat test.txt
 9A world
10A world
11A world
12A world hello
13
14[root@zzjlogin ~]# sed -i 's/A/hello/' test.txt
15[root@zzjlogin ~]# cat test.txt
16hello world
17hello world
18hello world
19hello world hello
20
21[root@zzjlogin ~]# sed -i 's/hello/A/g' test.txt
22[root@zzjlogin ~]# cat test.txt
23A world
24A world
25A world
26A world A
  1. 参数 -r

sed命令的匹配模式支持正则表达式的,默认只能支持基本正则表达式,如果需要支持扩展正则表达式,那么需要添加-r选项。

24.3.2. 行选择实例

默认情况下sed会对每一行内容进行匹配、处理、输出,某些情况不需要对处理的文本全部编辑,只需要其中的一部分,比如1-10行,偶数行,或者是包含”hello”字符串的行,这种情况下就需要我们去定位特定的行来处理,而不是全部内容。

  1. 只显示指定行

小技巧

文件末行用 $ 来表示。

显示指定行:

将第4行中hello字符串替换为A,其它行如果有hello也不会被替换。

1sed –n '4s/hello/A/' message

显示末行:

$符号表示最后一行,和正则中的$符号类似,但是第1行不用^表示,直接1就行了。

1sed –n '$s/hello/A/' message
  1. 显示指定区间的行内容

将第2-4行中hello字符串替换为A,其它行如果有hello也不会被替换。

1sed –n '2,4s/hello/A/' message

从第2行开始,再接着往下数4行,也就是2-6行,这些行会把hello字符替换为A。

1sed –n '2,+4s/hello/A/' message

第4行开始,到第6行。解释6的由来,”4,~3”表示从4行开始到下一个3的倍数,这里从4开始算,那就是6了,当然9就不是了,因为是要求3的第一个超过前边数字4的倍数,感觉这种适用场景不会太多。

1sed –n '4,~3s/hello/A/' message
  1. 按照等差数列取行

从第4行开始,每隔3行就把hello替换为A。比如从4行开始,7行,10行等依次+3行。这个比较常用,比如3替换为2的时候,也就是每隔2行的步调,可以实现奇数和偶数行的操作。

1sed –n '4~3s/hello/A/' message
  1. 选择去除指定行的其余行

!符号表示取反,该命令是将除了第1行,其它行hello替换为A,上述定址方式也可以使用!符号。

1sed -n '1!s/hello/A/' message
  1. 空行操作

删除空行

1sed -n '/^$/d' message

备注

上面命令中 d 是替换标识命令,即删除操作。

24.3.3. 子命令实例

  1. 子命令 a

a表示在指定行下边插入指定行的内容。

将message文件中每一行下边都插入添加一行内容是A。

1sed 'a A' message

将message文件中1-2行的下边插入添加一行内容是A

1sed '1,2a A' message

在匹配内容的行下面追加一行内容:

备注

这是每个匹配行下面都追加。

 1[root@zzjlogin ~]# cat /root/test.txt
 290
 391
 492
 593
 694
 795
 896
 997
1098
1199
12100
1390
1491
1592
1693
1794
1895
1996
2097
2198
2299
23100
24[root@zzjlogin ~]# sed -i '/91/a\append' /root/test.txt
25[root@zzjlogin ~]# cat /root/test.txt
2690
2791
28append
2992
3093
3194
3295
3396
3497
3598
3699
37100
3890
3991
40append
4192
4293
4394
4495
4596
4697
4798
4899
49100
  1. 子命令 i

i和a使用上基本上一样,只不过是在指定行上边插入指定行的内容。

将message文件中每一行上边都插入添加一行内容是A。

1sed 'i A' message

将message文件中1-2行的上边分别添加3行,3行内容分别是A、B、C,这里使用了n,插入多行内容都可以按照这种方式来实现。

1sed '1,2i A\nB\nC' message
  1. 子命令 c

c是表示把指定的行内容替换为自己需要的行内容。

将message文件中所有的行内容都分别替换为A行内容。

1sed 'c A' message

将message文件中1-2行的内容替换为A,注意这里说的是将1-2行所有的内容只替换为一个A内容,也就是1-2行内容编程了一行,定址如果连续就是这种情况。

1sed '1,2c A' message
  1. 子命令 d

d表示删除指定的行内容,比较简单,更容易理解。

将message所有行全部删除

1sed 'd' message

将message文件中1-3行内容删除。

1sed '1,3d' message
  1. 子命令 y

y表示字符替换,可以替换多个字符,只能替换字符不能替换字符串,且不支持正则表达式,具体使用方法看例子。

把message中所有a字符替换为A符号,所有b字符替换为B符号。

1sed 'y/ab/AB/' message
  1. 子命令 =

= 可以将行号打印出来。

将指定行的上边显示行号。

1sed '1,2=' message
2
31
4nihao
52
6hello world
  1. 子命令 r

r,类似于a,也是将内容追加到指定行的后边,只不过r是将指定文件内容读取并追加到指定行下边。

将a.txt文件内容读取并插入到message文件第2行的下边。

1sed '2r a.txt' message
  1. 子命令 s

s为替换子命令,是平时sed使用的最多的子命令,没有之一。因为支持正则表达式,功能变得强大无比,下边来详细地说说子命令s的使用方法。

基本语法:

[address]s/pattern/replacement/flags

上面的每个单词含义:

replacement部分用下列字符会有特殊含义

&:用正则表达式匹配的内容进行替换 n:回调参数 ():保存被匹配的字符以备反向引用n时使用,最多9个标签,标签书序从左到右

flags

n:可以是1-512,表示第n次出现的情况进行替换 g:全局更改 p:打印模式空间的内容 w file:写入到一个文件file中

在匹配结果前后分别加了111、222。

1cat message
2hello 123 world
3
4sed -r 's/([a-z]+)( [0-9]+ )([a-z]+)/111&222/' message
  1. 其他子命令

把message文件中内容的每行第一个字符i替换为A,然后把修改内容另存为b.txt文件。

1sed -n 's/i/A/w b.txt' message

把message文件中每行的第2个i字符替换为A。

1sed 's/i/A/2' message

在message文件中每行的首尾分别加上111、222。

1sed -r 's/.*/111&222/' message

24.3.4. 替换标识命令实例

把文件从第22行到第33行复制到56行后面。

1sed '22h;23,33H;56G' /etc/passwd

把文件从第22行到第33行移动到第56行后面。

1sed '22{h;d};23,33{H;d};56g' /etc/passwd

只显示每行的第一个单词。

1sed -r 's/([a-Z]+)([^a-Z]+)(.*)/\1/' /etc/passwd

删除第5行。

1sed '4{n;d}' message

交换每行的第一个单词和最后一个单词。

思路:

文件 /etc/passwd 的列数是固定的。所以可以用分组,然后这些分组后向引用。这样就达到了要求的目的。

1sed -r 's/([a-Z]+)([^a-Z]+)(.*)([^a-Z]+)([a-Z]+)([^a-Z]*$)/\5\2\3\4\1\6/' /etc/passwd