sed 笔记

Table of Contents

sed 是非交互式的流编辑器(stream editor),用来对文本增改删等操作。默认情况下 sed 不会修改文本源本身的内容。sed 更擅长对文本行的操作;awk 更擅长对文本列的操作。

1. 基本命令

sed 最常用的两个命令是“替换”和“删除”。

1.1. 替换

第一个语法,最简单也是最常用的:

sed 's/old/new/' file
  • s:sed 的操作命令;
  • /../../ :限定符,限定符也可以用其他符号,比如叹号:sed 's!root!ROOT!' /etc/passwd
  • old:查找字符串
  • new:替换的新字符串

1.1.1. 实例

1、替换多个空格为一个空格:

sed "s/\s\+/ /"

如果 old 和 new 有”/“,就需要做转义:\/

2、给文本文件每行开头结尾加字符开头和结尾:

sed 's/^/xxx/'
sed 's/$/xxx/'

3、使用正则,将 IP 替换成星号:

sed -r 's/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/***/' /etc/hosts

&:把内容替换成正则匹配到的内容。例,以下的匹配会给数字前后加上“@”:

$ echo 'abc123' | sed -r 's/[0-9]+/@&@/'
abc@123@

4、替换 16 进制内容,如替换 Hive 的输出分隔符(\x01):

sed 's/\x01/ /g'

注意:只有 GNU sed 才支持 16 进制表示,如果是 BSD 系统,可以用下面这种方式:

sed  's/'$(echo "\001")'/ /g'

1.1.2. flag

s 命令更详细的用法:

s/pattern/replacement/flags

flags:

  • n,表示对指定模式第 n 次匹配进行替换
  • g,全局替换
  • p,打印模式的内容
  • I:忽略大小写

1.2. 删除

删除命令也是非常常用的,用来删除匹配的行。删除命令是:d。

例,删除匹配到“root”的行:

sed '/root/d' /etc/passwd

还可以删除不匹配的行,例,删除不匹配 com.cn 的行:

sed -i '/com.cn$/!d' file

可以为删除指定地址范围,如,删除 1~10 行:

sed '1,10d'

表示从 20 行开始删到行尾:

sed '20,$d'

表示从第一行开始一直删除到第一次匹配到字符“c”的行:

sed '1,/c/d'

表示删除空行:

sed '/^$/d'

1.3. 追加、插入、更改、替换

1.3.1. 追加

语法:[位置]a\[内容]

$ echo abc | sed '1a\test'
abc
test

1.3.2. 插入

插入是在指定位置前面插入,和追加不一样,语法:[位置]i\[内容]。

$ echo abc | sed '1i\test'
test
abc

1.3.3. 更改

语法:[位置]c\[内容]。

$ echo -e 'aaa\nbbbb\nccccc' | sed '1c\test'
test
bbbb
ccccc

1.3.4. 替换

语法:[位置]y/abc/ABC/。

sed 'y/abc/ABC/'

表示每个出现的“a”都被替换成“A”、“b”被替换成“B”、“c”被替换成“C”。

1.4. 多命令

如何让 sed 一次执行多个操作。

方法 1,使用 -e 参数:

sed  -e 's/,/\t/g' -e 's/(//g' -e 's/)//g'

方法 2,指定 sed 脚本:

s/root/ROOT/
s/\/bash/\/zsh/ # 替换 shell

注:“#”是 sed 的注释符。

然后通过 -f 参数指定:

sed -f /tmp/sed.sed /etc/passwd

方法 3,使用分号做命令的分割符:

sed 's/,/\t/g;s/(//g;s/)//g'

方法 4,使用 shell 本身的分行功能:

sed 's/,/\t/g
> s/(//g
> s/)//g' test

1.5. next 命令

next 是一个控制流程的命令,负责读取”下一行“,可以用于完成”删除 xx 下一行内容“这样的任务。

例,如果匹配”nobody“的下一行是空白行,则删除:

sed '/nobody/{n;/^$/d}' text

1.6. 读写文件

w 用于写入到文件,如

sed 's/root/ROOT/w new' < /etc/passwd

r 则是将其他文件内容读入当前模式空间:

sed -n '/root/r /etc/hosts' /etc/passwd

1.7. 打印

/p 命令,将操作的行进行打印输出,但是输出内容时并不会清理其他输出,所以可能导致重复的输出,一般来说都搭配 -n 参数使用:

sed -n 's/root/ROOT/p' /etc/passwd

打印行号用 = 命令,也可以配合 -n 使用:

sed -n '/root/=' /etc/passwd

1.8. 分组

sed 可以在指定范围内做多个操作,多个操作用大括号包围起来,如:

/root/,/gdm/{
s/\/nologin/\/NOLOGIN/g
/adm/d
}

1.9. 退出

打印 10 行后结束运行:

sed '10 q' /etc/passwd

1.10. 修改源文件

sed 默认是不会修改文本源本身的内容的,如果要把处理结果直接写入源文件,可以用 -i 参数:

sed -i 命令

但要尽量避免直接操作,否则 sed 命令一旦写错,就导致源文本被彻底修改。

1.11. 标签和跳转(test)替换换行符

sed 默认是逐行处理,处理每一行时不会考虑换行符,在处理之后才会加上,所以以下方法替换换行符是没任何用的:

sed 's/\n//g'

前面说过,可以用“N”读取下一行,所以网上出现了这种方式替换换行:

sed 'N;s/\n//g'

不过,如果是多行情况下,这种替换就会导致出现每两行被合并到一行的现象:

$ cat file
a
b
c
d
$ sed 'N;s/\n//g' file
ab
cd

sed 语法中,冒号开头的可以作为标签,t 作为跳转指令,通过这两个结合,不断地读下一行来替换即可:

$ sed ':x;N;s/\n/,/g;tx' file   # 将换行符号替换成英文逗号;“x”是标签名,“tx”是跳转到标签“x”
a,b,c,d

2. 按范围操作文本

默认情况下,sed 的命令是作用于整个文本的,但有时只希望它操作部分行。sed 支持指定命令作用于哪行,只需在命令之前指定范围,比如:

sed '1s/o/0/'

表示将文本的第一行的字母“o”替换成数字“0”。

地址也可以用范围表示:

sed '1,3s/o/0/'

表示将 1 至 3 行的字母“o”替换成数字“0”。

行尾用”$“表示:

sed '3,$s/o/0/'

表示从第三行开始替换,一直到末行。

甚至可以用正则来表达:

sed '/^c/s/w/W/'

只有匹配到行首是”c“开头的,才做替换工作。

在地址后面加”!“,表示排除。如下,把”a“替换成”A“,除了 1~4 行:

sed '1,4!s/a/A/'

“!”也可以加在 flag 部分,比如有一个 Web 日志文件,内容大致如下:

2017-08-22 17:02:24 222.130.196.* /
2017-08-22 17:02:24 222.130.196.* /images/index.css
2017-08-22 17:02:26 222.130.196.* /video/201701/20170125.jpg
2017-08-22 17:02:24 222.130.196.* /images/logo.png
......

不过日志中某些行是畸形数据,这时可以用 sed 找出畸形数据:

# 打印非 2017 开头的行
sed -n '/^2017-/!p'

确认了畸形数据后,加上 -i 参数就可以重新编辑源文件了:

sed -i -n '/^2017-/p'

跳步是 GNU sed 独有的功能,可以每跳过几行用 sed 处理一次,如:

sed '0~3s/a/A/'

从首行开始,每 3 行执行一次替换操作。

选择范围时,结束范围若是正则表达式,则结束于第一次匹配到的那行,如:

echo -e '1\n2\na\n3' | sed -n '0,/[a-z]/p'

结束范围若以”+“开头,如:3,+4p,表示从第 3 行开始,往后 4 行为操作范围:

$ seq 10 | sed -n '3,+2p'
3
4
5

还有一个较少见的:3,~4p,表示结束范围是 4 的倍数(8),举例:

$ seq 10 | sed -n '3,~2p'
3
4

表示范围是第 3 行到第 4 行(2 的倍数)。

3. 高级命令——多行和流程控制

模式空间(pattern space):sed 会从文本中逐行读取,当前行的内容就放在模式空间中的。

3.1. 多行替换

通常 sed 会只读一行到模式空间中,但也可以让它读取多行,假如有这样一段文本:

Published software should be free software. To make it free software, you need to release it under a free software license. We normally use the GNU
General Public License (GNU GPL), but occasionally we use other free software licenses. We use only licenses that are compatible with the GNU GPL for GNU software.

我想将”GNU General Public License (GNU GPL)“替换为”GPL“,但”GNU“后面出现了换行,所以无法直接替换。

sed 的”N“命令可以把下一行的内容加入到当前的模式空间中,所以借助”N“命令就可以完成以上需求:

sed '/GNU$/{N;s/GNU\nGeneral Public License (GNU GPL)/GPL/}'

如果匹配到”GNU“结尾的行,就执行”N“命令把下一行内容追加到当前模式空间中后执行替换操作。替换后如下:

Published software should be free software. To make it free software, you need to release it under a free software license. We normally use the GPL, but occasionally we use other free software licenses. We use only licenses that are compatible with the GNU GPL for GNU software.

3.2. 多行删除

如下文本:

aaaaaa









aaaaaaaaaaaaaaaaaaa




aaaaa

aaa

现在想每个非空行之间只保留一个空行,这里用到“D”命令:

sed '/^$/{N;/^\n$/D}'

为什么不是”d“?修改成”d“以后,执行结果如下:

$ sed '/^$/{N;/^\n$/d}' test
aaaaaa

aaaaaaaaaaaaaaaaaaa
aaaaa

aaa

用”d“命令时,遇到空行就读入下一行到模式空间,如果新加入模式空间的这行也是空行,那么正则”^$“是可以成功匹配,接着整个模式都被删除了——所以两行都没有了,也就是说如果空行个数是奇数,那可以保留一行空行。

现在修改成”D“命令就正常了:

$ sed '/^$/{N;/^\n$/D}' test
aaaaaa

aaaaaaaaaaaaaaaaaaa

aaaaa

aaa

因为”D“只会删除当前模式空间中第一个匹配到的行,也就是两个空行合并到模式空间中,只有其中一行会被删除。

3.3. 多行打印

”p“命令会将当前模式空间内容全部打印出来,而”P“只会打印模式空间中第一行命令:

$ seq 10 | sed -n '/[0-9]/{N;P}'
1
3
5
7
9

当匹配到数字行时,会将下一行也读入当前模式空间,并且只打印模式空间第一行,所以实现了每隔一行打印一次。

3.4. 保存空间(hold space)

sed 会把每行内容都往模式空间中保存,除了模式空间,还有个保存空间可以使用。

h 或 H:将模式空间的内容复制到保存空间 G:将保持空间的内容复制到模式空间 x:交换保持空间和模式空间的内容

例,将下面文本中带“1”的行和带“2”的行交换顺序:

1
2
11
22
111
222

命令如下:

$ sed '/1/{h;d};/2/{G}' num_file
2
1
22
11
222
111

命令说明: 1、当匹配到“1”时,先执行“h”,将模式空间内容保存到保持空间,然后删除当前模式空间的内容;

2、当匹配到“2”时,执行“G”从保持空间中将数据复制到模式空间。

4. 参考资料