一、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 每行中被模式匹配的第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@/[^/]\+/\?$@@' #不知道为什么这里用""号就会报错,以后记得尽量用'号了,单引号更通用(当然‘’内不使用变量)