sed命令详解
sed是stream editor的简称,也就是流编辑器。它一次处理一行内容,处理时,把当前处理的行存储在临时缓存区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有 改变,除非你使用重定向存储输出。
sed,awk和grep号称shell编程三剑客,也是作为运维人员必须要掌握的,最好掌握的深入一点,这样在我们进行文本处理的或者编写脚本的时候会有很大的好处。工作中也是经常用到的,所以记录一下。
一、使用语法
1.1 sed命令的使用规则:
sed [options] 'command' file(s)
sed [options] -f scriptfile file(s)
1.2 OPTION为可选的,常用的option:
-n : 使用安静(silent)模式。在一般sed的用法中,所有来自stdin的内容一般都会被列出到屏幕上。但如果加上-n参数后,则只有经过sed特殊处理的那一行(或者动作)才会被列出来;
-e : 直接在指令列模式上进行 sed 的动作编辑
-f : 直接将 sed 的动作写在一个文件内, -f filename 则可以执行filename内的sed命令
-r : 让sed命令支持扩展的正则表达式(默认是基础正则表达式)
-i : 直接修改读取的文件内容,而不是由屏幕输出。
1.3 sed的command命令:
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.4 sed替换标记
g :表示行内全面替换。
p :表示打印行。
w :表示把行写入一个文件。
x :表示互换模板块中的文本和缓冲区中的文本。
y :表示把一个字符翻译为另外的字符(但是不用于正则表达式)
\1 :子串匹配标记
& :已匹配字符串标记
1.5 sed元字符集
^ :匹配行开始,如:/^sed/匹配所有以sed开头的行。
$ :匹配行结束,如:/sed$/匹配所有以sed结尾的行。
. :匹配一个非换行符的任意字符,如:/s.d/匹配s后接一个任意字符,最后是d。
* :匹配0个或多个字符,如:/*sed/匹配所有模板是一个或多个空格后紧跟sed的行。
[] :匹配一个指定范围内的字符,如/[az]ed/匹配aed和zed。
[^] :匹配一个不在指定范围内的字符,如:/[^A-RT-Z]ed/匹配不包含A-R和T-Z的一个字母开头,紧跟ed的行。
\(..\) :匹配子串,保存匹配的字符,如s/\(love\)able/\1rs,loveable被替换成lovers。
& :保存搜索字符用来替换其他字符,如s/love/**&**/,love这成**love**。
\< :匹配单词的开始,如:/\ 匹配单词的结束,如/love\>/匹配包含以love结尾的单词的行。
x\{m\} :重复字符x,m次,如:/0\{3\}/匹配包含3个0的行。
x\{m,\} :重复字符x,至少m次,如:/0\{2,\}/匹配至少有2个0的行。
x\{m,n\} :重复字符x,至少m次,不多于n次,如:/0\{1,3\}/匹配1~3个0的行。
[:digit:] : 所有数字, 相当于0-9
[:lower:] :所有的小写字母
[:upper:] :所有的大写字母
[:alpha:] :所有的字母
[:alinum:] :可打印的字符(包括空白字符)
[:space:] :空白字符
[:punct:] :标点符号字符
[:blank:] :空格和制表符
[:cntrl:] :控制字符
[:graph:] :可打印的和可见的(非空格)字符
[:print:] :可打印的字符(包括空白字符)
[:xdigit:] : 十六进数字
另外:\n 表示新的一行, \r 表示回车, \t 表示水平制表符, \v 表示垂直制表符, \b 表示后退符, \a 表示"alert", \0xx 表示转换为八进制的ASCII码。
1.6 指定命令行上的多重指令
第一种:用分好分割指令
第二种:在每个指令前放置-e
第三种:使用Bourne shell的分行指令功能。再输入单引号后按回车Enter键,就会出现多行输入的提示符(>)。
博文来自:www.51niux.com
二、实例演示
2.1 显示操作(n和p来结合打印输出)
sed -n '1p' file #显示第一行
sed -n '$p' file #显示最后一行
sed -n '2,5p' file #显示第二行到第五行
sed -n '6,$p' file #显示第六行到最后一行
# nl 1.txt |sed -n 'p;n' #打印奇数行,同sed -n '1~2p'输出一致
1 list listen321 list123
3 listen list222 listen123
# nl 1.txt |sed -n 'n;p' #打印偶数行,同sed -n '2~2p'输出一致
2 listn listen111 list123
4 listen listen333 listen123
# sed '10q' /etc/passwd #q是退出的意思,这里就是打印完10行后退出。一般我们都是过滤小文件体会不出来,如果去过滤大文件的时候,不用遍历全部节省时间。这个q也经常会用在sed的脚本里面使用。
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/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
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin
sed -n '/00/p' /root/install.log #将/root/install.log 中包含00的行打印出来
sed -n '/1\.2/p' /root/install.log #将/root/install.log 中包含1.2的行打印出来,因为.代表0或1个,所以这里需要\转义。
# sed -n '/^#/!p' /etc/vsftpd/vsftpd.conf #打印文件非#开头的行,就是先取出以#号开头的行,再用!取反
anonymous_enable=YES
local_enable=YES
write_enable=YES
local_umask=022
dirmessage_enable=YES
xferlog_enable=YES
connect_from_port_20=YES
xferlog_std_format=YES
listen=YES
pam_service_name=vsftpd
userlist_enable=YES
tcp_wrappers=YES
# sed -n '/^#/!{/^$/!p}' /etc/vsftpd/vsftpd.conf #对取出的结果再用{/^$/!p}再精准匹配,再将空格去除掉,从结果看,除了没有空格结果跟上方一致。
anonymous_enable=YES
local_enable=YES
write_enable=YES
local_umask=022
dirmessage_enable=YES
xferlog_enable=YES
connect_from_port_20=YES
xferlog_std_format=YES
listen=YES
pam_service_name=vsftpd
userlist_enable=YES
tcp_wrappers=YES
# sed -e '/^#/d' -e '/^$/d' /etc/vsftpd/vsftpd.conf #跟上方的效果是一样的。
注:下面三个演示一方面是为了显示行号,主要是为了展示多重指令的操作。
# sed -n '/Virtual Drive: 0/=' MegaSAS.log #打印现在在MegaSAS.log文件中,字符串Virtual Drive: 0都在文件的第几行出现了。
3
143
283
423
563
# sed -n -e '/Virtual Drive: 0/p' -e '/Virtual Drive: 0/=' MegaSAS.log #再复杂一点,就是用-e来指定多重指令,不仅把内容显示出来,还把行号打印出来。
Virtual Drive: 0 (Target Id: 0)
3
Virtual Drive: 0 (Target Id: 0)
143
Virtual Drive: 0 (Target Id: 0)
283
Virtual Drive: 0 (Target Id: 0)
423
Virtual Drive: 0 (Target Id: 0)
563
# sed -n '/Virtual Drive: 0/p;/Virtual Drive: 0/=' MegaSAS.log #跟上面的效果一致,分号也可以用来做多重指令操作
Virtual Drive: 0 (Target Id: 0)
3
Virtual Drive: 0 (Target Id: 0)
143
Virtual Drive: 0 (Target Id: 0)
283
Virtual Drive: 0 (Target Id: 0)
423
Virtual Drive: 0 (Target Id: 0)
563
# sed -n '
> /Virtual Drive: 0/p
> /Virtual Drive: 0/=' MegaSAS.log
Virtual Drive: 0 (Target Id: 0)
3
Virtual Drive: 0 (Target Id: 0)
143
Virtual Drive: 0 (Target Id: 0)
283
Virtual Drive: 0 (Target Id: 0)
423
Virtual Drive: 0 (Target Id: 0)
563
# nl MegaSAS.log|sed -n '/Virtual Drive: 0/p' #当然也可以借助nl来显示行号,这就涉及到跟其他命令组合使用了。
2 Virtual Drive: 0 (Target Id: 0)
130 Virtual Drive: 0 (Target Id: 0)
258 Virtual Drive: 0 (Target Id: 0)
386 Virtual Drive: 0 (Target Id: 0)
514 Virtual Drive: 0 (Target Id: 0)
2.2 删除某行或多行(如果不加-i文件是不会发送改变的,只是会把你操作完的结果显示在屏幕上,你可以通过>指向新文件的方式将更改之后的内容保存下来,用新文件来替换旧文件)
#sed '1d' MegaSAS.log #删除第一行
# sed '4,$d' MegaSAS.log #删除第四行到结尾的行
Adapter 0 -- Virtual Drive Information:
Virtual Drive: 0 (Target Id: 0)
# sed '/Virtual Drive: 0/d' MegaSAS.log #删除带条件Virtual Drive: 0的行
# sed '/^$/d' /root/MegaSAS.log #这里是使用了正则表达式,删除了空行
# sed '/[[:digit:]]/d' /root/MegaSAS.log #这是将所有行中带数字的行删除掉
2.3 增加一行或多行字符串
# sed '1a sed test OK!+1.6' 2.txt #1a表示在第一行的下一行增加字符串,字符串为 sed test OK!+1.6
abc 123 hah
sed test OK!+1.6
# sed '1a sed test OK\!\+1\.6 \tor is OK! \nhaha is OK!' 2.txt #这里用\屏蔽了特殊含义,但是\t为水平制表符我们又跟着插入了一个nor is OK!,再插入一个\n换行符,再插入字符串haha is OK!。这样可以插入多行的内容。
abc 123 hah
sed test OK!+1.6 or is OK!
haha is OK!
# sed '1,3a sed test OK\!\+1\.6 \tor is OK! \nhaha is OK!' 2.txt #这就是多行插入,1,3行都在下一行插入内容。
abc 123 hah
sed test OK!+1.6 or is OK!
haha is OK!
zouqi 321 das
sed test OK!+1.6 or is OK!
haha is OK!
dasda 32131 321321
sed test OK!+1.6 or is OK!
haha is OK!
21321 312321 31315adada
# sed '1,3i sed test OK\!\+1\.6 \tor is OK! \nhaha is OK!' 2.txt #i就是在选定行的上面插入内容,这里就是再1-3行每行的上面插入我们要插入的内容
sed test OK!+1.6 or is OK!
haha is OK!
abc 123 hah
sed test OK!+1.6 or is OK!
haha is OK!
zouqi 321 das
sed test OK!+1.6 or is OK!
haha is OK!
dasda 32131 321321
2.4 对整行做替换
# sed '1c #zhushi' 2.txt #将2.txt的第一行替换成#zhushi
# sed '1,4c \#zhushi' 2.txt #将2.txt的第一行到第四行换成一行的#zhushi,就相当于将1到4行删除了换成了一行的#zhushi
#zhushi
dadas dada das da da d
dad12321 31231 32132131
2.5 对文件内容进行查找并替换,这也是我们经常用到的一种方式,一般会加上参数-i,不过在操作之前要将文件替换掉,线上操作前要在线上做好测试。
# sed '1,3y/abcedfghi/ABCDEFGHI/' 1.txt #用y来变形。把1-3行内所有的abcdefghi转换为大写,注意正则表达式元字符不能使用这个命令。从结果中可以看到第四行没有发生变化。当然也可以不指定行,就是全局替换。
lIst lIstDn321 lIst123
lIstn lIstDn111 lIst123
lIstDn lIst222 lIstDn123
listen listen333 listen123
# sed '/listen1/s@^@#@g' /root/1.txt #在模糊匹配带有listen1的行的前面加上#。
list listen321 list123
#listn listen111 list123
#listen list222 listen123
#listen listen333 listen123
# sed '/listen1/s@$@ haha@g' /root/1.txt #相反的就是再匹配条件的行尾加字段,与# sed '/listen1/s@\(.*\)@\1 haha@g' /root/1.txt 效果一致
list listen321 list123
listn listen111 list123 haha
listen list222 listen123 haha
listen listen333 listen123 haha
# sed -n 's/list/#list/p' 1.txt #s表示替换指定字符, s/list/#list/p :表示将包含list的行替换成前面带#号,p就是打印出来,文件名称是1.txt
#list listen321 list123
#listn listen111 list123
#listen list222 listen123
#listen listen333 listen123
# sed -n 's/list/#list/gp' 1.txt #这句话多了一个g,全面替换每一行,不加g只会将每一行第一个出现的替换掉,加了g一行中有几个都会被替换掉
#list #listen321 #list123
#listn #listen111 #list123
#listen #list222 #listen123
#listen #listen333 #listen123
# sed -n 's/list/#list/2gp' 1.txt #g前面还可以加数字,表示从第几个匹配开始替换,我们这里是数字2就是从第二个匹配开始替换,你会发现第一个匹配没有被替换
list #listen321 #list123
listn #listen111 #list123
listen #list222 #listen123
listen #listen333 #listen123
# sed -n 's/\<list\>/#list/gp' 1.txt #如果我只想对list做精准替换,而不是模糊的匹配替换呢,就需要用到\<以什么开头,\>以什么结尾,\<list\>这就是以list开头和结尾。
#list listen321 list123
# sed 's/\<list[0-9]\{0,\}\>/#list/g' 1.txt #如果我想对list包括list后面带数字的行做替换呢,就要用到正则表达式[0-9]表示所有的数字,\{0,\}表示数字至少出现0次或者以上。
#list listen321 #list
listn listen111 #list
listen #list listen123
listen listen333 listen123
# sed 's/\<list[[:digit:] ]\{0,\}\>/#list/g' 1.txt #这个跟上面的效果是一致的。
定界符:
# sed -n 's/\/bin\/bash/\/sbin\/nologin/gp' /etc/passwd #这个例子主要是展示了定界符,也就是我们前面出现的:s/list/#list/中的///和现在出现的\\\,如果定界符出现在了样式内部需要进行转义,定界符还有###,@@@,:::,|||等等这种三个符号一起的。
root:x:0:0:root:/root:/sbin/nologin
已匹配字符串标记&:
# sed 's/\w\+/[&]/g' /etc/passwd #正则表达式\w\+匹配每一个单词,使用[&]替换它也可以是#&等等,&对应前面匹配的每一个单词。# cat /etc/passwd |sed 's/\w\+/[&]/g' 效果一致
[root]:[x]:[0]:[0]:[root]:/[root]:/[bin]/[bash]
[bin]:[x]:[1]:[1]:[bin]:/[bin]:/[sbin]/[nologin]
# sed 's/listen1/**&**/g' 1.txt #&已匹配的字符串标记,这就是将listen1前后都加上**
list listen321 list123
listn **listen1**11 list123
listen list222 **listen1**23
listen listen333 **listen1**23
子串匹配标记\n:
# echo aaa AAA | sed 's/\([a-z]\+\) \([A-Z]\+\)/\2 \1/' #\(..\)用于匹配子串,所以aaa就被匹配为第一个子串也就是标记为\1,AAA就是第二个子串\,
AAA aaa
# sed -n 's#\(listen[0-9]\)#@\1#gp' /root/1.txt #\(listen[0-9]\) 就是listen后面带数字的为匹配第一个子串,标记为\1
list @listen321 list123
listn @listen111 list123
listen list222 @listen123
listen @listen333 @listen123
2.6 指定行数做替换
# nl /etc/passwd|sed -n '/root/p'
1 root:x:0:0:root:/root:/bin/bash
11 operator:x:11:0:operator:/root:/sbin/nologin
# sed -n '1,10s/root/kaixin/gp' /etc/passwd
kaixin:x:0:0:kaixin:/kaixin:/bin/bash
# sed -n '1,20s/root/kaixin/gp' /etc/passwd
kaixin:x:0:0:kaixin:/kaixin:/bin/bash
operator:x:11:0:operator:/kaixin:/sbin/nologin
# sed '/list222/{n;s/listen/wokao/;}' 1.txt #n命令,是下一行的意思。这句话的意思是如果list222被匹配到了,然后就n;跳转到此行的下一行,将listen替换为wokao。
list listen321 list123
listn listen111 list123
listen list222 listen123
wokao listen333 listen123
2.7 文件的读入和写出
# sed -i "/list/r /etc/passwd" 1.txt #将/etc/passwd的内容写入到1.txt文件中list关键字所在的行的下面。
# sed 's/listen/woshinige/gw 2.txt' 1.txt #这是将1.txt文件中所有包括listen的更改为woshinige,并将更改的的内容的行插入到新的文件2.txt中
# sed 's/listen1/woshinige/gw 2.txt' 1.txt #我们来验证一下,将listen1的更改为woshinige
# cat 2.txt #经查看只是将更改的内容替换过来了而已。
listn woshinige11 list123
listen list222 woshinige23
listen listen333 woshinige23
2.8 编写sed脚本
# cat 1.txt #先查看一下我们测试文件的内容
list listen321 list123
listn listen111 list123
listen list222 listen123
listen listen333 listen123
# cat 1sed.sh #查看一下我们简单小脚本的内容,就是把''里面的内容写到脚本里面,多个操作就写多行
#!/bin/bash
s/listen/wokaka/g
s/list/zoi|qi/g
# sed -f 1sed.sh 1.txt #格式为:sed -f 脚本文件 要被匹配的文件
zoi|qi wokaka321 zoi|qi123
zoi|qin wokaka111 zoi|qi123
wokaka zoi|qi222 wokaka123
wokaka wokaka333 wokaka123
# nl 1.txt |sed -f 1sed.sh #混合使用也可以
1 zoi|qi wokaka321 zoi|qi123
2 zoi|qin wokaka111 zoi|qi123
3 wokaka zoi|qi222 wokaka123
4 wokaka wokaka333 wokaka123
博文来自:www.51niux.com
三、sed的一些高级用法
上面记录的内容,进行一般的shell脚本工作已经可以了,但是要玩一些6的操作,还是要掌握一点高级的用法。
首先我们要了解sed的工作原理:
sed是一个流文本处理工具,从文件到文件尾读取,一次只读取一行,并完成一系列操作才继续读取下一行。sed维护两个数据缓存区,一个是pattern space(模式空间),一个是hold space(保持空间)。它们初始都为空。模式空间是活跃缓冲区,每一次循环都会清空再存入下一行内容。保持空间是一个辅助的空间,不会在完成一个循环后清空,会一直保持,它的内容来自使用h,H,g,G命令得来。
高级命令:所谓的高级命令通常指模式空间 <---> 保持空间 行的交互,如何交换、覆盖、追加。除非使用特殊的命令,如”D”,否则pattern space会在两个循环之间被清空。而hold space则会保持不变,hold space的内容可以使用‘h’, ‘H’, ‘x’, ‘g’, ‘G’的命令来操作。
上面已经对高级命令做了解释,这里用一些实际例子来进行一下解释记录:
示例1(n读取下一行覆盖模式空间中的行):
# seq 11|sed 'n;d'
1
3
5
7
9
11
# seq 12|sed 'n;d'
1
3
5
7
9
11
#上面seq 11 和seq 12的输出结果是一致的,让我们捋一下。首先读取了第一行1到了模式空间里面,然后n是读取下一行的命令,1的下一行也就是2.默认动作是先输出模式空间中的行,再覆盖读取下一行,再指定d命令。所以就是1进入模式空间输出了,然后就是n命令把2读取到了模式空间,然后就执行了d是删除模式空间的内容(d命令在运行后直接执行下一个循环,所以它并不会执行之后的命令和打印模式空间),又将模式空间里面的2删除了,这样2就不输出了。然后3不是下一行,又相当于了第一行,4又相当于了下一行,以此类推。到了11读取进模式空间,运行命令n,不过读取不到下一行了,因为读取不到,所以sed退出。
示例2 (N读取下一行并追加到模拟空间中现有行后面来创建多行空间,新行和原有的行以\n分隔。sed将其看做一行处理。)
# seq 9|sed '{N;s/\n/\t/}' #跟# seq 9|sed '{N;s/\n/\t/g;p;d}'效果是一致的。通过这个例子可以比较清楚的了解这N的用法,先是1第一行放到了模式空间里面去,然后执行N命令,将1的下一行追加到了模式空间而不是覆盖哦。现在模式空间里面就是1\n2,然后紧跟着我们就将\n换行符改成了\t水平制表符,然后输出后,然后就往下读取3(系统读入时总是覆盖原有内容)以此类推,继续循环就是现在看到的效果。
1 2
3 4
5 6
7 8
9
# seq 5|sed 'N;a---' #或者用这个例子查看一下,在执行完N之后,在模式空间的尾部附加一行---,所以模式空间的内容为“1\n2\n----”而不是“1\n---\n2\n---”
1
2
---
3
4
---
5
下面我们通过下面一个删除的例子,再加深一下这个N的使用:
# seq 11|sed 'N;d'
11
# seq 12|sed 'N;d'
# seq 9|sed 'N;d'
9
示例3 (n,N和d,D(D会删除模式空间内第一行,并且如果模式空间内容不为空,它会循环执行前面命令。直到为空才会执行下一循环)经常一起使用,这里展示一下D和d的不同)
# seq 6|sed '{N;d}'
# seq 6|sed '{N;D}' #从这里可以看到D跟上面的d在最后的结果是有不同的。
6
示例4 (-n(把模式空间中的内容关闭显示也就是不输出sed的处理结果)和-p(打印匹配到的所有输出)和-P(输出多行模式空间的第一部分)的在这里的作用):
# seq 5|sed -n '{N}' #加-n 就不显示了,但是单加-n没什么意思,一般都是跟-p在一起使用的。
# seq 5|sed -n '{N;p}' #因为到5,下面就么有了所以N的执行过程到4就算结束了,所以这样一混合使用显示的就是1\n2 ,3\n4
1
2
3
4
# seq 5|sed -n '{N;P}' #而P呢,是显示多行的第一行部分,所以就是显示的1,3
1
3
示例5 (-x 交换模式空间与保持空间内的内容)
# seq 4|sed '/3/{x;}' #这里加了过滤条件,到了3才会执行我们的x命令,因为到了3模式空间里面的内容是3,而保持空间里面是一个空内容,所以两者替换,我们模式空间里面的内容就成了空,所以结果是1,2,空,4 1 2 4 # seq 4|sed -n '/3/{x;p}' #这下面是一个空行,是为了再跟示例4对应一下,只是将sed替换的部分显示了出来,因为到了3,就让模式空间与保持空间进行了对调,而保持空间初始是空,所以这里替换部分就是一个空行。
# seq 5|sed '/3/!{x;}' #这里就是当匹配条件不为3的时候执行后面的x命令,让模式空间与保持空间进行对换。
1
3
2
4
第一行:模式空间的1与保持空间的空白行对换,所以第一行打印的是1,然后现在保持空间里面是1
第二行:模式空间的2与保持空间的1对调,所以第二行打印的是1,然后现在保持空间里面是2
第三行:模式空间为3,触发了我们当匹配条件是3的时候不进行x命令操作,所以输出的是3,保持空间里面还是2
第四行:模式空间的4与保持空间的2对调,所以第四行打印的是2,然后现在保持空间里面是4
第五行:模式空间的5与保持空间的4对调,所以第五行打印的是4,然后保持空间里面的是5,不过已经没有下一行了,也就没有下一次的对换操作了,所以5也就没有输出
示例6 (-h 复制模式空间内容到保持空间以覆盖的形式 和 -H 复制模式空间内容到保持空间以追加的形式)
# seq 4|sed 'x;h' #输出结果为四个空行,以为h是覆盖形式吗,先是把保持空间的空行对换过来,再执行h将空行覆盖回去,每次都如此,所以输出了四个空行。
# seq 4|sed 'x;H' #输出结果是空行,1空行,21空行,321空行
1
2
1
3
2
1
第一步:模式空间的1与保持空间的空行对调,所以现在,模式空间里面是0,保持空间里面是1,然后复制模式空间里面的空行追加到1下面,所以保持空间最后是1空行,模式空间里面是0并输出。
第二步:模式空间的2与保持空间的1空行对调,所以现在,模式空间里面是1空行,保持空间里面是2,然后复制模式空间里面的1空行追加到2下面,所以保持空间最后是21空行,模式空间里面是1空行并输出。
第三步:模式空间的3与保持空间的21空行对调,所以现在,模式空间里面是21空行,保持空间里面是3,然后复制模式空间里面的21空行追加到3下面,所以保持空间最后是321空行,模式空间里面是21空行并输出。
第四步:模式空间的4与保持空间的321空行对调,所以现在,模式空间里面是321空行,保持空间里面是4,然后复制模式空间里面的321空行追加到4下面,所以保持空间最后是4321空行,模式空间里面是321空行并输出。
示例7 (g 把暂存缓冲区也就是保持空间里的内容复制到模式空间,覆盖原有的内容 和 G 把暂存缓冲区也就是保持空间的内容追加到模式空间里,追加在原有内容的后面)
# seq 4|sed '/4/!{g;}' #下面是1-3行输出的是空行,第四行为4
4
# seq 4|sed '/4/!{G;}' #下面第一部分是1空行,第二部分是2空行,第三部分是3空行,第四部分是4
1
2
3
4
#这里没多少好解释的,示例6已经解释的很详细了。G和H正好是相反。H是追加模式空间内容到保持空间,G是追加保持空间的内容到模式空间。
# seq 4|sed '{1!H;$G}' #第一行不执行H,也就是第一行不执行从模式空间复制内容追加到保持空间的操作,二三四行执行H。尾行还要执行G操作。
1
2
3
4
2
3
4
第一步:第一行不执行H,所以现在模式空间是1,保持空间是空行,所以模式空间的1输出
第二步:第二行执行H,所以现在模式空间是2,复制模式空间的2追加到保持空间,所以现在保持空间是空行2,模式空间是2并输出
第三步:第三行执行H,所以现在模式空间是3,复制模式空间的3追加到保持空间,所以现在保持空间是空行23,模式空间是3并输出
第四步:第四行执行H,所以现在模式空间是4,复制模式空间的4追加到保持空间,所以现在保持空间是空行234,尾行又执行G,所以又将保持空间的空行234追加到模式空间4的尾部,所以最后模式空间是4空行234并输出。
示例8 (混合使用)
# cat 3.txt #这是要操作的文本
A
a
B
b
C
c
D
d
# sed -n '{n;p}' 3.txt #显示偶数行
a
b
c
d
# sed -n '{N;P}' 3.txt #显示奇数行
A
B
C
D
# sed -n '{h;n;p;g;p}' 3.txt #这个就是先把a覆盖到保持空间,后执行n命令将下一行放到模式空间空间,p将模式空间里面的内容打印出来也就是第二行的a,然后执行g命令再将保持空间里面的A覆盖会模式空间,再执行p打印。
a
A
b
B
c
C
d
D
# sed -ne 'H;${x;s/\n/ /g;p}' 3.txt #行列转换
A a B b C c D d
# seq 10|sed -ne 'H;${x;s/\n/+/g;s/^+//;p}' #s/^+//是将1前面的+号去掉
1+2+3+4+5+6+7+8+9+10
# seq 10|sed -ne 'H;${x;s/\n/+/g;s/^+//;p}'|bc #直接就可以用bc进行数字间的求和了
55
示例9(加-e来进行操作)
# cat test.txt #这是实验文本,奇数行是用户,偶数行是邮箱
xiaoming
@51niux.com
shauiqi
@51niux.com
shuaiguo
@51niux.com
ddd51niux
@qq.com
如果我们只想取出@51niux.com的用户都是谁呢?
# sed -n -e '/@51niux.com/!h' -e '/@51niux.com/{x;p}' test.txt #第一段的意思是包含有@51niux.com的行不覆盖到保持空间去,那就一行一行来白。第一行是xiaoming,不在我们的匹配范围,所以覆盖到保持空间去,现在保持空间就是xiaoming,因为我们开头用了-n,所以没有p是不输出的。所以目前第一行是木有输出的。那往下走到了第二行,然后我们匹配条件有一个如果是@51niux.com的行,跟保持空间对换并且执行打印操作,那么保持空间的xiaoming就又换回到了模式空间,并执行替换打印操作,所以第一行的xiaoming是输出的,而第二行的@51niux.com是不打印的。以此类推,到了第7行,又丢到保持空间了,第八行一看不是@51niux.com,所以什么对调啊打印啊都没有执行,ddd51niux就被关在了保持空间没有打印出来,@qq.com是进入到了模式空间,但是没有p命令啊,页面是不显示的。
xiaoming
shauiqi
shuaiguo
如果我们想取出qq的用户并让它组成邮箱的形式呢?
# sed -n -e '/@qq.com/!h' -e '/@qq.com/{H;x;s/\n//p}' test.txt #第一段就是不是@qq.com的行就执行h,然后每一行都存在了模式空间然后覆盖到保持空间去,然后到了第七行依旧,所以保持空间里面现在是ddd51niux,然后第八行匹配到了,就执行将@qq.com追加到了保持空间,就成了ddd51niux.com\n@qq.com,然后x对调,然后模式空间就变成了ddd51niux\n@qq.com,然后我们将\n替换掉,再打印,就变成了我们想要的结果。
ddd51niux@qq.com
好了写到这里,基本可以写一些负载的sed脚本了,就是根据文本的规则多琢磨琢磨规则,怎么去写这个逻辑。
博文来自:www.51niux.com
四、sed的循环标签
编程语言很大的特点是循环语句实现重复执行,sed中没有for\while\until语句,而是通过定义标签,调用标签来实现循环这种功能的。标签名调用有b调用,t调用,T调用。
一般格式为:
sed -n ':标签名 范围1 命令1;/模式/b 标签名' filename
示例1:
# sed -n ' s/xiaoming/Xiaoming/;p' test.txt #因为我们没有加g,所以只替换第一行
Xiaoming xiaoming
# sed -n ':again s/xiaoming/Xiaoming/;/xiaoming/b again;p' test.txt #我们用标签的形式也可以实现g的效果。
Xiaoming Xiaoming
示例2:
# cat 5.txt #这是实验文本,如何让文本中的空行只保留一行空行
aaa
123
#1a
ddd
eee
# sed -rn 'h;n;:a;H;n;$!ba;g;s/(\n){2,}/\n\n/g;p' 5.txt
aaa
123
#1a
ddd
eee