shell 流编辑器 sed

一、sed介绍

Linux中,常使用流编辑器sed 进行文本替换工作。与交互式编辑器(例如vim)不同,sed编辑器以批处理的方式来编辑文件。

  1. 一次从输入中读取一行数据
  2. 根据所提供的编辑器命令匹配数据
  3. 按照命令修改流中的数据
  4. 将新的数据输出到 STDOUT(标准输出,即显示器)

在sed编辑器匹配完一行数据后,它会读取下一行数据并重复这个过程,直到处理完所有数据

二、sed 语法

sed [options] edit_commands [file]
# [ ]中的内容为可选可不选
  • 注意: sedgrep 不同,不管是否找到指定的模式,它的退出状态都是0,只有当命令存在语法错误时,sed的退出状态才是非0

常用选项

选项功能
-n(silent)使用安静(silent)模式。来自stdin的资料一般都会被列出到屏幕,加上-n后,则只有经过sed处理的行才会被列出
-e(expression)允许多点编辑
-f(file)将sed的动作写在一个文本内,-f filename 则可以执行filename内的sed动作。
-r(regexp)支持扩展正则
-i(in place)直接修改源文件

三、sed子命令

普通子命令功能
a追加
c修改
i插入
d删除
w将行写入文件
y将字符转换为另一字符
p打印
r读入字符串到文件
q退出sed
=打印当前行号
s替换

子命令取反

! 取反

flag标记

g 单行全局

p 打印

数字  操作到第几个匹配
高级子命令功能
h拷贝pattern space的内容到holding buffer
H追加pattern space的内容到holding buffer
g获得holding buffer中的内容,并替代当前pattern space中的文本
G获得holding buffer中的内容,并追加到当前pattern space的后面
x交换暂存缓冲区与模式空间的内容
n读取下一个输入行,用下一个命令处理新的行而不是用第一个命令
N追加新行到模式空间
P打印模式空间中的第一行
D删除模式空间的第一行,会导致sed从开头重新执行所有子命令

1.定址

默认sed 对文件中的所有行进行编辑。当然,也可以只指定特定的某些行,或者行范围进行流编辑,这需要用到行寻址。所指定的行地址放在子命令之前

[address]commands

数字定址

sed 编辑器将文本流中的每一行都进行编号,第一行的编号为1 ,后面的按顺序分配0行号,通过指定特定的行号,可以选择编辑特定的行

  • cat -n file-n是为了显示行号

[root@localhost ~]# cat -n test.txt | sed '3 s/bin/BIN/g'  		//将第三行中所有的 bin 替换成 BIN
     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
     6	sync:x:5:0:sync:/sbin:/bin/sync

[root@localhost ~]# cat -n test.txt |sed '2,5 s/bin/BIN/g' 		//将第2到5行中所有的 bin 替换成 BIN
     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
     6	sync:x:5:0:sync:/sbin:/bin/sync
     7	shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
  • $ 在正则中是匹配行尾

[root@localhost ~]# cat -n test.txt |sed '2,$ s/bin/BIN/g'		//将第2行到尾行中所有的 bin 替换成 BIN
     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
     6	sync:x:5:0:sync:/sBIN:/BIN/sync
     7	shutdown:x:6:0:shutdown:/sBIN:/sBIN/shutdown
     8	halt:x:7:0:halt:/sBIN:/sBIN/halt

正则定址

sed 编辑器允许使用正则过滤出命令要作用的行

/pattern/command

必须使用/ 将要指定的 pattern 包起来

sed 会寻找匹配文本模式的行,然后对这些行执行编辑命令

  • sed -n-n是静默输出,只输出经过sed处理的行

[root@localhost ~]# sed -n '/root/s/bin/BIN/p' /etc/passwd	//寻找包含有字符串 root 的行,并且将匹配行中的 bin 替换为 BIN
root:x:0:0:root:/root:/BIN/bash
operator:x:11:0:operator:/root:/sBIN/nologin

[root@localhost ~]# sed -n '/root/,/nologin/ s/bin/BIN/p'  /etc/passwd 		//寻找包含有字符串 root 或 nologin 的行,并且将匹配行中的 bin 替换为 BIN
root:x:0:0:root:/root:/BIN/bash
BIN:x:1:1:bin:/bin:/sbin/nologin
operator:x:11:0:operator:/root:/sBIN/nologin
games:x:12:100:games:/usr/games:/sBIN/nologin

2.子命令使用

1、s 替换

使用s命令来进行文本替换操作


[root@localhost ~]# cat aaa.txt | sed 's/bin/BIN/'
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

/字符为界定符,用于分隔字符串(sed 编辑器允许使用其他字符作为替换命令中的字符串分隔符)

  • 使用#作为字符串分隔符

    
    [root@localhost ~]# cat aaa.txt | sed 's#bin#BIN#'
    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
    

数字指明替换掉第几次匹配到的文本

没有设置这个标记时,默认是替换第一次匹配的文本

  • 将aaa.txt中每行的第2个bin 替换为 BIN

    
    [root@localhost ~]# cat aaa.txt | sed 's/bin/BIN/2'
    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
    

2、g替换所有匹配到的文本

  • 将aaa.txt 中的 bin 全部替换为 BIN

[root@localhost ~]# cat aaa.txt | sed 's/bin/BIN/g'
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

3、p打印与替换命令中指定模式(srcStr)相匹配的行

  • 使用显示行号可以看出含有bin的行被输出了两次
  • 一次是 sed 编辑器自动输出的
  • 另一次则是 p 标记打印出来的匹配行

[root@localhost ~]# cat -n aaa.txt | sed 's/bin/BIN/p'
     1	root:x:0:0:root:/root:/BIN/bash
     1	root:x:0:0:root:/root:/BIN/bash
     2	BIN:x:1:1:bin:/bin:/sbin/nologin
     2	BIN:x:1:1:bin:/bin:/sbin/nologin
     3	daemon:x:2:2:daemon:/sBIN:/sbin/nologin
     3	daemon:x:2:2:daemon:/sBIN:/sbin/nologin
     4	adm:x:3:4:adm:/var/adm:/sBIN/nologin
     4	adm:x:3:4:adm:/var/adm:/sBIN/nologin
     5	lp:x:4:7:lp:/var/spool/lpd:/sroot/nologin
     6	sync:x:5:0:sync:/sBIN:/bin/sync
     6	sync:x:5:0:sync:/sBIN:/bin/sync
  • 将aaa.txt中所有的root 全部都替换成 ROOT,并输出被修改的行

    
    [root@localhost ~]# cat -n aaa.txt | sed 's/root/ROOT/gp'
         1	ROOT:x:0:0:ROOT:/ROOT:/bin/bash
         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:/sROOT/nologin
         5	lp:x:4:7:lp:/var/spool/lpd:/sROOT/nologin
         6	sync:x:5:0:sync:/sbin:/bin/sync
    
    • 如果想只输出被修改的部分可以使用sed -n静默输出

4、d删除

sed 编辑器使用 d 命令来删除文本流中的特定行。使用d命令时,一般需要带上位寻址,以删除指定的行,否则默认会删除所有文本行。


[root@localhost ~]# cat aaa.txt 
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:/sroot/nologin
sync:x:5:0:sync:/sbin:/bin/sync

#使用d删除命令
[root@localhost ~]# cat aaa.txt |sed '/root/d'
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
sync:x:5:0:sync:/sbin:/bin/sync

  • 删除第2到最后一行$

    
    [root@localhost ~]# cat -n aaa.txt 
         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:/sroot/nologin
         6	sync:x:5:0:sync:/sbin:/bin/sync
    
    #使用命令后
    [root@localhost ~]# cat -n aaa.txt |sed '2,$d'
         1	root:x:0:0:root:/root:/bin/bash
    
    

插入追加修改文本

  • 使用i命令在sed编辑器中来向数据流中插入文本行,该命令会在指定行前增加一个新行

  • 使用a命令在sed编辑器中来向数据流中附加文本行,该命令会在指定行后增加一个新行

  • 注意这两个命令都不能在单行上使用(即不是用来在一行中插入或附加一段文本的),只能指定插入还是附加到另一行

    
    sed '[address][i | a] newline' file
    

[root@localhost ~]# sed 'i\Insert a line behind every line' /etc/passwd      # 向数据流的每一行前面增加一个新行,新行的内容为 \ 后面的内容
[root@localhost ~]# sed '1i\Insert a line behind the first line' /etc/passwd   # 在数据流的第一行前面增加一个新行
[root@localhost ~]# sed '3a\Append a line after the third line' /etc/passwd      # 在数据流的第三行后面增加一个新行    
[root@localhost ~]# sed '$a\Append a line in the last line' /etc/passwd      # 在数据流的最后一行后面增加一个新行 
  • 使用命令c可以将数据流中的整行文本修改为新的行,与插入、附加操作一样,这要求在 sed 命令中指定新的行

    sed '[address][c] newtext' file
    

[root@localhost ~]# sed '3 c\New text' /etc/passwd     # 将数据流中第三行的内容修改为 \ 后面的内容
[root@localhost ~]# sed '/root/ c\New text' /etc/passwd  # 将匹配到 root 的行的内容修改为 \ 后面的内容
[root@localhost ~]# sed '2,4c\New text' /etc/passwd     # 将第2到4行的内容修改为 \ 后面的内容,但是不是逐行修改,而是会将这之间的 3 行用一行文本来替代 

y逐字符转换

[address]y/inchars/outchars/

转换命令会对 inchars 和 outchars 的值进行一对一的映射。inchars 中的第一个字符会被转换成 outchars 中的第一个字符;inchars 中的第二个字符会被转换成 outchars 中的第二个字符;... 直到处理完一行。如果 inchars 和 outchars 的长度不同,则 sed 编辑器会产生一个错误消息。举个例子:


[root@localhost ~]# echo abcdefggfedcba | sed 'y/acg/ACG/'
 AbCdefGGfedCbA

w保存数据到文件

[address]w filename
  • 该语句将数据流的第 1、2 行写入文件 test.txt 中去

[root@qfedu.com ~]# sed '1,2w test.txt' /etc/passwd
[root@qfedu.com ~]# sed -n 's/root/ROOT/g w change.txt' /etc/passwd // 将 /etc/passwd 中所有的 root 都替换成 ROOT,并将被修改的行保存到文change.txt 中去

r从文件中读取数据

  • filename 为要插入的文件。r 命令常结合行寻址使用,以将文本插入到指定的行后面。

  • 可以使用 r 命令来将一个文本中的数据插入到数据流中去,与普通的插入命令 i 类似,这也是对行进行操作的,命令格式如下:

    [address]r filename
    
    • 将文件 test.txt 中的内容插入到数据流第三行后面去。

      
      [root@localhost ~]# sed '3 r test.txt' /etc/passwd
      

四、模式空间和保持空间

模式空间和保持空间是两个独立的缓冲区,可以进行交互,命令可以寻址模式空间但是不能寻址保持空间。

  • 模式空间:容纳当前输入行的缓冲区,通过模式匹配到的行被读入模式空间中。用来进行进一步的操作;在多行模式中,'\n'可以用来和模式空间(N命令的结果)的任意换行符匹配,单模式空间底部的换行符除外。^匹配多行的首,$匹配多行的尾,不是每行的行首和行尾
  • 保持空间:sed在处理文本的时候都是在模式空间中进行,但有时候有些复杂的操作单一的模式空间可能无法满足需求,于是就有了保持空间,这个空间通常是空闲的,并不处理数据,只在有需要的时候和模式空间进行一些必要的数据交换。

下面是模式空间中的常用命令。

  • h: 把模式空间中的内容覆盖至保持空间中
  • H:把模式空间中的内容追加至保持空间中
  • g: 从保持空间取出数据覆盖至模式空间
  • G:从保持空间取出内容追加至模式空间
  • x: 把模式空间中的内容与保持空间中的内容进行互换

多行模式空间

sed命令都是一行一行的进行处理文本的,不过有些时候单行处理可能并不能满足我们的需要,所以sed还提供了多行模式,多行模式的命令主要有 NPD 三个

  • N:读取匹配到的行的下一行追加至模式空间

  • P:打印模式空间开端至\n内容,并追加到默认输出之前

  • D:如果模式空间包含换行符,则删除直到第一个换行符的模式空间中的文本, 并不会读取新的输入行,而使用合成的模式空间重新启动循环。如果模式空间 不包含换行符,则会像发出d命令那样启动正常的新循环

1、示例


[root@qfedu.com ~]# cat /etc/passwd |sed -n '2{N;p}'
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
# 读取第二行的下一行,然后输出模式空间中的内容,此时模式空间中有两行

[root@qfedu.com ~]# cat /etc/passwd |sed -n '2{N;N;N;p}'
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
# 使用多个N命令可以读取多行进模式空间

[root@qfedu.com ~]# seq 1 6| sed -n '1,2H;4p;5{x;p}'
4

1
2

-n 是不显示默认输出内容,1,2H是将前两行追加至保持空间,4p显示第四行,5{x;p}是在第五行的时候交换保持空间和模式空间中的内容并且输出。注意输出中的空行,这是因为H命令追加的时候是添加换行符,由于保持空间默认是空的,所以添加换行符之后就多了一个空行。以用下面的命令先往保持空间覆盖一行然后追加。


[root@qfedu.com ~]# seq 1 6| sed -n '1h;2H;4p;5{x;p}'
4
1
2
  • 第一个循环结束之后:模式空间为空,保持空间为第一行内容
  • 第二个循环,将第二行追加到模式空间,此时模式空间为两行内容
  • 第三个循环,没有匹配内容,不执行操作,模式空间和保持空间内容不变
  • 第四个循环,读取第四行并输出,保持空间内容不变
  • 第五个循环,读入第五行,然后和保持空间中的内容交换,之后输出。

# 暂存和取用命令:h H g G
[root@qfedu.com ~]# sed -r '1h;$G' /etc/hosts
[root@qfedu.com ~]# sed -r '1{h;d};$G' /etc/hosts
[root@qfedu.com ~]# sed -r '1h; 2,$g' /etc/hosts
[root@qfedu.com ~]# sed -r '1h; 2,3H; $G' /etc/hosts

# 暂存空间和模式空间互换命令:x
[root@qfedu.com ~]# sed -r '4h; 5x; 6G' /etc/hosts

对于模式空间和保持空间的个人见解


[root@localhost ~]# seq 1 6 |sed -n '1H;2p;3,5{x;p}'
2

1
3
4

  1. 首先,seq 1 6 是遍历1-6,sed -n是静默输出
  2. 1H是将1放入保持空间等待
  3. 2p是将2打印出来
  4. 3,5{x;p}3到5依次去执行{x;p}
    • 首先是3去交换已经在保持空间内的1,然后再打印,结果就是打印1
    • 然后是4去交换刚被交换进去保持空间的3,然后打印,结果就是打印3
    • 再然后5去交换在保持空间的4,继续打印,结果就是打印4
    • 最后因为是静默输出所以结果就如上面,不执行sed的就不打印显示