sed是一种流线型、非交互式编辑器,它一次处理一行内容。处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有改变,除非使用重定向存储输出。sed主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。
sed中动作都是使用单引号(’ ')括起来的。
选项与参数:
- -n :使用安静(silent)模式。在一般 sed 的用法中,所有来自 STDIN 的数据一般都会被列出到终端上。但如果加上 -n 参数后,则只有经过sed 特殊处理的那一行(或者动作)才会被列出来。
- -e :直接在命令列模式上进行 sed 的动作编辑;
- -f :直接将 sed 的动作写在一个文件内, -f filename 则可以运行 filename 内的 sed 动作;
- -r :sed 的动作支持的是延伸型正则表达式。(默认是基础正则表示法语法)
- -i :直接修改读取的文件内容,而不是输出到终端,文件内容会改变。
常用命令:
- p : 打印,将某个选择的数据打印出来。通常会与参数sed -n 一起使用
- d : 删除,不用跟任何参数
- s : 替换,通常这个s的动作可以搭配正规表示法。
- w:将替换成功的行写入指定文件中
p :打印动作分析
不加参数直接使用p命令打印:
[root@admin home]# sed 'p' 12.txt
asdfsd
asdfsd
cccdgg
cccdgg
#直接使用一个p,将内容打印了2遍
[root@admin home]# sed '1p' 12.txt
asdfsd
asdfsd
cccdgg
使用1p时,只是把第一行打印了2遍
这些都是sed的特性,将所有来自 STDIN 的数据都被列出到终端上。然后又加了一个数字1,那第一行又被打印了一次。
加上-n参数后,指定哪行打印哪行:
[root@admin home]# sed -n '1p' 12.txt
asdfsd
这样的效率是最高的,对整个文件只遍历一次
sed通常情况下会通过管道的形式来完成它的任务。
打印第2行到第4行,包括边界行:
[root@admin home]# cat -n 11.txt | sed -n '2,4p'
2
3 bbbb
4 this is aaccdd
cat -n 表示列出行号,如这的 2 3 4。
这个效率要差一些,因为cat就已经将文件遍历一次,然后通过管道的形式传给sed,sed再去打印出来。
也可以使用下面的命令实现这个打印
[root@admin home]# cat -n 11.txt | head -4 | tail -3
这种效率是最低的,先通过cat遍历一遍整个文件,然后通过管道传给head,head再取出前4行,然后再通过管道传给tail,tail再打印出后3行。明显多出了几步,这个文件的行数少,到时没多大影响,但要是文件内容多了,效率就很低了。
sed中的匹配,使用 /…/ 实现
#匹配文件中连续的bb的内容,这个动作跟grep很像
[root@admin home]# cat 11.txt | sed -n '/bbb*/p'
bbbb
ccccbbbcccdddfgg
sed中加入正则表达式
#打印出文件中所有的空行,sed可以包括换行符\n,制表符\t,空格等形成的空行。
[ \t]就代表空行。
[root@admin home]# cat 11.txt | sed -n '/^[ \t]*$/p'
#打印文件中以this单词开头的行
[root@admin home]# cat 11.txt | sed -n '/^this/p'
this is aaccdd
扩展正则表达式:
[root@admin tody]# sed -r 's/ab+/!/' a.txt
this is a test
a
aa
!
!
!c
对于扩展的正则表达式字符,比如 +、?、| ,前面必须加 -r 参数。
基础的是 . 、 * 、 ^ 、 $等。
打印文件中两个以this开头行及其之间的行
[root@admin home]# cat 11.txt | sed -n '/this/,/this/p'
this is aaccdd
ccccbbbcccdddfggi
this is space
#及两次匹配this,中间用逗号隔开
但是这种匹配会引出一个问题,当我文件内容中有3个或者3个以上的行都是以this开头的,它又会怎么打印呢?实例如下:
实例1:
#匹配打印this及其之间的行
[root@admin home]# cat 11.txt
aaaaa
bbbb
this is aaccdd
ccccbbbcccdddfggi
this is space
sdfggccdd
this this
dsdf
[root@admin home]# cat 11.txt | sed -n '/this/,/this/p'
this is aaccdd
ccccbbbcccdddfggi
this is space
this this
dsdf
实例2:
#匹配打印连续字母a和连续字母b及其之间的行
[root@admin home]# cat 12.txt
asdfad
aaaaaddfd
sdfsdfghghdf
bbbbbhjjh
this is aaaa
asdfa
bbbb
gewrwe
[root@admin home]# cat 12.txt | sed '/aaa*/,/bbb*/p'
asdfad
gewrwe
实例3:
#匹配打印字母a和字母b及其之间的行
[root@admin home]# cat 14.txt
a
b
b
a
b
c
d
e
f
[root@admin home]# cat 14.txt | sed '/a/,/b/d'
b
c
d
e
f
上面几个实例中,怎么好像有的实例的运行结果并不是我们预想的呢?为什么会出现这样的结果呢?
其实这里有三种情况,我把它浓缩成两种情况来说明一下出现这样结果的原因:
第一种情况,是命令中第一个需要匹配的字符或者是字符串在文中出现的位置,在与第二个需要匹配的字符或字符串成对匹配的过程中,位置始终在第二个需要匹配的字符或字符串位置的前面,如上面实例2中连续的a与连续的b在成对匹配,连续的a的位置始终在连续b的前面,这样的文件内容在匹配时出现上面情况的原因如下:
其实原因很简单,就是编写人设计的算法原理的问题。sed工具就是一次处理一行的内容,当找到第一次出现this的行以后,它会对这个行做个标记,比如为1。然后继续寻找第二次出现this的行,标记为2(注意:无论这行中有多少个this,都算在一行来处理),然后将这几行的内容打印出来。但是这个文件内容还没扫描完,它会继续往下扫描,继续寻找第三次出现this的行,作为第一个匹配的项,标记为3,再继续往下寻找第4次出现this的行,作为第二个匹配的项,如果找到了,再接着把这几行的内容打印出来,然后再依照这样的原理继续寻找下去,直到文件结束。但是在这期间还会有一个小插曲,就是当出现this的行不是偶数行时,那最后一次出现this的行以及下面的所有内容都会被打印出来(此时就相当于运行的是cat 11.txt | sed -n ‘/this/,//p’,这是可以匹配出this及其下面所有的内容的),这是由于sed匹配的贪婪性造成的。
第二种情况:在成对的匹配中,当第二个需要匹配的字符或字符串出现的位置先与第一个需要匹配的字符或字符串时,程序不会理会它,还是继续寻找第一个需要匹配的字符或字符串,就像上面的实例3,当第一次找到a和b后,打印出a b,然后继续开始寻找下一个a,虽然b出现在第三行,先于第二次寻找的a,但是它不理会,然后继续找下面的与能与a组队的b,如果找到就打印出来,直到文件结束。如果找到了a,但是下面没有b了,那么就会打印出a及其下面所有的内容(相当于在接下来的内容中执行了cat 14.txt | sed -n '/a/,//p’命令,这也是会打印出a下面所有的内容的);如果没找到a,那么程序结束,下面的内容不打印。
d : 删除动作分析
语法结构:
sed '/寻址/d'
- a:追加命令,匹配行的下一行插入一行;sed ‘/寻址/a 新的内容’
- i:插入命令,匹配行的上一行插入一行;sed ‘/寻址/i 新的内容’
- c:更改命令,更改匹配行的内容;sed ‘/寻址/c 新的内容’
注:源文件的内容并没有被真正的删除)类时与grep的-v参数
但是加上-i参数后,就可以操作文件的内容了,而不需要打印到屏幕。
#删除第二行
[root@admin home]# cat 11.txt | sed '2d'
#删除匹配this的行
[root@admin home]# cat 11.txt | sed '/this/d'
查看一下原文件,内容并没有真正的被删除。如果想实现真正删除,有两种做法:
- 方法一:可以通过重定向实现,就是想删除后的内容重定向到一个新的文件中,然后再用该文件去覆盖原文件,当然最好将原文件备份一下,推荐这种做法。
- 方法二:使用sed的-i 参数。sed -i ‘/this/d’ 11.txt,但是不推荐这种做法,比较危险,有可能写错了导致删错。
[root@admin home]# cat 12.txt
asdfad
aaaaaddfd
sdfsdfghghdf
bbbbbhjjh
this is aaaa
asdfa
bbbb
aaaadddg
dgewrwe
#删除连续a和连续b之间的行
[root@admin home]# cat 12.txt | sed '/aaa*/,/bbb*/d'
asdfad
#首先分析一下,按照要求删除后结果应该是:
asdfad
this is aaaa
[root@admin home]# cat 12.txt | sed '/aaa*/,/bbb*/d'
asdfad <--咿,这个结果好奇怪
上面运行的结果尽然不是我们想要的结果,为什么会这样的情况呢?其实这个跟上面讲的打印匹配两this及其之间的行的原理是一样的,这里就不阐述了。都是sed匹配的贪婪性造成的。
[root@admin tody]# cat a.txt
this is a test
a
aa bb
abb
abbb
abbc
# 匹配上上一行插入
[root@admin tody]# sed '/aa/i line' a.txt
this is a test
a
line
aa
abb
abbb
abbc
# 匹配行下一行插入
[root@admin tody]# sed '/aa/a line' a.txt
this is a test
a
aa bb
line
abb
abbb
abbc
#更改匹配行
[root@gloryroad tody]# sed '/aa/c line' a.txt
this is a test
a
line
abb
abbb
abbc
s : 替换动作分析
sed的替换原理是,针对每一行从左到右进行扫描,每次替换一个,并且每一次替换都会产生一个临时寄存器,等到这一次替换完成或者后续需要的命令执行完成,才销毁这个临时寄存器,进行第二次匹配时,再产生新的临时寄存器。
如:
[root@admin home]# cat 14.txt | sed 's/a/c/'
替换命令格式:sed ‘s/a/c’,先写替换动作s,接下来写要替换的字符串,然后写替换后的字符串
有如下一个文件,其内容如下:
[root@admin home]# cat 16.txt
asdf
bsd
b
aacdf
b
c
aaffdd
d
b
b
e
f
将每行第一次出现的字母a替换成字母c
[root@admin home]# cat 16.txt | sed 's/a/c/'
csdf
bsd
b
cacdf
b
c
caffdd
d
b
b
e
f
上面的写法只能替换每行第一次出现的字母a,如果想把所有的a都替换成c的话,就需要加上一个全局参数g。但如果想替换指定那一次匹配的,可以将g改为确定的数字
[root@admin home]# cat 16.txt | sed 's/a/c/g'
csdf
bsd
b
cccdf
b
c
ccffdd
d
b
b
e
f
# 只替换第二次匹配的
[root@admin tody]# sed 's/a/A/2' b.txt
abcdwedAsdg
aAcdfwa
adfeAaadf
a
增加模式匹配,然后替换
[root@admin home]# cat 16.txt
aasdfff
bsd
b
aacdffgg
b
c
aaffdd
将所有含有a字母的行中的字符f替换成z
[root@admin home]# cat 16.txt | sed '/a/s/f/z/g'
aasdzzz
bsd
b
aacdzzgg
b
c
aazzdd
/a/代表模式匹配,必须是//包围。但替换则不一定适用//包围,也可以使用#或者是@符号代替/,这种替换可以用到需要转义的时候。如:
[root@admin home]# cat 16.txt | sed '/a/s#f#z#g'
sed命令中使用单双引号都是可以的,但什么时候必须二选一呢?
当需要匹配的字符串中含有双引号时,外面就用单引号包围,如果里面含有单引号时,外面就用双引号包围。这样做的好处时,可以避免转义单双引号。
#将第4行的所有f替换成z
[root@admin home]# cat 16.txt | sed "4s/f/z/g"
aasdfff
bsd
b
aacdzzgg
b
c
aaffdd
#将所有的字母都换成大写的Z
[root@admin home]# echo "111sss23jjsddd89" | sed 's/[a-zA-Z]/Z/g'
111ZZZ23ZZZZZZ89
使用sed命令也可以实现删除以某个字符或者字符串,实现的方法是,将需要删除的字符或字符串替换成空。
[root@admin home]# cat 16.txt
aasdfff
bsd
b
aacdffgg
b
c
aaffdd
#将连续的a字符串删除
[root@admin home]# cat 16.txt | sed 's/aaa*//'
sdfff
bsd
b
cdffgg
b
c
ffdd
一个特例:
[root@admin home]# echo "111sss23jjsddd89" | sed 's/.*/Z/g'
Z # 还是正则的贪婪性,.*表示所有的字符
[root@admin home]# echo "111sss23jjsddd89" | sed 's/./Z/g'
ZZZZZZZZZZZZZZZZ
[root@admin home]# echo "111sss23jjsddd89" | sed 's/[a-zA-Z]*/Z/g'
Z1Z1Z1Z2Z3Z8Z9Z
出现这样的结果是因sed特性和正则表达式的贪婪性造成的。因为*可以代表0次,所以在匹配第一个数字1时,没有匹配上,因为它不是字母,相当于匹配了0次,所有在前面加了一个字母Z,下面的数字前加了一个字母Z 的原理也是这样的。
sed的临时寄存器
临时寄存器就是用来临时存放数据用的,我们通过一道金典的关于sed的面试题讲解一下临时寄存器。sed会为每一次匹配生产一个临时寄存器,匹配以及后面的动作完成后,再销毁这个临时寄存器,为下一次匹配开辟新的临时寄存器。即每一次匹配都会产生一个新的临时寄存器。
1、批量修改文件的扩展名:将文件夹下所有的 .txt文件改成 .jpg文件
[root@admin test]# ls
aa.txt ls.txt result.txt right.txt sd.ex wrong.txt
#将test目录中所有以.txt文件改成.jpg结尾的文件
[root@admin test]# ls | sed 's/\(.*\)txt/ mv \1txt \1jpg/' | sh
sh: line 5: sd.ex: command not found
[root@admin test]# ls
aa.jpg ls.jpg result.jpg right.jpg sd.ex wrong.jpg
从上面的运行结果可以看出已经修改成功,那个报错我们先不管它。
解题思路分析如下:
首先,我们之前在将sed时说过,它得到的任何结果都不会改变原文件中的内容,所以我们没法直接通过替换命令(ls | sed ‘s/txt/jpg/’)去修改文件的扩展名,这样是实现不了修改原文件的扩展名的。
其次,修改文件的扩展名是通过 mv 命令实现的, 所以要想修改.txt文件为.jpg文件,所以我们必须构造出类似下面的语句
mv aa.txt aa.jpg
最后,把上面实现出的修改文件扩展名的命令语句交给 sh 命令去执行。只有执行了这样的语句,才能实现真正修改文件扩展名的功能。
sed 's/\(.*\)txt/ mv \1txt \1jpg/'命令的详细分析如下:
s : 是替换
\(\) : 这是临时寄存器的固定写法,括号需要转义,因为sed在执行过程中没有存储的功能,但我们修改的也仅是文件的扩展名,所以文件名还是需要被保留的,这个时候就需要先把文件的扩展名临时存放在临时寄存器中,用到时,直接去取出来用。
如果用到了多个临时寄存器,取时需要使用\1, \2等表示。\1表示第一个临时寄存器,\2表示第二个临时寄存器....
.* :是匹配所有的字符,就是文件名
注:sed一次只处理一行的内容,所以文件目录中有多少个.txt文件,就被划分成多少行,然后ls把.txt文件逐行地交给sed去处理。
所以通过这样的语句处理后:
[root@admin test]# ls | sed 's/\(.*\)txt/ mv \1txt \1jpg/'
mv aa.txt aa.jpg
mv ls.txt ls.jpg
mv result.txt result.jpg
mv right.txt right.jpg
mv wrong.txt wrong.jpg
我们需要修改文件扩展名的命令就构造好了,最后就是通过管道符交给 sh 命令去执行这个修改文件扩展名的命令了,这样就达到了我们批量修改文件扩展名的功能。
2、将文件中连续出现的字符替换成一个字符,如aaaaaa替换成a
[root@admin home]# cat 17.txt
1aaaaaaaaa2
1bbbbbbbbb323
3cccccc45
dddddd
eeeeee
asdfsdf
rrrfdgsdfffff
#将17.txt文件中连续出现的字符替换成一个相应的字符
[root@admin home]# cat 17.txt | sed 's/\(.\)\1\1*/\1/'
1a2
1b323
3c45
d
e
asdfsdf
rfdgsdfffff
解答分析:
sed 's/\(.\)\1\1*/\1/'
. :正则表达式中,点号表示匹配一个任意字符,因为这里我不知道都有哪些字符,所以使用一个点代替一个任一字符。
\(.\) :将这个任一的字符存放到临时寄存器中,后面两\1表示再取出2次这个任一字符,紧跟在它后面,相当于将匹配到的那个任一字符又复制了2次,再粘贴2次,这样就组成了3个连续的字符,如aaa,三个连续的字符再加一个星号,就表示匹配连续的字符串了。
最后面一个\1,就代表替换成一个相应的字符。
w:写入文件
[root@gloryroad tody]# cat b.txt | sed 's/a/A/w bb.txt'
Abcdwedasdg
Aacdfwa
Adfeaaadf
A
[root@gloryroad tody]# cat bb.txt
Abcdwedasdg
Aacdfwa
Adfeaaadf
A
寻址
sed默认对每行进行操作,增加寻址后对匹配的行进行操作。
常用寻址有:
- /正则表达式/s/old/new/g
- 行号s/old/new/g(行号可以是具体行号,也可以是最后一行 符 号 , 还 可 以 是 第 几 行 到 第 几 行 , 如 1 , 3 ; 1 , 符号,还可以是第几行到第几行,如1,3; 1, 符号,还可以是第几行到第几行,如1,3;1,)
- 可以两个寻址符号,也可以混合使用行号和正则地址
[root@admin tody]# cat passwd
adm:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:adm/bin:/sbin/nologin
daemon:x:2:2:adm:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
# 通过行号寻址
[root@gloryroad tody]# head -6 passwd | sed '4s/adm/!/g'
adm:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:adm/bin:/sbin/nologin
daemon:x:2:2:adm:daemon:/sbin:/sbin/nologin
!:x:3:4:!:/var/!:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
[root@gloryroad tody]# head -6 passwd | sed '1,4s/adm/!/g'
!:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:!/bin:/sbin/nologin
daemon:x:2:2:!:daemon:/sbin:/sbin/nologin
!:x:3:4:!:/var/!:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
# 通过正则进行寻址,以daemon开头的行替换
[root@gloryroad tody]# head -6 passwd | sed '/daemon/s/adm/!/g'
adm:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:adm/bin:/sbin/nologin
daemon:x:2:2:!:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
# 行号和正则混用,从以bin开头的行开始到最后一行进行替换
[root@gloryroad tody]# head -6 passwd | sed '/^bin/,$s/adm/!/g'
adm:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:!/bin:/sbin/nologin
daemon:x:2:2:!:daemon:/sbin:/sbin/nologin
!:x:3:4:!:/var/!:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync