背景
sed命令最初是由李·麦克马洪(Lee E. McMahon)在1973年所创造。sed(Stream Editor)是一个流编辑器,用于在Unix操作系统中对文本进行编辑。它允许用户对输入的文本进行各种操作,如替换、删除、插入和其他编辑操作,这些操作可以通过命令行进行控制。sed命令的创造为Unix系统用户提供了一种强大的文本处理工具,使得对文本进行批量处理和编辑变得更加高效和方便。
当处理文本或者多行输入时,sed命令将一行行进行筛选处理,而不会直接跳跃到匹配行。
格式
sed [OPTION]... {script-only-if-no-other-script} [input-file]...
其中 :
-
OPTION为选项
-
script-only-if-no-other-script为处理动作,可以有-e参数指定多个 -
input-file为输入文件,可以指定多个
*
注:如果省略input-file,则从标准输入读取内容
说明
sed命令会对输入文件或者管道输入逐行处理,言外之意如果使用sed命令打印文件的第2行,sed不会直接访问该行,而是从第1行逐步处理下来,并且也不会处理完第2行之后结束命令,还会将剩余的内容逐步处理完毕。要真正掌握好sed命令,而不是局限于“增删改查”这样基本的用法,读者需要了解sed命令中两个重要概念:
-
模式空间(pattern space)
-
模式空间是sed命令中的主要工作空间,用于存储、处理当前的文本行。
-
每当sed读取一行文本时,将文本行存储在模式空间中,sed在模式空间中对文本进行处理和操作。
-
模式空间中的文本可以通过命令进行修改、替换、删除、插入等操作,然后根据需要将最终的文本输出。
-
每处理完一行都会清理模式空间,而后处理下一行。
*注:如果sed中使用-e运行多个命令,那该行会在所有-e执行完后才会在模式空间中清理掉
-
保持空间(hold space)
-
保持空间是sed命令中的一个辅助空间,用于存储临时的数据或文本。
-
所有的增、删、改等操作,只能在模式空间中进行,而不能在保持空间中处理。
-
可以通过命令,来将保持空间与模式空间的数据进行互换、追加或者覆盖(将模式空间的数据追加/覆盖进保持空间;或者将保持空间的数据追加/覆盖模式空间)。
-
保持空间初始数据为“一个回车”
-
保持空间的数据会被持续保留(不会像模式空间处理下一行数据前清空),一旦保持空间与模式空间的数据交换,那保持空间仅存储互换而来的数据。
此外,sed命令实际上就是对选中的行进行一系列处理,一种扩展格式如下:
sed -n -e '[...] {a1;a2}; a3; a4' -e '[...] {a1; a2; a3}' filename
[...]表示选择行(可以不加。如果不加,代表对所有行进行操作),选择行的方式可以为[xxx , zzz] / [xxx, +yyy] / [ kkk ~ yyy] / [xxx],
其中xxx与zzz可以是数字或正则,但是yyy与kkk只能是数字,部分样例如下:
-
[1,/3/]:表示从第1行到第一个匹配到3的行(该匹配不算第1行)
-
[2,+5]:表示从第2行开始往下5行
-
[2,5]:表示从第2行到第5行
-
[1~2]:表示从第1行开始每隔2行直到标准输入的末尾(包括第1行)
*注:前后都为闭区间
关于上述参数-n与-e进一步解释,请看下一章节。
此外,
-
'[...] {a1; a2; a3}',表示如果满足[...]中条件,则执行(a1;a2;a3}命令,其它行不执行;
-
'
[...] {a1; a2}; a3'表示满足[...]条件则执行{a1;a2}命令后再执行a3命令,不满足[...]条件则执行a3命令
参数
本文仅介绍常见参数,读者如果有兴趣了解所有参数,请运行"man sed"或者"sed --help"进一步查看。
-
-e:同时进行多个操作。默认使用该参数处理一个操作,如果处理多个操作:sed -e '1s/p/|/g' -e '5s/q/|/g' a.txt
*注:如果使用多个-e处理多个操作,则第一个-e也不能省略
-
-i:直接对输入文件进行操作(慎用,验证后再决定是否使用)
-
-n:抑制“
默认对模式空间的输出”,如果需要输出到终端,请在sed中使用p/P显式打印*注:如果没有-n选项,sed命令默认会在处理完每一行后自动将模式空间的内容输出到标准输出上
-
-E或-r:支持扩展正则表达式,通常的扩展如下:
-
+:重复1次或者多次前面的字符
-
?:重复0次或者1次前面的字符
-
|:用“或”的方式查找多个符合的字符串
-
():作为某个特定组合,比如说sed -E '/level=(fail|error)/p' file,表示在file中查找包括level=fail或者level=error的内容。如果去掉括号sed -n -E '/level=fail|error/p' file,代表查找匹配level=fail或者一行中单独匹配error的内容
-
{m,n}/{m,}/{,n}/{n}:表示重复m到n次(闭区间)/大于等于m次/小于等于n次/n次,在不用-E参数时,所有这部分花括号用法得两侧加反斜线:{m,n}
内置命令(第一部分)
本文也仅介绍常用的sed内置命令,如果想进一步了解,请运行"man sed"。
-
i:将指定的文本在输出时添加到当前行的前面,但不会追加/覆盖到模式空间。比如sed '1i abcd' a.txt是在处理a.txt第1行的时候,将文本abcd在输出时候插入到第1行前面。
-
a:
将指定的文本在输出时追加到当前行的后面,也不会
追加/覆盖到模式空间。 -
c:先清空模式空间的数据,再将指定的文本输出到终端。
-
p:打印当前模式空间中的数据,一般与-n一起使用
-
q:退出sed命令,不处理输入中剩余内容。如果没有-n选项,会打印模式空间中数据
-
Q:退出sed命令,不处理输入中剩余内容。不管有没有-n选项,都不会打印模式空间中数据
-
d:删除当前模式空间中的数据
-
s:对当前模式空间内容进行替换 's/正则/文本/'
*注:为了更方便sed中的替换,所以s命令本身也有几个参数,拓展格式为:'s/正则/文本/[参数]',其中参数有:
数字:表示对一行中第几个出现的匹配项进行替换(如果命令要求对第2个匹配abc的部分进行替换,但该行只有1个abc,那不会进行替换)
i:ignore,忽略大小写
g:global,对整行数据所有匹配内容都进行替换。如果不加g,默认只会对第一个匹配内容进行替换。
p:print,对模式空间修改后的内容进行打印
w:w filename,将模式空间修改后的内容写入到filename中
-
r:将指定文件的内容追加到输出中,但不会追加/覆盖到模式空间。比如sed '1r filename' a.txt是在处理a.txt第1行时候,将filename的内容输出到终端。
-
w:将模式空间的内容写入到某个文件中,比如sed '2w filename' a.txt代表将第二行数据写入到filename中
-
y:y/acbd/ACBD/,在当前模式空间中将a替换成A,将c替换成C,将b替换成B,将d替换成D,比如echo 'ab' | sed 'y/bcad/BCAD/'的输出是AB
用法介绍(第一部分)
-
在a.txt的第2行前加上'xxx',第4行后加上'yyy',并且最后一行替换成'xyz'
首先a.txt中内容如下:
cat a.txt
xxx1 xxxa
xxx2
xxx3 xxb
test1
xxx2
xxx4
xxx3
test2 xxcxx
xxx33 xxx44
xxx2
xxx5
sed命令如下:
sed -e '2i xxx' -e '4a yyy' -e '$c xyz' a.txt
-
删除第2行数据到匹配test的数据
sed '2,/test/d' a.txt
-
将a.txt的第5行中2替换成22,并打印替换后信息
sed -n '5s/2/22/p' a.txt
*注,如果不使用-n参数与内置p命令,结果如下:
-
打印匹配/xxx2/到匹配/xxx3/的数据
读者如果观察源文件,会发现内容中有好几个匹配/xxx2/与/xxx3/的行,那么,到底sed是怎么处理的呢?
假如直接写命令 sed -n '/xxx2/,/xxx3/p' a.txt,是匹配第一个/xxx2/到第一个/xxx3/还是第一个/xxx2/到最后一个/xxx3/,还是每满足一次/xxx2/到/xxx3/都打印出来?
我们先看上述命令的运行结果:
基于上述结果,我们可以猜测首先不是打印的第一个/xxx2/到第一个/xxx3/,也很明显不是打印的第一个/xxx2/到最后一个/xxx3/,看起来像是每碰到一次/xxx2/与/xxx3/都会把之间的内容打印出来,但为何会打印了最后一行xxx5呢?
所以猜测对于[xxx,yyy],如果yyy不存在或者在xxx后不匹配,那代表是xxx开始往下的所有行都会进行处理。为了验证这一点,比如说我们打印a.txt中匹配/xxx2/与/abcdef/(不匹配/xxx2/后任一行)之间的行,看结果会如何。
果然,将第一个/xxx2/的行一直到最后一行的数据,都打印了出来。我们再换一种,打印从第7行开始直到匹配/abcdef/的行:
同样,如果执行sed -n '/xxx2/,/xxxa/p' a.txt
上述案例中,尽管/xxxa/出现在了第1行,但因为/xxx2/最早出现在第2行,也就是第二个正则在第一个正则匹配到的行的下方不能匹配到行(尽管在上方能匹配一行),这种情况下sed仍然会处理从第一个匹配/xxx2/的行到最后一行。
所以通过如上几个案例,也基本证实了最开始的猜测,如果通过[数字/正则, 正则]形式取行,假如第二个正则如果不存在,那命令会持续到文本结束。不过对[a,+b]或者[a~b]这两种取行而言,不存在此种混淆,因为b只能是数字,没有可能匹配到多行的情况。
但需要注意的是,如果sed -n '3,1p' a.txt,处理的是第3行数据,而不是从第3行开始往下所有行的数据。
我们将该部分内容总结如下:
1. [数字]:不会混淆,仅针对数字指定的行数进行处理。如果数字大于标准输入的行数,那sed不会对任何行进行处理(但会扫描标准输入的每一行)*注:数字大于等于1,下同
2. [正则]:不会混淆,仅针对正则能匹配到的所有行进行处理。如果正则匹配不到任何行,那sed不会对任何行进行处理(但会扫描标准输入的每一行)
3. [数字1, 数字2]:不会混淆,仅针对数字1到数字2之间的行进行处理(闭区间)
-
如果数字1大于标准输入的行数,那sed不会对任何行进行处理(但会扫描标准输入的每一行)
-
如果数字1小于等于标准输入的行数,但数字2大于标准输入的行数,那sed处理从数字1到标准输入的最后一行数据
-
如果数字2小于等于数字1,那sed不会报错,并且只会处理数字1对应的行
4. [数字, 正则]:可能混淆,仅针对数字到下面
第一个正则匹配到的行进行处理(而不管该正则可以匹配多少行)
-
如果数字大于标准输入的行数,那sed不会对任何行进行处理(但会扫描标准输入的每一行)
-
如果数字小于等于标准输入的行数,但正则从数字行以后匹配不到任何行,那sed处理从数字行到标准输入的最后一行数据
-
如果数字小于等于标准输入的行数,但正则从数字行以后可以匹配到若干行,
那sed处理从数字行到正则匹配到的第一行数据
5. [正则, 数字]:可能混淆,因为正则可能会匹配到多行,我们从下述样例进行说明:
-
首先看sed -n '/xxx3/,1p' b.txt的结果,起点是正则,尾点是数字,正则在b.txt中对应了第4、7、10行,数字对应第1行。根据上述[数字1, 数字2]类比,如果数字2(尾点)在起点上面,那只会处理起点的行。在本例中,起点是正则,匹配了3行,所以sed会轮询处理这3行,并且由于尾点为1,所以每次轮询都只会输出本轮起点的行,也就是为什么屏幕上输出了3个xxx3。
-
再来看sed -n '/xxx3/,5p' b.txt的结果,起点是正则,尾点是数字,正则对应第4、7、10行,数字对应第5行。所以第一个轮询是从[4,5],也就打印xxx3与xxx4;第二次轮询是[7,5],也就打印了第二个xxx3;第三次轮询是[10,7],所以打印了第3个xxx3。
-
接着看sed -n '/xxx3/,8p' b.txt,起点仍然是正则,尾点仍是数字,正则对应第4、7、10行,数字对应第8行。所以第一个轮询是[4,8],也就打印了xxx3、xxx4、test1、xxx3、xxx5行,此时已经处理完第8行了,所以正则的第二次轮询从再下一个匹配的第10行开始,也就是[10,8],所以继续打印最后匹配的那个xxx3。
-
最后看sed -n '/xxx3/,108p' b.txt,起点、尾点、正则对应行与前述一致,数字对应第108行(超过了标准输入的行数10),第一次轮询处理[4,108]也就是从第4行开始直到最后一行数据,此时也不再需要后续的轮询。
6.
[正则1, 正则2]:可能混淆,不过如果读者把该类场景也联想成起点与尾点的轮询,那所有的类比都是相似的。
简单总结下,对于[x,y]型获取行的场景:
为便于深入掌握,请解释下面命令的结果:
*注:cat -n为打印行号显示;第5行正好为/xxx4/对应行。
内置命令(第二部分)
在内置命令的第一部分,我们着重介绍了与“模式空间”相关的参数,下面介绍一些与保持空间相关的参数,并额外补充部分模式空间参数。
-
n:将下一行数据复制到模式空间,并且之后不会对下一行数据再次扫描
-
N:将下一行数据追加到当前模式空间(变为两行,回车作为换行符),并且之后不会对下一行数据再次扫描
-
P:打印模式空间第一行数据(p代表打印模式空间所有数据)
-
h:将模式空间的数据复制到保留空间
-
H:将模式空间的数据追加到保留空间
-
g:将保留空间的数据复制到模式空间
-
G:将保留空间的数据追加到模式空间
-
x:互换模式空间与保留空间的数据
-
z:将模式空间的数据换成n
-
b:
{a1;b;a2;a3;a4},在执行完a1后,直接跳到最后(不执行a2,a3,a4)
*注:上述复制都是覆盖型复制
所以在有了保持空间之后,sed命令变得更加强大了,不仅能处理单行数据,还能处理多行数据。我们继续使用样例来进行说明。
用法介绍(第二部分)
-
打印a.txt的第2,4,10行
#方法一:使用grep获取行号,再选择第2/4/10行,而后将对应的内容输出 grep -n '' a.txt | grep -E '^(2|4|10):' | cut -f2 -d: #方法二:使用sed -n -e的方式,通过多个-e来进行输出 sed -n -e '2p' -e '4p' -e '10p' a.txt #方法三:使用sed -n外加多个分号直接输出 sed -n '2p;4p;10p' a.txt #方法四:使用保持空间,第2行的时候将模式空间的数据复制到保持空间,第4行追加到保持空间,在第10行的时候追加到保持空间,而后再将两个空间交换,最后打印 sed -n '2h;4H;10{H;x;p}' a.txt #方法五:使用awk的NR参数,将对应的行选出后打印 awk 'NR == 2 || NR == 4 || NR == 10' a.txt
-
将a.txt第3行的数据插入到第8行下
#方法一:直接使用awk,将第3行数据保存到某个变量,并且第8行输出当前行+回车+该变量,其余行正常输出 awk '{if(NR==3){a=$0};if(NR==8){print $0"n"a} else {print}}' a.txt #方法二:使用sed,将第3行数据获取到作为另一个参数传入新的sed sed "8a $(sed -n '3p' a)" a.txt #方法三:使用sed,将第3行数据先复制进保持空间,并在sed处理第8行时,将保持空间数据追加到当前模式空间第8行下 sed '3h;8G' a.txt
-
每一行后加上回车再输出
#方法一:使用awk,每次打印当前行+回车 awk '{print $0"n"}' a.txt #方法二:sed进行s///替换 sed 's/$/n/' a.txt #方法三:sed直接使用a命令追加空行,这里“空行”在a命令追加时,请使用两个反斜杠表示 sed 'a \' a.txt #方法四:将保持空间直接追加到当前模式空间(保持空间默认就是换行) sed 'G' a.txt
-
输出偶数行
#方法一:使用awk awk '{if(NR%2==0) {print}}' a.txt #方法二:仍使用awk,但简化上述命令 awk 'NR%2==0' a.txt #方法三:使用sed,从第2行开始,每隔2行输出 sed -n '2~2p' a.txt #方法四:利用模式空间命令n,直接跳跃到下一行(将下一行复制到模式空间) sed -n 'n;p' a.txt
-
将相邻行合并成一行输出,行内使用'|'分隔
#方法一:使用awk,奇数行只输出自己,偶数行输出|+自己+回车 awk '{if(NR%2==1){printf "%s",$0} else {printf "|%sn",$0}}' a.txt #方法二:使用sed以及-n参数 sed -n '$!N;s/n/|/;p' a.txt #$!N代表如果不是最后一行则将下一行数据追加到当前模式空间。这样能保证如果总行数是奇数行时,最后一行也能被打印出来 #方法三:改进上述sed,去掉-n参数 sed 'N;s/n/|/' a.txt
-
如何取出/abc/的行(忽略大小写)
sed -n '/abc/Ip' a.txt #在s///中,文章上方有介绍过一些参数,但是通过正则匹配若干行时候,如果忽略大小写,请使用I参数
-
除了第3与第5行,其余行下新增文本内容"xxx"
#方法一:使用awk awk '{if(NR == 3 || NR == 8){print} else {print $0"n""xxx"}}' a.txt #方法二:使用两次sed sed 'a xxx' a.txt |sed '6d;16d' #方法三:使用sed的b参数,如果是第3或者8行,直接跳过当前行 sed -n 'p;3b;8b;a xxx' a.txt
-
假如文件内容如下,如何取beijing的同事
[root@host46 ~]# cat name
name1
shanghai
name2
shanghai
name3
tianjin
name4
beijing
name5
beijing
name6
guiyang
name7
guiyang
#方法一:直接grep加B参数,取出beijing的上一行,再使用管道处理 grep -B1 beijing name |grep -v beijing #方法二:使用sed将两行合并成1行,然后再做筛选 sed -n '$!N;s/n/|/;p' name | grep 'beijing'|cut -f1 -d| #方法三:使用awk awk '{if (NR%2==1){a=$0} else {if($0 ~ /beijing/){print a}}}' name #方法四:使用sed,先将模式空间与保持空间交换,再取下一行到模式空间,如果下一行匹配北京,则把保持空间内容重新交换回来后打印 sed -n 'x;n;/beijing/{x;p}' name #方法五:还是使用sed,直接将下一行追加到模式空间中,如果模式空间匹配北京,则打印模式空间上面一行(即为姓名) sed -n 'N;/beijing/P' name
其它用法
sed命令还提供了一些其它用法如下:
-
$:最后一行
-
!:不匹配,比如说sed -n '2!p' a.txt,代表不是第2行则打印;或者sed -n '/abc/!p' a.txt代表不匹配abc则打印
-
&:sed通过s进行替换时,前一部分正则匹配到的内容
-
():sed(使用-E参数)通过s进行替换时,如果使用小括号,则该部分内容在后续可以使用1代替;如果有两个小括号,响应使用2代替
-
U:替换成大写
-
I:替换成小写
-
u:首字母替换成大写
-
i:首字母替换成小写
比如将a.txt转化为大写,可以使用sed 's/[a-z]/U&/g' a.txt
如果只是将a.txt第3行出现的第一个xx替换成大写,并只输出替换行,可以使用sed -n '3s/xx/U&/p' a.txt
如果将a.txt中,连续小写字母与连续数字进行对调,可以使用sed -n -E 's/([a-z]+)([0-9]+)/21/gp' a.txt
总结
sed作为Linux/Unix上上强大的文本处理工具,提供了多种参数供用户选择,并且内部也有各种各样命令来实现对行内容的各种处理。同时,sed还有两种空间:模式空间与保持空间,用户可以自由操作并且切换这两种空间来实现更加复杂的文件增删改查等功能而避免了编写脚本的过程。另外,sed在不特别指定情况下,会逐行处理文件,而不会处理到指定行就结束。指定行时,sed可以通过行号或者正则来完成,并且可以选所有行/部分行/单选一行,或者从某一行到某一行,再或者某一行往下几行,还有从某一行每隔几行等方式。
总之,sed非常强大并且完备,需要多加练习与尝试才能得心应手。此外,sed还有一些其它参数,像是-f,或者其它命令,包括label等方面,本文对这部分不做额外介绍,如果读者有兴趣了解,可以另行查阅资料学习。