一.Sed简介
Sed是一款流编辑工具,用来对文本进行过滤与替换操作,特别是当你想要对几十个配置文件做统一修改时,你会感受到Sed的魅力!Sed通过一次仅读取一行内容来对某些指令进行处理后输出,所以Sed更适合于处理大数据文件。首先,Sed通过文件或管道读取文件内容,但Sed默认并不直接修改源文件,而是将读入的内容复制到缓冲区中,我们称之为模式空间(pattern space),所有的指令操作都是在模式空间中进行的,然后Sed根据相应的指令对模式空间中的内容进行处理并输出结果,默认输出至标准输出(即屏幕上)。Sed的工作流程如下图所示:
二.Sed的基本语法格式
Sed从文件中读取数据,如果没有输入文件,则默认对标准输入进程数据进行处理,脚本指令是第一个非“-”开头的参数,具体语法格式如下:
sed [选项]...{脚本指令} [输入文件]
选项 |
含义 |
--version |
显示sed版本 |
--help |
显示帮助文档 |
-n,--quit,--silent |
静默输出,默认情况下,sed程序在所有的脚本指令执行完毕后,将自动打印模式空间中的内容,该选项可以屏蔽自动打印。 |
-e script |
允许多个脚本指令被执行 |
-f script-file |
从文件中读取脚本指令,对编写自动脚本程序很实用。 |
-i,--in-place |
慎用,该选项将直接修改源文件 |
-l,N |
该选项指令l指令可以输出的行长度,l指令为输出非打印字符。 |
--posix |
禁用GNU sed扩展功能 |
-r |
在脚本指令中使用扩展正则表达式 |
-s,--separate |
默认情况下,sed将把输入的多个文件名作为一个长的连续的输入流,而GNU sed则允许把它们当作单独的文件 |
-u,--unbuffered |
最低限度的缓存输入和输出 |
三.Sed入门规范
1.基本格式规范
Sed通过特定的脚本指令对文件进行处理,这里就简单介绍几个脚本指令操作作为Sed程序的规范。a,append表示追加指令;i,insert表示插入指令;d,delete表示删除指令;s,substitution表示替换指令。Sed脚本指令的基本格式是:[地址]命令(有些命令仅可以对一行操作,有些可以对多行操作),命令也可以用花括号进行组合,是命令序列可以作用于同一个地址:
address{
command1
command2
command3
}
注意:第一个命令可以和左花括号在同一行,但右花括号必须单独处于一行。此外,命令后添加空格会产生错误。
下面的test.txt为操作样本源文件(注意有若干空白行),介绍Sed的用法:
[root@andrew Andrew]# cat -n test.txt
1 DEVICE=eno16777736
2 BOOTPROTO=static
3 IPADDR=192.168.0.1
4 NETMASK=255.255.255.0
5
6 GATEWAY=192.168.0.254
7
8 ONBOOT=yes
第二行后追加TYPE=Ethernet:
[root@andrew Andrew]# sed '2a TYPE=Ethernet' test.txt
DEVICE=eno16777736
BOOTPROTO=static
TYPE=Ethernet
IPADDR=192.168.0.1
NETMASK=255.255.255.0
GATEWAY=192.168.0.254
ONBOOT=yes
第三行前追加TYPE=Network:
[root@andrew Andrew]# sed '3i TYPE=Network' test.txt
DEVICE=eno16777736
BOOTPROTO=static
TYPE=Network
IPADDR=192.168.0.1
NETMASK=255.255.255.0
GATEWAY=192.168.0.254
ONBOOT=yes
将样本文件中的所有yes替换为no:
[root@andrew Andrew]# sed 's/yes/no/g' test.txt
DEVICE=eno16777736
BOOTPROTO=static
IPADDR=192.168.0.1
NETMASK=255.255.255.0
GATEWAY=192.168.0.254
ONBOOT=no
以上大多数操作指令都依据行号定位操作对象(地址),如2a即第二行后追加。实际工作中,可能大多数情况你并不确定你要操作对象(地址)的行号,这时更多的会使用正则表达式确定操作对象(地址)。下面是使用正则表达式定位操作行的示例:
匹配到包含ONBOOT的行,并在其后添加TYPE=Ethernet:
[root@andrew Andrew]# sed '/ONBOOT/a TYPE=Ethernet' test.txt
DEVICE=eno16777736
BOOTPROTO=static
IPADDR=192.168.0.1
NETMASK=255.255.255.0
GATEWAY=192.168.0.254
ONBOOT=yes
TYPE=Ethernet
匹配GATEWAY开始的行,并删除该行:
[root@andrew Andrew]# sed '/^GATEWAY/d' test.txt
DEVICE=eno16777736
BOOTPROTO=static
IPADDR=192.168.0.1
NETMASK=255.255.255.0
ONBOOT=yes
另外,我们的操作指令可以写入到脚本文件中,并通过sed的-f选项读取,脚本文件中的注释行是以#开始的行,如果#后面的字符为n,则屏蔽Sed程序的自动输出功能,等同于命令选项-n。创建一个sed脚本,内容如下:
[root@andrew Andrew]# cat sed.sh
#This is a test sed command
#脚本内容为匹配到空白行后,删除该行
/^$/d
对test.txt文件执行sed.sh脚本指令:
[root@andrew Andrew]# sed -f sed.sh test.txt
DEVICE=eno16777736
BOOTPROTO=static
IPADDR=192.168.0.1
NETMASK=255.255.255.0
GATEWAY=192.168.0.254
ONBOOT=yes
而当你需要执行多个指令时,可以使用以下三种方法:
(1)使用分号隔开指令
[root@andrew Andrew]# sed 's/yes/no/;s/static/dhcp/' test.txt
DEVICE=eno16777736
BOOTPROTO=dhcp
IPADDR=192.168.0.1
NETMASK=255.255.255.0
GATEWAY=192.168.0.254
ONBOOT=no
(2)使用-e选项
[root@andrew Andrew]# sed -e 's/yes/no/' -e 's/static/dhcp/' test.txt
DEVICE=eno16777736
BOOTPROTO=dhcp
IPADDR=192.168.0.1
NETMASK=255.255.255.0
GATEWAY=192.168.0.254
ONBOOT=no
(3)利用分行
[root@andrew Andrew]# sed '
> s/yes/no/
> s/static/dhcp/' test.txt
DEVICE=eno16777736
BOOTPROTO=dhcp
IPADDR=192.168.0.1
NETMASK=255.255.255.0
GATEWAY=192.168.0.254
ONBOOT=no
然而在命令行上输入过长的指令是愚蠢的,这时需要使用-f选项指定sed脚本文件,在脚本文件中可以包含多行指令,而且也便于修改。
2.操作地址匹配范例
通过以上范例不难发现,我们编写的脚本指令需要指定一个地址来决定操作范围,如果不指定,则默认对文件的所有行进行操作。如:sed ‘d’ test.txt将删除test.txt的所有行,而‘2d’则仅删除第二行。Sed为我们提供了以下这些方式来确定需要操作地址的范围。
-number |
指定输入文件的唯一行号 |
-first~step |
指定以first开始,并指定操作步长为step,如1、2指定第一行、第三行、第五行……为操作地址。2~5指定第二行开始,每5行匹配一次操作地址 |
$ |
匹配文件的最后一行 |
/regexp/ |
//中间包含的是正则表达式,通过正则表达式匹配操作地址。如果//中正则表达式为空,匹配最近一次正则表达式的匹配地址,后面会有范例 |
\cregexpc |
\c和c之间匹配正则表达式,c字符可以使用任意字符代替 |
addr1,addr2 |
匹配从操作地址1到操作地址2的所有行 |
addr1,+N |
匹配地址1以及后面的N行内容 |
打印文件的奇数行:
[root@andrew Andrew]# cat -n test.txt
1 DEVICE=eno16777736
2 BOOTPROTO=static
3 IPADDR=192.168.0.1
4 NETMASK=255.255.255.0
5
6 GATEWAY=192.168.0.254
7
8 ONBOOT=yes
[root@andrew Andrew]# sed -n '1~2p' test.txt
DEVICE=eno16777736
IPADDR=192.168.0.1
删除2~8之间的所有行:
[root@andrew Andrew]# cat -n test.txt
1 DEVICE=eno16777736
2 BOOTPROTO=static
3 IPADDR=192.168.0.1
4 NETMASK=255.255.255.0
5
6 GATEWAY=192.168.0.254
7
8 ONBOOT=yes
[root@andrew Andrew]# sed '2,8d' test.txt
DEVICE=eno16777736
四.Sed指令和脚本
1.Sed常用指令汇总
下表给出了常用sed脚本指令的说明,下面分别看看每个指令的详细用法:
指令 |
功能 |
指令 |
功能 |
-s |
替换 |
-d |
删除 |
-a |
追加 |
-i |
插入 |
-c |
更改 |
-l |
打印(显示非打印字符) |
-y |
按字符转换 |
-L |
打印(不显示非打印字符) |
-p |
打印 |
-r |
读入文件内容 |
-w |
保存至文件 |
-q |
退出 |
2.部分指令详解
(1)替换指令(s,Substitution)
指令格式:[address]s/pattern/replacement/flags
address为操作地址,s为替换指令,/pattern/匹配需要替换的内容,/replacement为替换的新内容。Flags标记可以是:
Flags标记 |
含义 |
-n |
1-512之间的数字,表示对模式空间中指定模式的第n次出现进行替换。如一行中有3个A,而只想替换第二个A。 |
-g |
对模式空间的所有匹配进行全局更改。没有g则只有第一次匹配被替换,如一行中有3个A,则仅替换第一个A。 |
-p |
打印模式空间的内容 |
-w file |
将模式空间的内容写到文件file中 |
replacement为字符串,用来替换与正则表达式匹配的内容。在replace部分,只有下列字符有特殊含义:
& |
用正则表达式匹配的内容进行替换 |
\n |
匹配第n个子串,该子串之前在pattern中用\(\)指定 |
\ |
转义(转义替换部分包含:&、\等) |
(2)删除指令(d,delete)
删除指令用于删除匹配的行,而且删除命令还会改变sed脚本中命令的执行顺序。因为匹配的行一旦删除,模式空间将变为“空”,自然不会再执行sed脚本后续的命令。删除命令将导致读取新的输入行(下一行),而sed脚本中的命令则从头开始重新执行。需要注意的是,删除时是删除整行,而不只是删除匹配的内容(如果删除匹配的内容,可以使用替换)
(3)转换指令(y)
按字符转换(Transform)的语法格式为:[address]y/source-chars/dest-chars,其中,[address]用来定位需要修改的行,source-chars为需要被修改的字符,dest-chars为准备替换的字符。
3.Sed脚本指令范例
(1)范例1
范例1所使用的样本文件为:
[root@andrew Andrew]# cat test.txt
<html>
<title>First Web</title>
<body>Hello the World!<body>
</html>
范例1:将样本文件中第二个<body>替换为</body>
编写sed脚本,替换与行匹配相同的内容,即将body替换为/body,但仅替换第二个body为/body。
[root@andrew Andrew]# cat sed.sh
/body/{
s//\/body/2
}
执行sed程序的结果如下:
[root@andrew Andrew]# sed -f sed.sh test.txt
<html>
<title>First Web</title>
<body>Hello the World!</body>
</html>
(2)范例2
范例2使用的样本文件为:
[root@andrew Andrew]# cat test.txt
<html>
<title>First Web</title>
<body>
h1Helloh1
h2Helloh2
h3Helloh3
</body>
</html>
范例2:给所有第一个的h1,h2等添加<>,第二个h1,h2添加</>.
编写sed脚本为:
[root@andrew Andrew]# cat sed.sh
/h[0-9]/{
s//\<&\>/1
s//\<\/&\>/2
}
说明:匹配h紧跟一个数字的行,替换与上一行中匹配内容相同的内容,即将h[0-9]替换为<&>,其中&为前面要替换的内容。第一条s替换指令仅替换第一个h1,h2…,第二条s替换指令用来替换第二个h1,h2...
执行sedc程序的结果如下:
[root@andrew Andrew]# sed -f sed.sh test.txt
<html>
<title>First Web</title>
<body>
<h1>Hello</h1>
<h2>Hello</h2>
<h3>Hello</h3>
</body>
</html>
技巧:关于‘s///’命令的另一个妙处是:‘/’分隔符有许多替换方案,如果规则表达式或替换字符串中有许多斜杠,则可以通过在‘s’之后指定一个不同的字符来更改分隔符。示例:sed -e ‘s:/usr/local:/usr:g’ mylist.txt此时是替换分隔符,sed会将/usr/local替换为/usr.
(3)范例3
范例3所使用的样本文件为(注意有空白行):
[root@andrew Andrew]# cat test.txt
DEVICE=eno16777736
ONBOOT=yes
BOOTPROTO=static
IPADDR=192.168.0.1
NETMASK=255.255.255.0
GATEWAY=192.168.0.254
范例3:删除文件中的空白行。
编写sed脚本为:
[root@andrew Andrew]# cat sed.sh
/.*/{
/^$/d
}
执行sed程序的结果如下:
[root@andrew Andrew]# sed -f sed.sh test.txt
DEVICE=eno16777736
ONBOOT=yes
BOOTPROTO=static
IPADDR=192.168.0.1
NETMASK=255.255.255.0
GATEWAY=192.168.0.254
(4)范例4-范例6
范例4-范例7所使用的样本文件为:
[root@andrew Andrew]# cat test.txt
DEVICE=eno16777736
ONBOOT=yes
BOOTPROTO=static
NETMASK=255.255.255.0
GATEWAY=192.168.0.254
范例4:在static行后添加一行,内容为IPADDR=192.168.0.1。
[root@andrew Andrew]# sed '/static/a IPADDR=192.168.0.1' test.txt
DEVICE=eno16777736
ONBOOT=yes
BOOTPROTO=static
IPADDR=192.168.0.1
NETMASK=255.255.255.0
GATEWAY=192.168.0.254
范例5:在匹配NETMASK的行前插入内容IPADDR=192.168.0.1。
[root@andrew Andrew]# sed '/NETMASK/i IPADDR=192.168.0.1' test.txt
DEVICE=eno16777736
ONBOOT=yes
BOOTPROTO=static
IPADDR=192.168.0.1
NETMASK=255.255.255.0
GATEWAY=192.168.0.254
范例6:将包含ONBOOT行的内容更改为ONBOOT=no
[root@andrew Andrew]# sed '/ONBOOT/c ONBOOT=no' test.txt
DEVICE=eno16777736
ONBOOT=no
BOOTPROTO=static
NETMASK=255.255.255.0
GATEWAY=192.168.0.254
(5)范例7和范例8
范例7和范例8所使用的样本文件为:
[root@andrew Andrew]# cat test.txt
DEVICE=eno16777736
ONBOOT=yes
BOOTPROTO=static
netmask=255.255.255.0
GATEWAY=192.168.0.254
范例7:将小写转换为大写
编写sed脚本为:
[root@andrew Andrew]# cat sed.sh
/.*/{
/netmask/y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
}
执行sed程序的结果如下:
[root@andrew Andrew]# sed -f sed.sh test.txt
DEVICE=eno16777736
ONBOOT=yes
BOOTPROTO=static
NETMASK=255.255.255.0
GATEWAY=192.168.0.254
范例8:显示第一、第二行的内容
打印(p):作用类似于l(列印),但不显示非显示字符,一般与-n配合使用
[root@andrew Andrew]# sed -n '1,2p' test.txt
DEVICE=eno16777736
ONBOOT=yes
(6)范例9和范例10
范例9和范例10所使用的样本文件为:
[root@andrew Andrew]# cat name.txt
Jacob
Tom
Jerry
[root@andrew Andrew]# cat mail.txt
[email protected]
[email protected]
[email protected]
范例9:先读取name.txt文件内容,再读取mail.txt文件内容。
编写sed脚本为:
[root@andrew Andrew]# cat sed.sh
/.*/{
$r mail.txt
}
执行sed程序的结果如下:
[root@andrew Andrew]# sed -f sed.sh name.txt
Jacob
Tom
Jerry
[email protected]
[email protected]
[email protected]
范例10:显示name.txt内容的前两行内容后退出sed指令。
[root@andrew Andrew]# sed '2q' name.txt
Jacob
Tom
五.Sed高级应用
正常的Sed数据处理流程是读取文档的一行至模式空间,然后对该行应用相应的Sed指令,当指令完成后输出该行并清空模式空间,依次循环读入文档的下一行数据,直至文档数据结尾。然而在真实环境中的数据可能不会那么有规律,有时我们会把数据分多行写入文档,如:
姓名:张三,
邮箱:[email protected]
姓名:李四,
邮箱:[email protected]
从上面的模板文件中可以看出,实际上每两行为一条完整的记录,而此时如果需要使用Sed对该文档进行处理,就需要对Sed工作流程进行人工干预。
1.多行操作Next
Next(N)指令通过读取新的输入行,并将它追加至模式空间的现有内容之后,来创建多行模式空间。模式空间的最初内容与新的输入行之间用换行符分割。在模式空间中插入的换行符可以使用\n匹配。
范例1:使用的样本文件为:
[root@andrew Andrew]# cat test.txt
Name:Tom
Mail:[email protected]
Name:Jerry
Mail:[email protected]
编写Sed指令脚本如下(读取样本文件内容至模式空间,当读取的内容与Name匹配时,立刻读取下一行内容,再输出模式空间中的内容,#n用来屏蔽自动输出)。
[root@andrew Andrew]# cat sed.sh
#n
/Name/{
N
L
}
运行脚本结果如下:
[root@andrew Andrew]# sed -f sed.sh test.txt
Name:Tom Mail:[email protected]
Name:Jerry Mail:[email protected]
范例2:使用的样本文件为:
[root@andrew Andrew]# cat test.txt
111
222
222
222
333
编写Sed指令脚本(读取样本文件内容至模式空间,当读取的内容与222匹配时,立刻读取下一行内容,再输出模式空间中的内容,小写的l会打印非打印字符)。
[root@andrew Andrew]# cat sed.sh
#n
/222/{
N
l
}
运行脚本结果如下:
[root@andrew Andrew]# sed -f sed.sh test.txt
222\n222$
222\n333$
2.多行操作Print
Print(P)即多行打印P,它与打印p稍有不同,前者仅输出多行模式空间中的第一部分,直到第一个插入的\n换行符为止。综合范例所使用的样本文件为:
[root@andrew Andrew]# cat test.txt
aaa
bbb
ccc
ddd
eee
fff
下面通过多条sed命令对比不同打印方式的差别,输出结果如下所示:
[root@andrew Andrew]# sed '/.*/N' test.txt
aaa
bbb
ccc
ddd
eee
fff
[root@andrew Andrew]# sed '/.*/N;L' test.txt
aaa bbb
aaa
bbb
ccc ddd
ccc
ddd
eee fff
eee
fff
[root@andrew Andrew]# sed '/.*/N;P' test.txt
aaa
aaa
bbb
ccc
ccc
ddd
eee
eee
fff
[root@andrew Andrew]# sed '/.*/N;p' test.txt
aaa
bbb
aaa
bbb
ccc
ddd
ccc
ddd
eee
fff
eee
fff
第一个sed命令使用了N读取下一行,新读取的内容与原有内容直接使用\n分隔。但sed读取下一行后没有任何后续指令,所以sed自动输出,即输出源文件的所有内容。
第二个sed命令使用了N读取下一行,L表示显示模式空间的内容,即aaa bbb,同时sed命令的自动输出功能会把源文件内容显示出来,即aaa,bbb。以此类推,sed继续读取第三行ccc,并使用N将ddd追加至行尾,使用L显示模式空间的内容(不显示非打印字符),sed自动输出后再把源文件内容显示出来。
第三个sed命令使用N将下一行追加至行尾,现在模式空间中的内容为aaa\bbb,而P指令的作用是打印模式空间中的第一部分内容直到\n结尾,即仅打印aaa,这时sed的自动输出功能输出aaa,bbb(sed自动输出会将\n输出为换行)。依次类推,读取第三行ccc,N将ddd追加至行尾,P打印\n前的内容,同时sed命令也将自动输出。
第四个sed命令的原理类似于第三个sed命令,但p打印时,\n看作回车换行,所以打印出来的是aaa回车bbb.
3.多行删除操作Delete(D)
指令d为删除命令,其作用是删除模式空间中的内容并读入新的输入行,而如果sed在d指令后还有多条指令,则余下的指令将不再执行,而是返回第一条指令对新读入行进行处理。而多行删除指令D将删除模式空间中直到第一个插入的换行符(\n)前的这部份内容,它不会读入新的输入行,并返回sed脚本的顶端,使得剩余指令继续应用于模式空间中剩余的内容。
4.Hold(h,H),Get(g,G)
我们知道,模式空间是存放当前输入行的缓冲区。除此之外,Sed还有一个称为保持空间(hold space)的缓冲区。模式空间的内容可以复制到保持空间,保持空间的内容同样可以复制到模式空间,有一组Sed命令用于在两者之间移动数据。
Hold(h|H) |
将模式空间的内容复制或追加到保持空间 |
Get(g|G) |
将保持空间的内容复制或追加到模式空间 |
Exchange(x) |
交换保持空间与模式空间的内容 |
保留空间范例所使用的样本文件如下:
[root@andrew Andrew]# cat test.txt
aaa
bbb
ccc
ddd
[root@andrew Andrew]# cat sed.sh
/aaa/{
h
d
}
/ccc/{
G
}
[root@andrew Andrew]# sed -f sed.sh test.txt
bbb
ccc
aaa
ddd