文本处理三剑客之sed

文本处理三剑客之sed


  • sed概述

  • sed使用示例

  • sed的高级应用


sed概述

sed, 作为文本三剑客之一,其定位就是一个编辑器, 而且sed是一个流式编辑器(stream editor),其主要功能是过滤和转换文本。

sed - stream editor for filtering and transforming text

作为一个强大的文本处理功能,sed 自然能够配合正则表达式,另外,所谓流编辑器,sed 是逐行地读取文本,在文本行中应用指定的命令,且默认输出到stdout; sed 拥有两个缓冲区,活跃模式空间(active pattern space)与辅助保持空间(auxiliary hold space), 简称为模式空间与保持空间,且这两个空间默认都为空。

`sed' maintains two data buffers: the active _pattern_ space, 
and theauxiliary _hold_ space. Both are initially empty.

sed的基本应用只用到模式空间就够了,sed的高级应用需要模式空间与保持空间的想到配合。在sed

的基本应用中,sed会把当前处理的行存储在一个临时缓冲区,即模式空间;然后sed会在模式空间中处理文本,并且传送到标准输出,再接着处理下一行,如
此循环往复,直至文件末尾,这就是sed的基本工作原理。由此可风,sed在默认情况下是不修改原文件的;在sed的高级应用中,多了一个保持空间,保持
空间是与模式空间进行交互的,且以模式空间为主,保持空间为辅,sed匹配的内容最终都得经由模式空间传输至stdout.

常用参数

  1. -n: –quiet, –silent, 静默模式,不输出模式空间内容的自动打印

  2. -e: –expression=script, 多点编辑,类似于管道

  3. -f: –file=script-file, 从指定文件中读取sed脚本

  4. -r: 支持使用扩展正则表达式

  5. -i: 原处编辑,-i.bak可同时作备份

sed地址定界

所谓sed的地址定界,就是我们对要处理的文本给出一个范围,然后让sed只针对此范围去做相应的处理。

  1. 不给地址,默认全文处理

  2. 单地址:

    #:指定的行
     /pattern/:被此模式所能匹配到的每一行
  3. 地址范围:

    #,#:从多少行到多少行
     #,+#:从第#行起至第#行+#行结束(相对地址)
     #,/part/: 从第#行开始,到第一次匹配到/part/模式结束
     /part1/,/part2/: 从第一次匹配到/part1/开始,到最后一次匹配到/part2/结束
  4. 步进: ~

       
        1~2:奇数行
        2~2: 偶数行

sed编辑命令

  1. d: 删除模式空间匹配到的行

  2. p: 显示模式空间中的内容

  3. a \test: 在行后面追加文本,\可用空格替代;支持使用\n 实现多行追加

  4. i \test: 在行前面插入文本,\可用空格替代;支持使用\n 实现多行插入

  5. c \test: 替换为单选或多行文本,\可用空格替代

  6. w /PATH/TO/SOMEFILE: 另存为模式匹配的行到指定文件

  7. r /PATH/TO/SOMEFILE: 读取指定文件的文本至模式空间匹配到的行后

  8. =: 为模式空间的行打印行号

  9. !: 模式空间匹配的行作反处理

  10. s///: 查找替换,支持使用其它分隔符,如s@@@, s###

替换标记:
            g: 行内全局替换
            p: 显示替换成功的行
            w /PATH/TO/SOMEFILE:将替换成功的行保存至指定的文件中


sed使用示例

创建一个测试文件:

[root@centos7 ~#]cat f1
1
2
3
4
5
  1. 删除f1中的空格并写入原文件

[root@centos7 ~#]sed -i '/^$/d' f1

wKiom1erEDvSleAdAABC4VPEYmQ943.png

  1. 删除1-3行

[root@centos7 ~#]sed '1,3d' f1
4
5
[root@centos7 ~#]
  1. 打印出第2行,并说明默认打印模式空间的意义

[root@centos7 ~#]sed -n '2p' f1   # 只打印出了第2行
2
[root@centos7 ~#][root@centos7 ~#]sed '2p' f1
1
2                               # sed 默认显示出模式空间中的2
2                               # sed 匹配到第2行并打印出
3
4
5
  1. 给第2行后加入‘liansir’字样,在第3行前加入‘xiaolei'字样,替换第4行为’lvxing'

[root@centos7 ~#]nl f1 |sed -e '2a \liansir' -e '3i \xiaolei' -e '4c \lvxing' 
     1  1           # 命令n: nl - number lines of files
     2  2
liansir
xiaolei     
     3  3
lvxing     
     5  5
[root@centos7 ~#]
  1. 给f1加入空格后并显示空格行号

[root@centos7 ~#]sed -n '/^$/=' f1

wKioL1erEYfhxMeiAAAxw1iZk_A483.png

  1. 给数字2后加'liansir'字样,给数字3前加'xiaolei'字样

[root@centos7 ~#]sed -e 's/2/&liansir/' -e 's/3/xiaolei&/' f1

wKioL1erJifjoTt9AAAyaxK3ZTI975.png

  1. 删除/etc/grub2.cfg文件中所有以空白开头的行行首的空白字符

[root@centos7 ~#]sed -r 's/^[[:space:]]+//' /etc/grub2.cfg   # 以“替换”的方式实现了“删除”,-r 表示使用扩展正则表达式

wKioL1erJonjH9eRAAB3iSDcYtk073.png

wKiom1erJpyx_RNjAAAxw1iZk_A118.png

  1. 删除/etc/fstab文件中所有以#开头,后面至少跟一个空白字符的行的行首的#和空白字符

[root@centos7 ~#]sed -r 's/^#[[:space:]]*//' /etc/fstab

wKiom1erJqySxaeQAACjBTSSyu4251.png

wKioL1erJseiBuAgAACjjU_TG1s241.png

  1. 在/root/install.log每一行行首增加#号

[root@centos6 ~#]sed 's/^/#/' /root/install.log
或[root@centos6 ~#]sed -r 's/(.*)/#\1/' /root/install.log  # 后向引用,注意使用扩展正则表达式
或[root@centos6 ~#]sed -r 's/(.*)/#&/' /root/install.log

wKioL1erJuaA8zJNAACRixUhHXU596.png

  1. 处理/etc/fstab路径,使用sed命令取出其目录名和基名 (此题有料,众多解法,有兴趣者探索之)

取基名:下面命令的主要思路是不管前面是什么,只锚定最后一个单词的开头

[root@centos6 ~#]echo '/etc/fstab' |sed -r 's/(.*)\/<//'  # 注意转义
/etc/fstab
[root@centos6 ~#]
[root@centos6 ~#]echo '/etc/fstab' |sed -r 's@(.*)/<@@'  # 不想转义可直接换分隔符
/etc/fstab
[root@centos6 ~#]echo '/etc/fstab/' |sed -r 's@(.*)/<@@' # 当基名为一个目录时照样适用
/etc/fstab/
[root@centos6 ~#]

取目录名:着眼点还是在后面的基名上,即删除了基名

[root@centos6 ~#]echo '/etc/fstab' |sed -r 's@[^/]+/?$@@'
/etc/
[root@centos6 ~#]echo '/etc/' |sed -r 's@[^/]+/?$@@'     
/
[root@centos6 ~#]

取目录名与基名的完美解法

[root@centos6 ~#]echo /etc/fstab |sed -r 's@(.*/)([^/]+/?)$@\1@'  # 取目录名
/etc/
[root@centos6 ~#]echo /etc/fstab |sed -r 's@(.*/)([^/]+/?)$@\2@'  # 取基名
fstab
[root@centos6 ~#]

完美之外:

1. 涉及到/ 时则换分隔符,避免转义,如@
2. 将目录名与基名以分组的形式分别表示出来,表达了一个完整的路径
3. 分别后向引用则可取得目录名或基名,属于通用解法
  1. 利用sed 取出ifconfig命令中本机的IPv4地址 # 先打印出本机IP所在行,然后"掐头去尾"。

[root@centos7 ~#]ifconfig |sed -n '2p' |sed -e 's/.*inet//' -e 's/net.*//'
 10.1.253.100  
[root@centos7 ~#]
  1. 统计centos安装光盘中Package目录下的所有rpm文件的以.分隔倒数第二个字段的重复次数

[root@centos6 /media/CentOS_6.8_Final/Packages#]ls |sed -r 's@.*\.(.*)\.rpm$@\1@' |sort |uniq -c
      4 i686    
      919 noarch      
      1 TRANS.TBL   
      2283 x86_64
[root@centos6 /media/CentOS_6.8_Final/Packages#]
# 注意\.是转义.

至此,我们再来了解下sed的高级应用!

sed高级编辑命令:

  1. h: 把模式空间中的内容覆盖至保持空间中

  2. H: 把模式空间中的内容追加至保持空间中

  3. g: 从保持空间读取数据覆盖至模式空间中

  4. G: 从保持空间读取数据追加至模式空间中

  5. x: 把模式空间中的内容与保持空间的内容进行互换

  6. n: 读取匹配到的行的下一行覆盖至模式空间

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

  8. d: 删除模式空间中的行

  9. D: 删除当前模式空间开端至\n的内容(不再传至标准输出),放弃之后的命令,但是对剩余模式空间重新执行sed.

h H    Copy/append pattern space to hold space.
g G    Copy/append hold space to pattern space.
x      Exchange the contents of the hold and pattern spaces.
n N    Read/append the next line of input into the pattern space.
d      Delete pattern space.  Start next cycle.

可见,模式空间与保持空间的交互命令有:h, H, g, G, x.其流向关系可用下图表示:

wKioL1erJ2yjr5zhAAAhKP-QJ_c512.png

sed高级应用示例

创建一个测试文件:

[root@centos7 ~#]echo {1..5} |tr ' ' '\n' >f1
[root@centos7 ~#]cat f1
1
2
3
4
5
[root@centos7 ~#]
  1. 输出偶数行与奇数行:

普通用法:
[root@centos7 ~#]sed -n '1~2p' f1
1
3
5
[root@centos7 ~#]sed -n '2~2p' f1
2
4
[root@centos7 ~#]
高级用法:
[root@centos7 ~#]sed 'n;d' f1   
1
3
5
[root@centos7 ~#]sed -n 'n;p' f1
2
4
[root@centos7 ~#]

高级用法解析:

~#]sed 'n;d' f1
    首先,sed读入第一行的1到模式空间,n动作匹配到第二行中的2并覆盖掉
    模式空间中的1,此时模式空间中仅有2,又因为d动作被删除了,模式空间全
    空了!那怎么又输出1了呢?这是因为sed 没有使用-n选项,故1在被2覆盖之
    前已被sed默认输出,而2在模式空间却被d给干掉了,故而屏幕上输出了1。
    然后sed双读入第三行中的3,被n匹配至其下一行中的4并读入模式空间,但
    在4覆盖3之前sed已默认打印出3,而4却“胎死腹中”,故屏幕双留下了3.

~#]sed -n 'n;p' f1 
    首先,sed 读入第一行的1到模式空间,因为n动作,又匹配到第二行中的2,
    并把2读入模式空间覆盖掉之前的1,模式空间中的内容输入至stdout而得到2.
    然后,sed读入第三行中的3到模式空间,因为n动作,匹配到其下一行中的4,
    并把4读入模式空间并覆盖掉之前的3并p至stdout.
  1. 倒序输出:

[root@centos7 ~#]sed '1!G;h;$!d' f1
5
4
3
2
1
[root@centos7 ~#]

高级用法解析

~#]sed '1!G;h;$!d' f1
    这个sed命令,整体被分别;分隔为三段,我们认为他有三个动作:
    1!G;  如果不是第1行,则把保持空间中的内容追加至模式空间(保持空间默认为空)    
    h;    把模式空间中的内容覆盖至保持空间
    $!d   如果不是最后一行则删除

结合sed流编辑器的特点,逐行读入,意味每读入一行便会执行上面的三个动作;
每一行:
1. sed读入第一行中的1到模式空间,由于不符合'1!'这个条件,则跳过G执行h;
2. h 把模式空间中的1覆盖至保持空间;(此时模式空间中为1,保持空间中为1)
3. 1不是最后一行,符合条件'$!',删除(此时模式空间为空,保持空间都为1)
第二行:
1. sed读入第二中的2至模式空间,符合'1!'这个条件,从而执行G动作,G把
保持空间中的1追加至模式空间;
于是,此时模式空间中的内容为:
2
1
但保持空间是1
2. h 把模式空间中的内容覆盖至保持空间
3. 执行 $!d(模式空间为空)

依次类推,当执行完第四步时,情况是这样的:模式空间为空,保持空间的内容为:
4
3
2
1
第五步:
1. sed读入第五行中的内容5至模式空间,符合'1!'这个条件,执行G动作,把
保持空间中的内容追加到模式空间
2. h 把模式空间中的内容覆盖至保持空间
3. 第五行是最后一行,不符合'$!',跳过$!d于是,屏幕上显示:
5
4
3
2
1

一个sed的高级命令,应得大费如此篇幅,目的就是更为深入地理解sed这个流编辑器,逐行处理文本,有两个模式空间,一主一辅。看下图(图片来自网友),就更能清楚地理解sed的工作原理了。

wKioL1erKAbClxluAAB_8W01-iY715.png

此图仅供参考!

  1. 一锅上了先

[root@centos7 ~#]sed '$!N;$!D' f1
4
5
[root@centos7 ~#]
[root@centos7 ~#]sed '$!d' f1  # 不是最后一行则删除,意味着最后一行保留输出
5
[root@centos7 ~#]
[root@centos7 ~#]sed 'G' f1   # 保留空间默认为空
1

2

3

4

5
[root@centos7 ~#]

[root@centos7 ~#]sed 'g' f1





[root@centos7 ~#]sed 'g' f1 |wc -l  # 可见输出了5个空格
5
[root@centos7 ~#]
[root@centos7 ~#]sed '/^$/d;G' f1
1
2
3
4
5
[root@centos7 ~#]
[root@centos7 ~#]sed -n '1!G;h;$p' f1  # 看与高级应用分析与区别
5
4
3
2
1
[root@centos7 ~#]
4. 交换模式空间与保持空间的内容
[root@centos7 ~#]sed 'x' f1  # 模式空间中最后交换了一行被保留空间中第一个空白替换了

1
2
3
4
[root@centos7 ~#]

sed 的功能如此强大,掌握也非一日之功,sed可以帮助我们自动编辑一个或多个文件,简化对文件的反复操作,编写程序转换等。

原创文章,作者:Liansir,如若转载,请注明出处:http://www.178linux.com/33184