一、sed概述

文件:本质是数据流,被标记的一断硬盘上的存储空间,存储在数据块中的字节流,文件名可以引用这段数据流

  sed是一种非交互式的流编辑器,通过多种转换修改流经它的文本

  sed会一次处理一行内容;处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。默认情况下文件内容并没有改变(可以添加-i选项修改原文件)

   另外需要注意的是,和grep不同,sed不论是否找到指定的模式,它的退出状态都是0(Linux中通常代表真)。只有存在语法错误时,sed的退出状态才不是0

sed的处理流程,简化后是这样的:

1、读取文本第一行内容到模式空间;

2、如果不符合地址定界,则忽略后续的命令,并把模式空间的内容送往屏幕,继续读取下一行;

3、如果符合地址定界,则执行后续的命令,并把模式空间的内容送往屏幕继续读取下一行,一直重复2,3步骤直到全部行读取完毕

注意:不管是否符合地址定界都会进入模式空间,只要经过编辑命令后还在模式空间的行都默认会输出到屏幕


二、sed的使用

   sed [OPTIONS] "SCRIPTS" FILENANE...

常用用法:

   sed [OPTIONS] 'addr1[,add2]编辑命令' FILENAME...

   sed [OPTIONS] 'addr1[,addr2]s/查找条件/替换文本/修饰符' FILENAME...

               #如果不定界默认匹配全文

选项:

    -n静默模式,不输出模式空间中的内容,但会输出编辑命令处理后的内容

    -r:支持扩展的正则表达式

    -f:/PATH/TO/sed_script_file:指定sed的脚本文件,文件中的脚本不需要引号

    -e: 用法: -e‘SCRIPT’-e 'SCRIPT /FILENAME   指定多个编辑指令,“;”也可

    -i:把操作保存原文件    

编辑命令:

     d:删除    删除命令会将模式空间中的内容全部删除,并且导致后续的编辑命令不会执行并且读入新行,因为当前模式空间的内容已经为空。

     p:打印       定界查找到的行,打印出来(和默认输出模式空间的行就打印2次了)

     a [\]string    在某行之后插入行,添加多行时,除最后一行外,string中可以\n表示换行,这样来实现添加多行;字符串前面\可以不写,但建议写,增加可读性

     c [\]string    替换某行,.....

     i [\]string    在某行之前插入行,......

     r /FILENAME    在指定行号插入某文件的内容

     w /FILENAME    将符合条件的所有行保存至指定的文件中,覆盖添加

            =    显示符合条件的行的行号,显示在该行的前一行行首

            l   (是小写字母l不是数字1)命令类似p命令,不过会显示控制字符

          s///    和vim中替换的用法一样:查找条件可以使用模式,但要替换为的内容不可以,但可以使用后向引用\1,\2,&,也可以使用“@”或“#”替换“/”,

    y/SET1/SET2/    转换字符;将SET1中出现的字符替换成SET2中对应位置的字符  

           n    将下一行的内容提前读入,并且将之前读入的行(在模式空间中的行,没有被编辑命令编辑)输出到屏幕,然后后续的命令会应用到新读入的行上。

                         

地址定界:

      startline,endline    例:1,3  第一行到第三行

      /pat1/,/pat2/       被第一个模式匹配到的行开始到第二个模式匹配行结束

      /partern/          被模式匹配到的行

      startline,/parttern/  可以混合使用,但/partten/,endline不可以


地址定界需要注意:

1、对于像“addr1,addr2”这种形式的地址匹配,如果addr1匹配,则匹配成功,“开关”打开,在该行上执行命令,此时不管addr2是否匹配,即使addr2在addr1这一行之前;

   接下来读入下一行,看addr2是否匹配,如果addr2在addr1之前,则不匹配,不执行命令,关闭“开关”;如果addr2在addr1之后,则一直处理到匹配为止,换句话说,如果addr2一直不匹配,则开关一直不关闭,因此会持续执行命令到最后一行。


2、对于0,/partter/这种形式默认第一个地址是匹配的,然后直到第一个被/parttern/匹配到的行为止。因此上述情况,只要看每一行是否匹配第二个地址就可以了addr2,因为第一行是匹配的,

   如果/partter/匹配不到则addr2(没有/partter/匹配的行),则匹配后面的所有行

   但是这种地址对表示有一个限制,即addr2只能使用/parttern/形式,如果使用行号,就会出错,不信可以试试。


3、地址对addr1, addr2的匹配方式 ,从匹配addr1的那行开始,打开匹配开关,直到匹配addr2的那行结束,关闭匹配开关,之后的行会忽略这个地址对,不再做匹配。

  如果addr1是number,即行号,如果新读入行的行号大于addr1,则匹配;小于addr1,则不匹配。

   如果addr2是行号,如果新读入行的行号小于addr2,则匹配,继续往下读;大于addr2,则不匹配,关闭匹配开关。


4、如果地址或者地址对之后有一个"!",表明对匹配的行不执行后面的命令,刚好相反。


再补充总结s替换时所用到的编辑命令:

通用格式为s/oldstring/newstring/flag


oldstring   要被替换的内容,支持正则表达式

newstring   新内容,不支持正则,但支持old中分组的后向引用,还有下面几个特殊用法\1,\2... 对old中\(\)分组内容进行引用,&引用整个old中的值

下面的不常用:

         \U 把\U后面的字母全部转换为大写

         \u 只把\u后面的第一个字母大写,其他不变

         \L 把\L后面的字母全转换为小写

         \l 只把\l后的第一个字母小写,其他不变

         \E 代表终止,后面的不再处理

[root@Note3 src]# sed -n 's/^a/\Usb/p' passwd     #所以有毛用,不用记这些命令
SBroot:x:0:0:root,,,a:/root:/bin/bash
SBda:x:3:4:adm:/var/adm:/sbin/nologin
SB:x:3:4:adm:/var/adm:/sbin/nologin

flag(修饰符)  实现增强功能模块的,默认情况只会替换每行首个匹配的内容

            g  全局替换,行中所有被匹配的都会被替换

            i  不区分字符大小写

            p  打印模式空间的内容,请注意通常与sed命令选项-n配合使用只打印处理行

            w  file 将模式空间行打印写到file中

             每行中被模式匹配的第N个内容被替换

 

l命令类似p命令,不过会显示控制字符,这个命令和vim的list命令相似,例如:

[root@BAIYU_110 tmp]# echo "column1 column2 column3" | sed 'l'   
column1 column2 column3$
column1 column2 column3
[root@BAIYU_110 tmp]# echo "column1 column2 column3" | sed '='   
1
column1 column2 column3
[root@BAIYU_110 tmp]# echo "column1 column2 column3" | sed 'p'
column1 column2 column3
column1 column2 column3
[root@BAIYU_110 tmp]# echo "column1 column2 column3" | sed 'l'
column1 column2 column3$
column1 column2 column3

如果flags中明确指定替换第n次的匹配,例如N=2:

[root@Note3 src]# echo "column1 column2 column3 column4" | sed 's/ /;/2'
column1 column2;column3 column4

    当替换命令的pattern与地址部分是一样的时候,比如/regexp/s/regexp/replacement/可以省略替换命令中的pattern部分,这在单个编辑命令的情况下没多大用处,但是在组合命令的场景下还是能省不少功夫的

例如:把文中aa都加上s然后在有aa的行的行首再加上+

[root@Note3 src]# cat aa.txt
aa  bb   cc
da  adsa   casdfa
dasd  dasf  aa
bb   cc   nihao

[root@Note3 src]# sed '/aa/ s//aas/g;s/^/+/' aa.txt 
+aas  bb   cc
+da  adsa   casdfa
+dasd  dasf  aas
+bb   cc   nihao
[root@Note3 src]# sed '/aa/ {s//aas/g;s/^/+/'} aa.txt 
+aas  bb   cc
da  adsa   casdfa
+dasd  dasf  aas
bb   cc   nihao
[root@Note3 src]# sed -e '/aa/ s//aas/g' -e '/aa/ s/^/+/' aa.txt 
+aas  bb   cc
da  adsa   casdfa
+dasd  dasf  aas
bb   cc   nihao

转换命令: y

语法:

[address]y/SET1/SET2/

  它的作用是在匹配的行上,将SET1中出现的字符替换成SET2中对应位置的字符

例如1,3y/abc/xyz/会将1到3行中出现的a替换成x,b替换成y,c替换成z。是不是觉得这个功能很熟悉,其实这一点和tr命令是一样的。可以通过y命令将小写字符替换成大写字符,不过命令比较长:

$ echo "hello, world" | sed 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'HELLO, WORLD

使用tr命令来转换成大写:

$ echo "hello, world" | tr a-z A-Z                                                    
HELLO, WORLD

取下一行命令: n

语法:

[address]n

  n命令为将下一行的内容提前读入,并且将之前读入的行(在模式空间中的行)输出到屏幕,然后后续的命令会应用到新读入的行上。因此n命令也会同d命令一样改变sed的控制流程。

书中给出了一个例子来介绍n的用法,假设有这么一个文本:

$ cat text
.H1 "On Egypt"

Napoleon, pointing to the Pyramids, said to his troops:
"Soldiers, forty centuries have their eyes upon you."

现在要将.H1后面的空行删除:

$ sed '/.H1/{n;/^$/d}' text
.H1"On Egypt"
Napoleon, pointing to the Pyramids, said to his troops:
"Soldiers, forty centuries have their eyes upon you."

退出命令: q

语法

[line-address]q    #不能使用addr1,addr2,只能使用某行,读取到该行就退出

当sed读取到匹配的行之后即退出,不会再读入新的行,并且将当前模式空间的内容输出到屏幕。

例如打印前3行内容:

[root@BAIYU_110 tmp]# cat test
aa  bb   cc
da  adsa   casdfa
dasd  dasf  aa
bb   cc   nihao

[root@BAIYU_110 tmp]# sed '3q' test            
aa  bb   cc
da  adsa   casdfa
dasd  dasf  aa
[root@BAIYU_110 tmp]# sed -n '3p' test 
dasd  dasf  aa
[root@BAIYU_110 tmp]# sed -n '1,3p' test
aa  bb   cc
da  adsa   casdfa
dasd  dasf  aa

   打印前3行也可以用p命令:但是对于大文件来说,前者比后者效率更高,因为前者读取到第N行之后就退出了。后者虽然打印了前N行,但是后续的行还是要继续读入,只不会不作处理。

到此为止,sed基础命令的部分就介绍完了。


示例(需要注意的细节)

1、让我们来看一个非常简单的例子,将一段文本中的pig替换成cow,并且将cow替换成horse:

[root@BAIYU_110 tmp]# echo "pig a cow"|sed 's/pig/cow/g;s/cow/hores/g' 
hores a hores

   初看起来好像没有问题,但是实际上是错误的,原因是第一个替换命令将所有的pig都替换成cow,紧接着的替换命令是基于前一个结果处理的,将所有的cow都替换成horse,造成的结果是全部的pig/cow都被替换成了horse,这样违背了我们的初衷。

在这种情况下,只需要调换下两个编辑命令的顺序:

       # echo "ping a cow"|sed 's/cow/hoers/;s/ping/cow/'


2、分析下面2个命令的执行结果,为什么会是这样?

seq 6|sed '1,2d'|sed '1,2d'

seq 6|sed -e '1,2d' -e '1,2d'

第一眼觉得结果都是5,6

想了下第一个结果是5,6,第2个结果是3,4,5,6

不猜了,在电脑上分别执行一下这2个命令,出人意料

解释

   首先第一行被读入,遇到第一组expression -> 1, 2d,第一行匹配成功(打开匹配开关),执行d命令,d命令清空模式空间的内容,因此不会再执行接下来的命令。

继续从标准输入读入第二行,同第一行,读入第三行,第一组expression匹配失败(因为3>2),因此试着执行第二组expersson->1,2d,因为3>1,打开匹配开关,执行d。(这里是关键)

读入第四行,执行第二组expersson->1,2d,因为4>2,匹配失败,关闭匹配开关,同时也不执行d。

因此,最后第1 2 3行被删除。


练习:

He like his liker

He like his lover

she love her liker

she love her lover

1、删除以上内容当中包含“l..e”前后一致的行

   # sed "/\(l..e\).\+\1/d"

2、将文件中“l..e”前后一致的行中,最后一个"1..e"词首的l换成大写L

   # sed "/\(l..e\).\+\1/ s/l\(..er\)/L\1/"

   # sed "/\(l..e\).\+\1/ s/l\(..er\)/L\1/p"      #替换时修饰符的位置还可以用p和w,打印或保存更改后的内容

   # sed "/\(l..e\).\+\1/ s/l\(..er\)/L\1/w /tmp/22222"

3、替换/etc/inittab文件中"id:3:initdefault:"-行中的数字为5

   # sed "s/\(id\):[0-9]:\(initdefault:\)/\1:5:\2/p"

4、去取一个文件路径的目录名称,如/etc/sysconfig/network其目录为/etc/sysconfig,功能同dirname

 # echo "/etc/sysconfig/network"|sed 's@/[^/]\+/\?$@@'  #不知道为什么这里用""号就会报错,以后记得尽量用'号了,单引号更通用(当然‘’内不使用变量)