shell三剑客之sed

胸怀万里世界, 放眼无限未来。这篇文章主要讲述shell三剑客之sed相关的知识,希望能为你提供帮助。
1.
流编辑器,过滤和替换文本

工作原理:sed命令将当前处理的行读入模式空间进行处理,处理完把结果输出,并清空模式空间。然后再将下一行计入模式空间进行处理输出,以此类推,直到最后一行。还有一个空间叫保持空间,又称暂存空间,可以暂时存放一些处理的数据,但不能直接输出,只能放到模式空间输出。这两个空间其实就是在内存中初始化的一个内存区域,存放正在处理的数据和临时存放的数据。
选项    描述
-e 执行脚本 、表达式来处理

-f 执行运作从文件读取执行

-r 使用扩展正则表达式
2.
命令    描述
p 打印当前模式空间

P 打印模式空间的第一行

d 删除模式空间,开始下一个循环

D 删除模式空间的第一行,开始下一个循环

= 打印当前行号

a \\text 当前行追加文本

i \\text 当选行上面插入文本

c \\text 所选行替换新文本

q 立即退出sed脚本

r 追加文本来自文件

: label label为b和t命令

b label 分支到脚本中带有标签的位置,如果分支不存在则分支到脚本的末尾

t   label 如果s///是一个成功的替换,才跳转到标签

h H 复制/追加模式空间到保持空间

g G 复制/追加保持空间到模式空间

x   交换模式空间和保持空间内容

l   打印模式空间的行,并显示控制字符$

n N   读取/追加下一行输入到模式空间

w filename 写入当前模式空间到文件

!   取反、否定

& 引用已匹配字符串
3.
地址      描述
【shell三剑客之sed】first~step 步长,每step行,从第first开始

$   匹配最后一行
/regexp/   正则表达式匹配行

number 只匹配指定行

addr1,addr2 开始匹配addr1行开始,直到addr2行结束

addr1,+N 从addr1行开始,向后的N行

addr1,~N 从addr1行开始,到N行结束
4.
打印相关实例

[root@study ~]# tail /etc/services
3gpp-cbsp48049/tcp# 3GPP Cell Broadcast Service Protocol
isnetserv48128/tcp# Image Systems Network Services
isnetserv48128/udp# Image Systems Network Services
blp548129/tcp# Bloomberg locator
blp548129/udp# Bloomberg locator
com-bardac-dw48556/tcp# com-bardac-dw
com-bardac-dw48556/udp# com-bardac-dw
iqobject48619/tcp# iqobject
iqobject48619/udp# iqobject
matahari49000/tcp# Matahari Broker

对比不加-n和加-n
[root@study ~]# tail /etc/services|sed/^blp5/p
3gpp-cbsp48049/tcp# 3GPP Cell Broadcast Service Protocol
isnetserv48128/tcp# Image Systems Network Services
isnetserv48128/udp# Image Systems Network Services
blp548129/tcp# Bloomberg locator
blp548129/tcp# Bloomberg locator
blp548129/udp# Bloomberg locator
blp548129/udp# Bloomberg locator
com-bardac-dw48556/tcp# com-bardac-dw
com-bardac-dw48556/udp# com-bardac-dw
iqobject48619/tcp# iqobject
iqobject48619/udp# iqobject
matahari49000/tcp# Matahari Broker
[root@study ~]# tail /etc/services|sed -n/^blp5/p
blp548129/tcp# Bloomberg locator
blp548129/udp# Bloomberg locator

打印第一行
[root@study ~]# tail /etc/services|sed -n1p
3gpp-cbsp48049/tcp# 3GPP Cell Broadcast Service Protocol

只打印奇数行
[root@study ~]# tail /etc/services|sed -n1~2p
3gpp-cbsp48049/tcp# 3GPP Cell Broadcast Service Protocol
isnetserv48128/udp# Image Systems Network Services
blp548129/udp# Bloomberg locator
com-bardac-dw48556/udp# com-bardac-dw
iqobject48619/udp# iqobject

打印匹配行,及以后一行
[root@study ~]# tail /etc/services|sed -n/^blp5/,+1p
blp548129/tcp# Bloomberg locator
blp548129/udp# Bloomberg locator

不打印最后一行,感叹号是取反
[root@study ~]# tail /etc/services|sed -n$!p
3gpp-cbsp48049/tcp# 3GPP Cell Broadcast Service Protocol
isnetserv48128/tcp# Image Systems Network Services
isnetserv48128/udp# Image Systems Network Services
blp548129/tcp# Bloomberg locator
blp548129/udp# Bloomberg locator
com-bardac-dw48556/tcp# com-bardac-dw
com-bardac-dw48556/udp# com-bardac-dw
iqobject48619/tcp# iqobject
iqobject48619/udp# iqobject

引用变量,下面两种写法效果一样的
[root@study ~]# a=1
[root@study ~]# tail /etc/services|sed -n$a,3p
3gpp-cbsp48049/tcp# 3GPP Cell Broadcast Service Protocol
isnetserv48128/tcp# Image Systems Network Services
isnetserv48128/udp# Image Systems Network Services
[root@study ~]# tail /etc/services|sed -n "$a,3p"
3gpp-cbsp48049/tcp# 3GPP Cell Broadcast Service Protocol
isnetserv48128/tcp# Image Systems Network Services
isnetserv48128/udp# Image Systems Network Services

删除是d,打印是把匹配的打印出来,删除是把匹配的删除,删除只是不再用-n选项
5.
替换 
替换开头是blp5的字符串,并打印
[root@study ~]# tail /etc/services |sed -n s/^blp5/test/p
test48129/tcp# Bloomberg locator
test48129/udp# Bloomberg locator

使用& 命令引用匹配内容并替换
[root@study ~]# tail /etc/services |seds/48129/& .0/
3gpp-cbsp48049/tcp# 3GPP Cell Broadcast Service Protocol
isnetserv48128/tcp# Image Systems Network Services
isnetserv48128/udp# Image Systems Network Services
blp548129.0/tcp# Bloomberg locator
blp548129.0/udp# Bloomberg locator
com-bardac-dw48556/tcp# com-bardac-dw
com-bardac-dw48556/udp# com-bardac-dw
iqobject48619/tcp# iqobject
iqobject48619/udp# iqobject
matahari49000/tcp# Matahari Broker

IP加双引号,下面两种写法效果一样的
[root@study ~]# echo 10.10.10.1 10.10.10.2 10.10.10.3|sed -r s/[^ ]+/"& "/g
"10.10.10.1" "10.10.10.2" "10.10.10.3"
[root@study ~]# echo 10.10.10.1 10.10.10.2 10.10.10.3|sed -r s/[! ]+/"& "/g
10.10.10.1" "10.10.10.2" "10.10.10.3

多重编辑
[root@study ~]# tail /etc/services |sed s/blp5/test/; s/3g/4g/
4gpp-cbsp48049/tcp# 3GPP Cell Broadcast Service Protocol
isnetserv48128/tcp# Image Systems Network Services
isnetserv48128/udp# Image Systems Network Services
test48129/tcp# Bloomberg locator
test48129/udp# Bloomberg locator
com-bardac-dw48556/tcp# com-bardac-dw
com-bardac-dw48556/udp# com-bardac-dw
iqobject48619/tcp# iqobject
iqobject48619/udp# iqobject
matahari49000/tcp# Matahari Broker

注,上面的还可以写成
[root@study ~]# tail /etc/services |sed -e s/blp5/test/ -e s/3g/4g/
4gpp-cbsp48049/tcp# 3GPP Cell Broadcast Service Protocol
isnetserv48128/tcp# Image Systems Network Services
isnetserv48128/udp# Image Systems Network Services
test48129/tcp# Bloomberg locator
test48129/udp# Bloomberg locator
com-bardac-dw48556/tcp# com-bardac-dw
com-bardac-dw48556/udp# com-bardac-dw
iqobject48619/tcp# iqobject
iqobject48619/udp# iqobject
matahari49000/tcp# Matahari Broker

分组使用,加上test
[root@study ~]# tail /etc/services |sed -r s/(.*)(.*)(#.*)/\\1\\2test \\3/
3gpp-cbsp48049/tcptest # 3GPP Cell Broadcast Service Protocol
isnetserv48128/tcptest # Image Systems Network Services
isnetserv48128/udptest # Image Systems Network Services
blp548129/tcptest # Bloomberg locator
blp548129/udptest # Bloomberg locator
com-bardac-dw48556/tcptest # com-bardac-dw
com-bardac-dw48556/udptest # com-bardac-dw
iqobject48619/tcptest # iqobject
iqobject48619/udptest # iqobject
matahari49000/tcptest # Matahari Broker

将协议与端口号位置调换
[root@study ~]# tail /etc/services |sed -r s/(.*)(\\< [0-9]+)\\/(tcp|udp)(.*)/\\1\\3\\/\\2\\4/
3gpp-cbsptcp/48049# 3GPP Cell Broadcast Service Protocol
isnetservtcp/48128# Image Systems Network Services
isnetservudp/48128# Image Systems Network Services
blp5tcp/48129# Bloomberg locator
blp5udp/48129# Bloomberg locator
com-bardac-dwtcp/48556# com-bardac-dw
com-bardac-dwudp/48556# com-bardac-dw
iqobjecttcp/48619# iqobject
iqobjectudp/48619# iqobject
mataharitcp/49000# Matahari Broker

注意:(\\< [0-9]+)这里的\\< 表示的是< ,作用是匹配字符串的开始,而不是行的开始,与^不同,要注意。
替换x为X
[root@study ~]# echo "abc cde xyz"|sed -r s/(.*)x/\\1X/
abc cde Xyz

位置调换
[root@study ~]# echo "abc:cde; 123:456"|sed -r s/([^:]+)(; .*:)([^:]+$)/\\3\\2\\1/
abc:456; 123:cde

注:上面的如果分开来理解,容易理解错,从直觉上看,第一个分组([^:]+)匹配到abc,第二个分组(; .*:)匹配; 123:,第三个分组([^:]+$)匹配到456,那么\\3\\2\\1的顺序,不应该是456; 123:abc吗。其实不是的,首先这个匹配要从整体上来考虑,也就是三个分组是一起连续匹配,就是说第一个匹配非:字符,后面必须连着接着第二个分组即; 123:,然后再紧接着第三个分组即456,这样的话abc后面是:而不是; 123:,所以就第一个分组继续区配非:的字符串cde,cde后面紧接着匹配第二个分组; 123:,那么第一个分组就是cde了,而不是abc。
注释匹配后的多少行
[root@study ~]# seq 10 |sed /5/,+3s/^/#/
1
2
3
4
#5
#6
#7
#8
9
10

注释指定的多行
[root@study ~]# seq 5 |sed -r s/^3|^4/& #/
1
2
3#
4#
5

去掉开头和结尾的空格
[root@study ~]# echo1 2 3|sed s/^\\s*//; s/\\s*$//
1 2 3

添加新内容a、i、c,注意添加的内容前面要加个反斜杠,a是追加i是插入c是替换
[root@study ~]# tail /etc/services |sed /blp5/i \\test
3gpp-cbsp48049/tcp# 3GPP Cell Broadcast Service Protocol
isnetserv48128/tcp# Image Systems Network Services
isnetserv48128/udp# Image Systems Network Services
test
blp548129/tcp# Bloomberg locator
test
blp548129/udp# Bloomberg locator
com-bardac-dw48556/tcp# com-bardac-dw
com-bardac-dw48556/udp# com-bardac-dw
iqobject48619/tcp# iqobject
iqobject48619/udp# iqobject
matahari49000/tcp# Matahari Broker

替换blp5替换新行
[root@study ~]# tail /etc/services |sed /blp5/c \\test
3gpp-cbsp48049/tcp# 3GPP Cell Broadcast Service Protocol
isnetserv48128/tcp# Image Systems Network Services
isnetserv48128/udp# Image Systems Network Services
test
test
com-bardac-dw48556/tcp# com-bardac-dw
com-bardac-dw48556/udp# com-bardac-dw
iqobject48619/tcp# iqobject
iqobject48619/udp# iqobject
matahari49000/tcp# Matahari Broker

在指定行下一行添加一行
[root@study ~]# tail -5 /etc/services |sed 2a \\test
com-bardac-dw48556/tcp# com-bardac-dw
com-bardac-dw48556/udp# com-bardac-dw
test
iqobject48619/tcp# iqobject
iqobject48619/udp# iqobject
matahari49000/tcp# Matahari Broker

在指定行的前一行,后一行分别插入内容
[root@study ~]# seq 5|sed 3s/.*/txt\\n& /
1
2
txt
3
4
5
[root@study ~]# seq 5|sed 3s/.*/& \\ntxt/
1
2
3
txt
4
5

读取文件并追加到匹配行后:
[root@study ~]# tail /etc/services |sed /blp5/r a.txt
3gpp-cbsp48049/tcp# 3GPP Cell Broadcast Service Protocol
isnetserv48128/tcp# Image Systems Network Services
isnetserv48128/udp# Image Systems Network Services
blp548129/tcp# Bloomberg locator
123
456
blp548129/udp# Bloomberg locator
123
456
com-bardac-dw48556/tcp# com-bardac-dw
com-bardac-dw48556/udp# com-bardac-dw
iqobject48619/tcp# iqobject
iqobject48619/udp# iqobject
matahari49000/tcp# Matahari Broker

将匹配行写入文件
[root@study ~]# tail /etc/services |sed /blp5/w b.txt
3gpp-cbsp48049/tcp# 3GPP Cell Broadcast Service Protocol
isnetserv48128/tcp# Image Systems Network Services
isnetserv48128/udp# Image Systems Network Services
blp548129/tcp# Bloomberg locator
blp548129/udp# Bloomberg locator
com-bardac-dw48556/tcp# com-bardac-dw
com-bardac-dw48556/udp# com-bardac-dw
iqobject48619/tcp# iqobject
iqobject48619/udp# iqobject
matahari49000/tcp# Matahari Broker
[root@study ~]# cat b.txt
blp548129/tcp# Bloomberg locator
blp548129/udp# Bloomberg locator

以下属于sed的高级部分:
6.
读取下一行(n和N)
n读取下一行到模式空间
N追加下一行内容到模式空间,并以换行符\\n分隔
打印匹配的下一行
[root@study ~]# seq 5 |sed -n /3/n; p
4

打印偶数
[root@study ~]# seq 6 |sed -n n; p
2
4
6

sed先读取第一行1,执行n命令,获取下一行2,此时模式空间是2,执行p命令,打印模式空间。现在模式空间是2,sed再读取3,执行n命令,获取下一行4,此时模式空间为4,执行p命令,以此类推。
打印奇数
[root@study ~]# seq 6 |sed n; d
1
3
5

sed先读取第一行1,此时模式空间是1,并打印模式空间1,执行n命令,获取下一行2,执行d命令,删除模式空间2,sed再读取3,此时模式空间是3,并打印模式空间,再执行n命令,获取下一行4,执行d命令,删除模式空间的3,以此类推。
打印奇数
[root@study ~]# seq 6 |sed -n p; n
1
3
5

每三行执行一次p命令
[root@study ~]# seq 6|sed n; n; p
1
2
3
3
4
5
6
6

第三行替换一次
#方法1
[root@study ~]# seq 6|sed n; n; s/^/=/; s/$/=/
1
2
=3=
4
5
=6=
#方法2,用地址匹配来实现相同效果
[root@study ~]# seq 6|sed 3~3s/^/=/; s/$/=/
1
2
=3=
4
5
=6=
#方法3
[root@study ~]# seq 6|sed -r n; n; s/^|$/=/
1
2
=3
4
5
=6

当执行多个sed命令时,有时相互会产生影响,我们可以用大括号把他们括起来
[root@study ~]# seq 6|sed N; q
1
2
[root@study ~]# seq 6|sedN; s/\\n//
12
34
56

第一个命令:sed读取第一行1,N命令读取下一行2,并以\\n2追加,此时模式空间是1\\n2,再执行q退出。

为了进一步说明N的功能,看第二个命令:执行N命令后,此时模式空间是1\\n2,再执行把\\n替换为空,此时模式空间是12,并打印。
[root@study ~]# seq 5|sed -n N; p
1
2
3
4
[root@study ~]# seq 6|sed -n N; p
1
2
3
4
5
6

为什么第一个不打印5呢?

因为N命令是读取下一行追加到sed读取的当前行,当N读取下一行没有内容时,则退出,也不会执行p命令打印当前行

当行数为偶数时,N始终就能读到下一行,所以也会执行p命令。
[root@study ~]# seq 5|sed -n $!N; p
1
2
3
4
5

如上,想打印奇数行的最后一行,加一个满足条件,当sed执行到最后一行时,用感叹号不去执行N命令,随后执行p命令。
7.
打印和删除模式空间第一行(P和D)
P 打印模式空间的第一行

D 删除模式空间的第一行

打印奇数
[root@study ~]# seq 6|sed -n N; P
1
3
5

保留最后一行
[root@study ~]# seq 6|sed N; D
6

读取第一行1,执行N命令读取下一行并追加到模式空间,此时模式空间是1\\n2,执行D命令删除模式空间第一行1,剩余2.

读取第二行,执行N命令,此时模式空间是3\\n4,执行D命令删除模式空间第一行3,剩余4.

以此类推,读取最后下一行打印时,而N获取不到下一行则退出,不再执行D,因此模式空间只剩余6就打印

你可能会问,不是剩余2剩余4了吗,它们怎么不打印?是这样的,sed取下一行之前,把模式空间的内容打印到标准输出,并且清除模式空间的内容。
8.
保持空间操作
h 复制模式空间内容到保持空间(覆盖)。

H 复制模式空间内容追加到保持空间。

g 复制保持空间内容到模式空间(覆盖)。

G 复制保持空间内容追加到模式空间。

x 模式空间与保持空间内容互换。
将匹配的内容覆盖到另一个匹配
[root@study ~]# seq 6|sed -e /3/h; d -e /5/g
1
2
4
3
6

h命令把匹配的3复制到保持空间,d命令删除模式空间的3.后面命令再对模式空间匹配5,并用g命令把保持空间3覆盖模式空间5
将匹配的内容放到最后
[root@study ~]# seq 6|sed -e /3/h; d -e $G
1
2
4
5
6
3

交换模式空间和保持空间
[root@study ~]# seq 6|sed -e /3/h; d -e /5/x -e $G
1
2
4
3
6
5

看后面的命令,在模式空间匹配5并将保持空间的3与5交换,5就变成了3,最后把保持空间的5追加到模式空间。
倒叙输出
[root@study ~]# seq 5 |sed 1!G; h; $!d
5
4
3
2
1


每行后添加新行
[root@study ~]# seq 5|sed G
1

2

3

4

5

打印匹配行的上一行内容
[root@study ~]# seq 5|sed -n /3/x; p; h
2


其实执行x; p; q也可以,这样当执行到匹配行的时候,后面h就不再执行了q是退出
打印匹配行到最后一行
[root@study ~]# seq 5|sed -n /3/,$p
3
4
5
[root@study ~]# seq 5|sed -n /3/,$h; x; p
3
4
5
[root@study ~]# seq 5|sed -n /3/:a; N; $!ba; p
3
4
5

打印匹配行下一行到最后一行
[root@study ~]# seq 5|sed -n /3/n; :a; N; $!ba; p
4
5

匹配到3时,n读取下一行4,此时模式空间是4,执行N命令读取下一行并追加到模式空间,此时模式空间是4\\n5,标签循环完成后打印模式空间4\\n5。
9.
标签(:、b和t)
标签可以控制流,实现分支判断。

: lable name 定义标签

b lable 跳转到指定标签,如果没有标签则到脚本末尾

t   label 跳转到指定标签,前提是s///命令执行成功
将换行符替换成逗号
[root@study ~]# seq 6|sedN; s/\\n/,/
1,2
3,4
5,6

这种方式并不能满足我们的需求,每次sed读取到模式空间再打印是新行,替换\\n也只能对N命令追加后的1\\n2这样替换。

这时就可以用到标签了:
[root@study ~]# seq 6|sed :a; N; s/\\n/,/; ba
1,2,3,4,5,6

先将每行读入到模式空间,最后再执行全局替换。$!是如果是最后一行,则不执行b a跳转,最后执行全局替换
[root@study ~]# seq 6|sed :a; N; b a; s/\\n/,/g
1
2
3
4
5
6

可以看到,不加$!是没有替换,因为循环到N命令没有读到行就退出了,后面的替换也就没执行。
这里,我想到了一个不用标签就能成功的,就是先通过H,复制追加到保持空间,到执行到最后一行的时候,交换保持空间与模式空间,然后执行全局替换换行符为逗号,然后打印出全部模式空间,与是如下:
[root@study ~]# seq 5|sed -n H; $x; s/\\n/,/g; p
,1,2,3,4,5

差一点就成功了,不明白为什么1前面还要个逗号,说明1前面有个换行符,为什么会有换行符?百度了下,在
??sed的模式空间和保持空间??  看到了sed命令定义
+ g:[address[,address]]g 将hold space中的内容拷贝到pattern space中,原来pattern space里的内容清除。
+ G:[address[,address]]G 将hold space中的内容append到pattern space\\n后。
+ h:[address[,address]]h 将pattern space中的内容拷贝到hold space中,原来的hold space里的内容被清除。

+ H:[address[,address]]H 将pattern space中的内容append到hold space\\n后。
+ d:[address[,address]]d 删除pattern中的所有行,并读入下一新行到pattern中。

+ D:[address[,address]]D 删除multiline pattern中的第一行,不读入下一行。

+ x:交换保持空间和模式空间的内容。

标红的\\n说明,H命令是追加hold  spac\\n后,说明前面还有个换行符,虽然最初的保持空间是没有内容,但追加时,在前面自动加个换行(\\n),意思是空行也会加换行符的,所以就多出来一个换行符。
于是我想到可以再把替换的第一个换行符,再替换成空,如下:
[root@study ~]# seq 5 |sed -n H; $x; s/\\n/,/g; s/,//1; p
1,2,3,4,5

注意$x; s/\\n/,/g; s/,//1; p这里的1,是指把前面替换成功的第一个逗号替换成空。
想到这里,其实还有别的方式的?。既然追加前面会有换行符,那我覆盖就没有了吧,可以第一个执行覆盖到保持空间,其它的都追加。与是:
[root@study ~]# seq 5 |sed -n 1h; 1!H; $x; s/\\n/,/g; p
1,2,3,4,5

10.
每三个数字加个逗号
[root@study ~]# echo 123456789 |sed -r :a; s/([0-9]+)([0-9]3)/\\1,\\2/; ta
123,456,789

执行第一次时,替换最后一个,跳转后,再对123456匹配替换,直到匹配替换不成功,不执行t命令
11.
查找忽略大小写
[root@study ~]# echo -e "a\\nA\\nb\\nc"|seds/a/1/Ig
1
1
b
c

如果只想输出匹配替换成功的行
[root@study ~]# echo -e "a\\nA\\nb\\nc"|sed -n s/a/1/Igp
1
1

12.
换取总行数,“=”打印当前行号
[root@study ~]# seq 10|sed -n $=
10


    推荐阅读