【记录】sed 学习

2017-05-25  本文已影响63人  zzpwestlife

原文链接
http://zouzhipeng.com/2017/05/25/%E3%80%90%E8%AE%B0%E5%BD%95%E3%80%91sed%20%E5%AD%A6%E4%B9%A0/

本文是慕课网 《实例妙解sed 和 awk 的秘密》》 的学习记录的第二部分:sed。

Linux 三大利器: grep, sed, awk.

1. sed 工作原理

sed 是一种流处理编辑器

  1. 文本或管道输入(正则选定文本)
  2. 读入一行到模式空间(临时缓冲区)
  3. sed命令处理
  4. 结果输出到屏幕
  5. 重复第2步操作

sed 一次处理一行内容

sed 不改变文件内容 (除非重定向)

2. 使用 sed 格式

$ sed [options] 'command' file(s)

options: -e, -n

command: 行定位(正则) + sed命令 (操作)

$ sed -f scriptfile file(s)

举个栗子:

// p 即 print,打印命令
$ sed -n 'root/p'

2. sed 基本操作命令

1. -p 打印相关命令

$ sed 'p' passwd

结果每一行都被输出了两次,为什么?

sed 原理为读入一行,输出一行到屏幕,现在中间又加入一个打印的步骤,所以就出现了重复。

如何规避?

再添加一个 -n 参数即可。该参数一般与 p 参数一起使用,表示只打印出相关的行。

$ sed -n 'p' passwd

2. 行定位

// 打印第33行
$ sed -n '33p' passwd
_svn:*:73:73:SVN Server:/var/empty:/usr/bin/false

通过 nl 命令查看行号

// 打印第33行
$ nl passwd | sed -n '33p'
    33  _svn:*:73:73:SVN Server:/var/empty:/usr/bin/false
// 打印包含root的行
$ sed -n '/root/p' passwd
root:*:0:0:System Administrator:/var/root:/bin/sh
daemon:*:1:1:System Services:/var/root:/usr/bin/false
_cvmsroot:*:212:212:CVMS Root:/var/empty:/usr/bin/false
// 打印10到20行
$ nl passwd | sed -n '10,20p'
// 打印第一个包含root的行到第一个包含www的行之间所有的行
$ nl passwd | sed -n '/root/,/www/p

都是匹配第一个

// 不打印第20行
$ nl passwd | sed -n '20!p'
// 不打印10到20行
$ nl passwd | sed -n '10,20!p'
// 输出奇数行,Mac亲测无效,CentOS 有效
$ nl passwd | sed -n '1~2p'
     1  root:x:0:0:root:/root:/bin/bash
     3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
     5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
     ...

3. sed 行处理命令

1. 基本操作命令(Mac下失效)

a:新增行

i:插入行

c:替代行

d:删除行

// 在第5行后面添加分隔线
$ nl passwd | sed '5a ==============='
     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
     ...
// 在第1-5行后面添加分隔线
$ nl passwd | sed '1,5a ==============='
     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
     ...
// 在第1-5行之前添加分隔线
$ nl passwd | sed '5i ==============='
     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
// 第5行替换为其他内容
$ nl passwd | sed '5c ==============='
     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
===============
     6  sync:x:5:0:sync:/sbin:/bin/sync
$ nl passwd | sed '1,5c ==============='
===============
     6  sync:x:5:0:sync:/sbin:/bin/sync
     7  shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

注意替换规则与插入规则的区别,连续行的替换是整体替换,而不是一行一行。

随意感受一下

$ nl passwd | sed '/nologin/c ==============='
     1  root:x:0:0:root:/root:/bin/bash
===============
===============
===============
===============
     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
===============
===============
===============
===============
===============
===============
===============
===============
===============
===============
===============
===============
===============
===============
===============
===============
    25  zzp:x:1000:1000::/home/zzp:/bin/bash
===============
// 删除行,不会影响源文件
$ nl passwd | sed '/root/d'
     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

2. 案例1 修改配置文件

简单的操作就可以替代 vim 了

// 在文档末尾增加一些内容
$ sed '$a new line \nnew line'

3. 案例2 删除文本中的空行

$ sed '/^$/d' file

4. 案例3 log 中提取 error

$ sed -n '/Error/p' file

4. sed 替换命令 s (核心,重点)

$ sed 's/nologin/login/' passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/login
daemon:x:2:2:daemon:/sbin:/sbin/login
adm:x:3:4:adm:/var/adm:/sbin/login
lp:x:4:7:lp:/var/spool/lpd:/sbin/login
$ sed 's/:/%/' 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
adm%x:3:4:adm:/var/adm:/sbin/nologin
lp%x:4:7:lp:/var/spool/lpd:/sbin/nologin

这里出现了一个问题,每一行只替换了第一个匹配。

添加全局标记 g 即可

$ sed 's/:/%/g' 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
adm%x%3%4%adm%/var/adm%/sbin/nologin
lp%x%4%7%lp%/var/spool/lpd%/sbin/nologin

1. 案例:获取网卡中的ip

思路:首先取出 ifconfig 命令输出的第二行,然后将 ip 前后两部分无用的信息分别替换为空即可。

// 取出所需行
$ ifconfig | sed -n '/inet .*broadcast.*/p'
        inet 172.17.147.89  netmask 255.255.240.0  broadcast 172.17.159.255
// 替换ip前的字符为空
$ ifconfig | sed -n '/inet .*broadcast.*/p' | sed 's/inet //'
        172.17.147.89  netmask 255.255.240.0  broadcast 172.17.159.255
// 替换ip后的字符为空
$ ifconfig | sed -n '/inet .*broadcast.*/p' | sed 's/inet //' | sed 's/netmask.*//'
        172.17.147.89
// 替换可能漏掉的空格
$ ifconfig | sed -n '/inet .*broadcast.*/p' | sed 's/inet //' | sed 's/netmask.*//' | sed 's/\ //'
       172.17.147.89

3. sed 高级操作命令

1. -{ }: 多个sed命令,用;分开

// 先将2-10行删除,然后将false替换为true,两个命令写到一起,这里{}不用转义
$ nl passwd | sed '{2,10d;s/false/true/}'
     1  root:x:0:0:root:/root:/bin/bash
    11  games:x:12:100:games:/usr/games:/sbin/nologin
    12  ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
    13  nobody:x:99:99:Nobody:/:/sbin/nologin

2. -n 读取下一个输入行(用下一个命令处理)

有一个跳行的过程

$ nl passwd | sed -n '{n;p}'
     2  bin:x:1:1:bin:/bin:/sbin/nologin
     4  adm:x:3:4:adm:/var/adm:/sbin/nologin
     6  sync:x:5:0:sync:/sbin:/bin/sync
     8  halt:x:7:0:halt:/sbin:/sbin/halt

这就实现了偶数行输出。如何输出奇数行?先输入p后跳行即可。

$ nl passwd | sed -n '{p;n}'
     1  root:x:0:0:root:/root:/bin/bash
     3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
     5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
     7  shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

之前的等效做法

$ nl passwd | sed -n '1~2p'
$ nl passwd | sed -n '2~2p'

2,5,8... 这样输出呢?

// 两种方法等效,推荐使用前者
$ nl passwd | sed -n '2~3p'
$ nl passwd | sed -n '{n;p;n}'

3. & 符号使用

-&: 替换固定字符串
优化替换操作。

案例: passwd 文件中的用户名后面添加空格,便于阅读。

分析:

使用 &,就没有必要在替换后的字符串中将替换前的正则重新再写一遍。

$ sed 's/^[a-z_-]\+/&   /' 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

原来的写法可能是这样的,还不一定能实现。

$ sed 's/^[a-z_-]\+/^[a-z_-]\+  /' passwd

案例: 大小写转换

将用户名的首字母转化为大写/小写

元字符

$ sed 's/^[a-z_-]\+/\u&/' 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
Adm:x:3:4:adm:/var/adm:/sbin/nologin
$ sed 's/^[a-z_-]\+/\U&/' 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

文件名转大写

$ ls *.txt | sed 's/^\w\+/\U&/'

4. ()符号

一个括号代表一个要被替换的部分。注意转义括号。

示例:数据筛选,获取passwd中 USER, UID 和 GID

一个一个获取

$ sed 's/\(^[a-z_-]\+\):.*$/\1/' passwd
root
bin
daemon
adm
$ sed 's/\(^[a-z_-]\+\):x:\([0-9]\+\).*$/\1 \2/' passwd
root 0
bin 1
daemon 2
adm 3
lp 4
sync 5
shutdown 6
halt 7
mail 8
operator 11
games 12
ftp 14
nobody 99
$ $ sed 's/\(^[a-z_-]\+\):x:\([0-9]\+\):\([0-9]\+\).*$/USER:\1 UID:\2 GID:\3/' passwd
USER:root UID:0 GID:0
USER:bin UID:1 GID:1
USER:daemon UID:2 GID:2
USER:adm UID:3 GID:4
USER:lp UID:4 GID:7
USER:sync UID:5 GID:0
USER:shutdown UID:6 GID:0
USER:halt UID:7 GID:0
USER:mail UID:8 GID:12
USER:operator UID:11 GID:0
USER:games UID:12 GID:100
USER:ftp UID:14 GID:50
USER:nobody UID:99 GID:99

示例: 获取网卡IP

分析:之前我们是将IP行分为三段,分别将前后两段无用的信息替换为空。现在我们还是将该行分为三段,但是只要获取第二段即可。

先找到IP所在的行,匹配第一部分,注意别漏了空格。第二部分用括号括起来,用于输出,第三部分不用管。最后将第二部分打印输出即可。

$ ifconfig | sed -n '/inet /p' | sed -n '1p' | sed 's/[a-z ]\+\([0-9.]\+\) .*$/\1/'
172.17.147.89

5. rw 命令

读和写命令,文件互操作

r: 复制指定文件插入到匹配行

w: 复制匹配行拷贝至指定文件

$ echo -e '11111\n22222\n33333' > 123.txt
$ echo -e 'aaaa\nbbbbb\ncccc' > abc.txt
// 读取文件123.txt,将结果放入到abc.txt 中,放置位置为第一行后。
$ sed '1r 123.txt' abc.txt
aaaa
11111
22222
33333
bbbbb
cccc

如果不指定行号,将会在目录文件的每一行后面插入读取到的文件内容。

读文件后,两个文件的内容都没有发生改变。

// 将123.txt文件的第一行写入abc.txt
$ sed '1w abc.txt' 123.txt

写操作后,abc.txt内容受影响,原内容消失,只剩下本次操作写入的内容。不指定行号就相当于文件内容拷贝。

引号中是rw直接操作的文件。使用w的时候要格外小心。

6. q 命令

退出 sed

在执行中间提前退出sed,没必要每次都执行到最后一行。

// 执行到第5行退出sed
$ nl passwd | sed '5q'
     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
// 找到第一个 nologin 就退出 sed
$ nl passwd | sed '/nologin/q'
     1  root:x:0:0:root:/root:/bin/bash
     2  bin:x:1:1:bin:/bin:/sbin/nologin
上一篇 下一篇

猜你喜欢

热点阅读