sed的使用

Shell 2019-03-31 6744 字 1151 浏览 点赞

概念

一种流编辑器(stream editor),对标准输出或文件逐行进行处理。

语法格式(参数 + 动作):

  • stdout | sed [option] 'function'
  • sed [option] 'function' file

鸟哥建议,function 总是用引号括起来。

option

选项含义
-n静默模式,只打印模式匹配行(sed 默认打印原文件的所有数据)
-e默认选项,直接在命令行进行sed编辑(如果存在多个动作,-e就不能省略)
-f将文件中的内容作为编辑动作,用法:-f file
-r支持扩展正则表达式,如果没有 -r,则只支持基础正则
-i直接修改文件内容

function

编辑命令含义
p打印,通常与 sed -n 一起使用
a追加
c替换
i插入
d删除
s替换,与正则搭配使用:'1,20s/old/new/g'
r读取外部文件,行后追加(用法:'r filename'
w将匹配的内容,写入到外部文件(用法:'w filename'

事实上,sed 玩儿得 6 不 6 全看对正则是否熟悉。这是因为,除了 s 外,其他的动作也可以搭配正则使用,就连行号都可以用正则代替。比如有以下文本,我希望输出所有数字开头的行内容。

1 Hello World
Hello World
2 Hello world
Hello World
Hello World

那么:cat txt | sed -n '/^[1-9]/p' ,其效果等同于:cat txt | sed -n '1p;3p'。显然,在前提条件下,第一种处理方式要比第二种灵活、强大许多。

s///g 替换语句是对行内操作,c 则是以为最小处理单位做处理。s 的前面可以放参数,代表需要处理哪行,参数可以是正则(要求:将数字开头的行中的 Hello 用 # 替换):

root@hui:shell-study$ cat txt | sed '/^[1-9]/s/Hello/#/g'
1 # World
Hello World
2 # world
Hello World
Hello World

g 表示需要处理行内所有符合条件的关键字。现实中可能不需要全部处理,有时只需要处理行内第二个符合条件关键字,那就不用 g,直接用数字(要求:将数字开头的行中的第二个 l ,用 # 替换):

root@hui:shell-study$ cat txt | sed '/^[1-9]/s/l/#/2'
1 Hel#o World
Hello World
2 Hel#o world
Hello World
Hello World

正则补充

由于正则相关的内容很多,所以在此并非要补充正则的知识,而是补充一些我的知识体系里需要的那部分。

圆括号匹配

现在我需要将 /etc/passwd 文件中每一行的第一个冒号之前的内容用符号 “【】” 括起来,那么:

$ nl passwd | sed -rn 's/([a-z-]+):/【\1】:/1;p'
     1    【root】:x:0:0:root:/root:/bin/bash
     2    【bin】:x:1:1:bin:/bin:/sbin/nologin
     3    【daemon】:x:2:2:daemon:/sbin:/sbin/nologin
     4    【adm】:x:3:4:adm:/var/adm:/sbin/nologin
     5    【lp】:x:4:7:lp:/var/spool/lpd:/sbin/nologin
     ...

也就是说,用圆括号括起来的部分,可以在后面用 1 代表匹配出来的内容;如果前面有两个圆括号,则第二个括号中匹配出来的内容可用 2 代替;依次类推。

占位符 &

现在我想标记出所有的 rootbin ,则可以:

root@hui:shell-study$ nl passwd | sed -r 's/root|bin/【&】/g'
     1    【root】:x:0:0:【root】:/【root】:/【bin】/bash
     2    【bin】:x:1:1:【bin】:/【bin】:/s【bin】/nologin
     3    daemon:x:2:2:daemon:/s【bin】:/s【bin】/nologin
     4    adm:x:3:4:adm:/var/adm:/s【bin】/nologin
     5    lp:x:4:7:lp:/var/spool/lpd:/s【bin】/nologin
     ...

也就是说,占位符 & 用来表示前面匹配到的内容本身。

美元符 $

美元符 $ 表示“末尾”,如果我想在每一行的末尾添加感叹号,可以:nl passwd | sed 's/$/!/g' ;如果我想在整个文本的末尾添加 “over” ,可以:nl passwd | sed '$a over'

相对应的, ^ 表示开头,用法与 $ 相似。

练习

下面以 /etc/passwd 文件作为演示文件。

1.输出文件中2到5行的内容

root@hui:shell-study$ nl passwd | sed -n '2,5p'
    2    bin:x:1:1:bin:/bin:/sbin/nologin
    3    daemon:x:2:2:daemon:/sbin:/sbin/nologin
    4    adm:x:3:4:adm:/var/adm:/sbin/nologin
    5    lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

2.在第 2 行后面追加 “hello world” ,同时只输出前5行的内容

root@hui:shell-study$ nl passwd | sed -n -e '2a hello world' -e '1,5p'
     1    root:x:0:0:root:/root:/bin/bash
     2    bin:x:1:1:bin:/bin:/sbin/nologin
hello world
     3    daemon:x:2:2:daemon:/sbin:/sbin/nologin
     4    adm:x:3:4:adm:/var/adm:/sbin/nologin
     5    lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

此时有两个操作,因此 -e 选项不可省略。

3.替换第 2 到 5 行的内容,同时只输出前 6 行的内容

root@hui:shell-study$ nl passwd | sed -n -e '2,5c No 2-5 number' -e '1,6p'
     1    root:x:0:0:root:/root:/bin/bash
No 2-5 number
     6    sync:x:5:0:sync:/sbin:/bin/sync

动作 i(插入) 与 动作 d(删除) 的用法与 a、c 一样。


4.将 1 到 3 行中的冒号 “:” 用空格替换,并输出前 4 行

root@hui:shell-study$ nl passwd | sed -n -e '1,3s/:/ /g' -e '1,4p'
     1    root x 0 0 root /root /bin/bash
     2    bin x 1 1 bin /bin /sbin/nologin
     3    daemon x 2 2 daemon /sbin /sbin/nologin
     4    adm:x:3:4:adm:/var/adm:/sbin/nologin

此时,多个动作可以通过 “;” 连接起来,合成一句话:

root@hui:shell-study$ nl passwd | sed -n '1,3s/:/ /g;1,4p'
     1    root x 0 0 root /root /bin/bash
     2    bin x 1 1 bin /bin /sbin/nologin
     3    daemon x 2 2 daemon /sbin /sbin/nologin
     4    adm:x:3:4:adm:/var/adm:/sbin/nologin

最后来一个综合性的例子。

5.打印所有网卡的 ipv4

因我的环境没有安装 ifconfig ,所以这里用 ip addr 来代替,因此输出为:

root@hui:shell-study$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:d5:d9:d4 brd ff:ff:ff:ff:ff:ff
    inet 192.168.20.131/24 brd 192.168.20.255 scope global noprefixroute dynamic ens33
       valid_lft 1765sec preferred_lft 1765sec
    inet6 fe80::bbfb:8164:68e9:75c2/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever

显然,此时要获取 ipv4 的值,那么得先挑出 inet 这一行来:

root@hui:shell-study$ ip addr | grep inet
    inet 127.0.0.1/8 scope host lo
    inet6 ::1/128 scope host 
    inet 192.168.20.131/24 brd 192.168.20.255 scope global noprefixroute dynamic ens33
    inet6 fe80::bbfb:8164:68e9:75c2/64 scope link noprefixroute 

此时有 ipv6 的内容,尽管可以通过 grep -v 干掉它,但这里我想用 sed 实现目的。于是:

root@hui:shell-study$ ip addr | grep inet | sed 's/inet6.*//g'
    inet 127.0.0.1/8 scope host lo
    
    inet 192.168.20.131/24 brd 192.168.20.255 scope global noprefixroute dynamic ens33
    

接下来需要干掉第二个ipv4 brd 后面的内容:

root@hui:shell-study$ ip addr | grep inet | sed 's/inet6.*//g' | sed 's/brd.*//g'
    inet 127.0.0.1/8 scope host lo
    
    inet 192.168.20.131/24 
    

接下来我们干掉有所得字母和空格就好了:

root@hui:shell-study$ ip addr | grep inet | sed 's/inet6.*//g' | sed 's/brd.*//g' | sed -e 's/[a-z]*//g' -e 's/\s//g'
127.0.0.1/8

192.168.20.131/24

此时发现两个 ipv4 下面都有空行,空行也要干掉(^$ 表示空行),因此:

root@hui:shell-study$ ip addr | grep inet | sed 's/inet6.*//g' | sed 's/brd.*//g' | sed -e 's/[a-z]*//g' -e 's/\s//g' | sed '/^$/d'
127.0.0.1/8
192.168.20.131/24

最后得到了我们想要得的结果,但是观察发现,我们使用了很多 s 操作,从上面也可以看出,这些动作可以通过管道连接起来,也可以用 -e 组合起来。事实上,我们也可以使用扩展正则表达式来合并它们:

root@hui:shell-study$ ip addr | grep inet | sed -r 's/inet6.*|brd.*|[a-z]*|\s//g' | sed '/^$/d'
127.0.0.1/8
192.168.20.131/24

sed 代替 grep 也并非不可以:

root@hui:shell-study$ ip addr | sed -n '/inet/p' | sed -r 's/inet6.*|brd.*|[a-z]*|\s//g' | sed '/^$/d'
127.0.0.1/8
192.168.20.131/24

还可以:

ip addr | sed -n '/inet/p' | sed -r '/inet6/d;s/brd.*|[a-z]*|\s+//g'

事实上,相关的解法有很多,并不存在标准答案,如何达到目的是看你心情。

感谢



本文由 Guan 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论