语法
sed <全局命令><指定行地址和局部处理命令> <文件>
sed的工作原理
当sed开始运行的时候会在内存里面另开一片空间,这个空间叫做模式空间,这个空间是sed的私人空间,只能由它一个程序使用。那么这片天地有什么作用呢?它的作用就是从硬件把我们编辑的文件整个吸出来放到这个空间内,放到空间之后根据我们指定的地址(就是我们指定编辑的行)找到相对应的行,根据再根据我们指定的命令做处理,这个处理可能是删除或者是输入屏幕等等,等到命令执行完毕之后然后再把整个模式空间的内容倾倒到屏幕上去。
总结:从硬件盘“吸出”文件内容>找到要编辑的行>用处理命令处理找到的这些行>把空间的内容倾倒到屏幕
p和d
说一下p的问题,那么p有什么问题值得我大费周章的说一说呢?是这样的,我们看下面这个例子,这个例子的原意是:“把/etc/fstab文件当中的以#号开头的行打印出来,”但是我们看到结果每个开头是#号的行都打印了两遍!这明显是搞事情啊!如下图所示:
那么这是肿么回事?如果你不理解的话,说明你还没有彻底理解sed的工作原理,好吧,我们再来复习一遍!
第一步:先把文件从硬件读取到内存的模式空间
第二步:然后根据我们指定的地址找到对应的行
第三步:再然后根据我们根据的局部命令去处理些对应的行
第四步:最后把整个模式空间当中的内容倾倒到屏幕上。
我在上面这个例子当中的第三步使用的p这个命令,这个命令的原意是把第二步找到的行打印到屏幕,这个命令就真的就这么做了,把第二步匹配到的开头是#的行都打印到屏幕上,这一步没毛病,但是别忘了,还是第四步呢!!第四步就是把整个模式空间的内容都倾倒到屏幕上,解释到这里你是否有点明白了呢?虽然第三步的局部命令把匹配的行打印到了屏幕,但是第一步从硬件读取的这些文件内容依然还是在内存空间没有被删除,到第四步的时候一下子把模式空间的所有内容都倾倒给屏幕,结果我们看到的就是p打印出来的开头是#号的行+整个/etc/fstab的内容,不知道这么说你明白了吗?如果大家真的真想打印出匹配到的行,不想把模式空间的内容也一块显示出来的话,可以使用-n这个全局命令,这个命令会避免把模式空间当中的内容倾倒至屏幕,只会在屏幕上显示局部命令处理过的行,如下图所示,这样就正常了:
那么大家想一下如果把p命令换成d命令会不会发生这样的事呢?那当然不会了,过程还是一样,到了第三步先执行局部命令时发现并不是p打印至屏幕而是删除,删除了之后然后把模式空间倾倒至屏幕,那么我们看到的就是删除了匹配行的文件内容,如下图:
全局命令参数
- -n默认模式,不把命令空间当中的内容倾倒到屏幕,只显示被命令处理过的内容
- -i修改源文件,很危险的操作,少用为妙。
- -e scripts –e scripts同时执行多个脚本
全局命令参数练习
1、-n只显示被局部命令处理过的内容
[root@zhanghe ~]# sed -n '1p' /etc/passwd #1代表第一行 root:x:0:0:root:/root:/bin/bash [root@zhanghe ~]# sed -n '$p' /etc/passwd #$代表最后一行 zhangjia:x:501:501::/home/zhangjia:/bin/bash #sed要使用单引号 [root@zhanghe ~]# sed -n '1,+2p' /etc/passwd #1,+2p代表1,2,3行 root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin
[root@zhanghe ~]# sed -n '1,3p' /etc/passwd #指的是1至3行,也就是1,2,3行 root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin u /正则表达式/:只有一个正则表达式 [root@zhanghe ~]# sed -n '/^root/p' /etc/passwd #仅处理开头是root的行 root:x:0:0:root:/root:/bin/bash u /正则表达式1/,/正则表达式2/:模式1匹配到的行一直到模式2匹配到的行中间所有的行 [root@zhanghe ~]# sed -n '/^root/,/nologin$/p' /etc/passwd root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin
-n好像仅对连续的行感兴趣,如果我仅要打印某个文件的第一行、第五行、第十二行,一条命令就无能为力了,你如果这样写:sed -n '1、5、12p' /etc/passwd会报错,怎么办呢?只能多条命令连续起来,如下所示:
sed -n '1p' /etc/passwd ; sed -n '5p' /etc/passwd ; sed -n '12p' /etc/passwd
只能这样了吗?有没有更好的办法?其实是有的,可以要样改进:
2、-i修改源文件
sed默认是只修改内存当中的内容,修改过后不会向硬盘写入,如果使用-i选项的话,sed命令就会从内存向硬盘写入了,我们来举个例子:
sed 's@^root@ROOT@' /etc/passwd #这样的话,仅是修改内存中的内容,你去cat原文件会发现原文件并没有任何变化,仅是显示上变化。 sed -i 's@^root@ROOT@' /etc/passwd #这样的话,原文件就会变化了,要小心使用。
3、同时执行多个脚本,-e选项允许在同一行里执行多条命令
sed -e '2,$d' -e 's@ROOT@TTTTT@' /etc/passwd #先将第2行到最后一行给删除了,只留下第一行,然后将第一行的ROOT替换成TTTTT TTTTT:x:0:0:root:/root:/bin/bash
局部处理命令
-d 删除
[root@zhanghe ~]# sed '2,$d' /etc/passwd #删除从第二行到最后一行,只剩下了第一行 root:x:0:0:root:/root:/bin/bash
-a在指定的行后添加,a \string 在指定的行后面添加一个新行,内容为sting
[root@zhanghe ~]# sed '/^root/a \zhanghe\nzhangjia\nzhangwei' /etc/passwd
-i在指定的行前添加
sed '/^root/i \zhanghe\nzhangjia\nzhangwei' /etc/passwd
-r PATH/TO/FILE 在匹配的行后面添加路径中指定的文件内容
sed '/^root/r /tmp/text.txt' /etc/passwd
-w 将地址指定的范围的内容提取出来到另一个文件当中去,不是追求,而不是覆盖
sed -n '/my/w test2.txt' test.txt #把text.txt文件里面包括my的行放置到text.txt里面,注意这里面的顺序
-s/模式/字符/ FILE 把匹配到的字符用字符替换,默认只替换开头第一个
sed 's@^root@ROOT@' /etc/passwd
s/模式/字符/g 全局替换
s/模式/字符/i 查找时不区分大小写
NOTE:p:显示符合条件的行,sed使用p会显示两次,使用-n选项可避免
后向引用
note:在替换当中,只有第一个条件可以使用模式,第二个不可以
[root@zhanghe ~]# cat zh.txt i like on,my,love [root@zhanghe ~]# sed 's#\(l..e\)#\1r#' zh.txt #把l..e替换成l..er i liker on,my,lover [root@zhanghe ~]# sed 's#l\(..e\)#L\1#' zh.txt #仅把l..e的l替换成大写 i Like on,my,Love
利用sed命令把history开始的空白字符给删除了
[root@zabbix ~]# history | sed 's@^[[:space:]]*@@' [root@LAMP ~]# history | sed 's@^[[:space:]]\+@@g' [root@zhanghe ~]# history|sed 's#^[[:space:]]\{3\}##g'
练习
1. 删除/etc/grub.conf文件中行首的空白字符
[root@zhanghe ~]# sed 's@^[[:space:]]@@' /etc/grub.conf
2. 替换/etc/inittab文件当中的id:3:initdefault:一行当中的数字为5
[root@A ~]# sed "s%^id:[0-9]:initdefault:$%id:5:initdefault:%" /etc/inittab [root@China ~]# sed "s@\(id:\)[0-9]\(:initdefault:\)@\15\2@g" /etc/inittab sed "s@\(id:\)[[:digit:]]\(:initdefault:\)@\16\2@" /etc/inittab sed -r -i "s@(id:)[[:digit:]](:initdefault:)@\16\2@" /etc/inittab
3. 删除/etc/inittab文件当中的空白行
[root@zhanghe ~]#[root@China ~]# sed "/^[[:space:]]*$/d" /tmp/grub.conf
4. 删除/etc/inittab文件当中以#开头的行
[root@zhanghe ~]# sed "/^#/d" /etc/inittab
5. 删除某文件中开头的#及后面的空白字符的行,但要求#号后面必须有空白字符
[root@zhanghe ~]# sed "/^#[[:space:]]/d" test.sh
6. 删除某文件中以空白字符后面跟#号的行中开头的空白字符及#
[root@zhanghe ~]# sed "s@^[[:space:]]\+#@@" test.sh
7. 取出一个文件路径的目录名称
[root@China ~]# echo "/etc/sysconfig" | sed 's@[^/]\+$@@' /etc/ [root@China ~]# echo "/etc/sysconfig/" | sed 's@[^/]\+$@@' /etc/sysconfig/ [root@China ~]# echo "/etc/rc.d" | sed -r "s@^(/.*/)[^/]+/?@\1@g" /etc/ [root@China ~]# echo "/etc/rc.d" | sed -r "s@[^/]+/?\$@@g" /etc/
解析:第一回是在线开头的字符至少出现一次,并且还要在词尾给删了,删除之后可不就剩下斜线开头的目录了嘛,但是,如果目录是一个绝对路径呢?就像
echo /zhang/he/ | sed "s@\(\/[[:alnum:]]\+\/\)\([[:alnum:]]\+\/\?\)@\1@" /zhang/ echo /zhang/he/ | sed "s@\(\/[[:alnum:]]\+\/\)\([[:alnum:]]\+\/\?\)@\2@" he/
8,取出一个目录的基名和目录名
[root@China ~]# basename /etc/sysconfig sysconfig [root@China ~]# dirname /etc/sysconfig /etc
9、把/etc/fstab当中空行和开头是空格的、开头是#号都删除掉
sed 's@^#@@' /etc/fstab | sed '/^[[:space:]]*$/d' | sed 's@^[[:space:]]@@'
10、打印奇数或偶数行
sed -n 'p;n' <file> #打印奇数行 sed -n 'n;p' <file> #打印偶数行
11、打印完前三行后,退出sed
[root@zhanghe ~]# sed '3q' /etc/passwd ROOT:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin
12、将文件当中所有的字母转成大写
最好的办法不是使用sed而是使用tr,如果tr命令的话是这样:
tr 'a-z' 'A-Z' < file
13、将文件当中前10的abcde转成为大写
sed '1,10y/abcde/ABCDE/' /etc/passwd
14、将前10行当中的所有小写的s转换成大写的S和将全文所有小写的s转成大写的S
sed '1,10y/s/S/' /etc/passwd sed 's@s@S@g' /etc/passwd