HOME | Links | About | Read

用sed命令编辑文本

Table of Contents

前言

sed是一个“交互式的”面向字符流的编辑器,起源于unix行编辑器――ed。sed可以将手动的编辑操作过程提取出来,转换成执行脚本来实现。在处理批量大文件时,就能体会到sed命令的强大。

大多数不熟悉sed的人都觉得,编写执行一系列编辑动作的脚本,比手动做一些改动更冒险。这种担心的原因是自动化任务会发生一些不可逆转的事情。学习sed的目的就是更好的理解它从而可以预测执行结果。换句话说,你将逐渐理解编辑的脚本与得到的输出之间的因果关系。

学习sed前,最好先掌握简单的正则表达式。

sed工作原理和基本知识

执行过程

sed维护一个模式空间(也叫工作区或者临时缓冲区),默认sed操作模式空间的内容。sed的处理过程式这样的

  1. 将文本的下一行内容读入到模式空间
  2. 将编辑命令作用于模式空间的内容
  3. 输出模式空间的内容,回到1

下面实际操作下

sed命令格式: sed [options] script filename

script一般用单引号括起来,防止shell对特殊字符进行处理

test.txt文本内容如下

linux
unix
windows
linux

sed 's/linux/Linux/' test.txt 将linux替换为Linux,执行后终端输出如下

Linux
unix
windows
Linux

如果有多次编辑,可以用;分开,比如 sed 's/linux/Linux/;s/Linux/LInux/' test.txt 执行后终端输出如下

LInux
unix
windows
LInux

如果一行中有多个需要编辑的地方,可以在script最后加g,即对每个匹配到的问题都进行替换。

从这我们就可以看到sed从文本读取一行到模式空间,然后依次将编辑命令作用于模式空间的内容,最后输出模式空间的内容。在这里,模式空间的linux被替换为Linux,接着Linux被替换为LInux,然后输出模式空间内容并清空,将下一行读入模式空间继续编辑。

在写sed脚本时,需要关注模式空间中内容的变化。

地址匹配

sed默认是对文本的每一行进行编辑的,其实我们可以限定某些行。比如 sed '1s/linux/Linux/' test.txt,只对第一行进行替换,执行后终端输出如下

Linux
unix
windows
linux

也可以这样, sed '1,4s/linux/Linux/' test.txt 对1到4行进行替换,执行后终端输出如下

Linux
unix
windows
Linux

还可以用正则匹配, sed 'in/s/x/tt' test.txt 对于含有in的行,将x替换为tt,执行后终端输出如下

linutt
unix
windows
linutt

也可以用地址对, sed 'unix,/windows/s/x/tt/' test.txt 对从含有unix的行到含有windows的行,将x替换为tt,执行后终端输出如下

linux
unitt
windows
linux

还有一种常见的用法是在地址后面加感叹号!,对匹配到的行不执行编辑, sed 'linux!s/x/tt/' test.txt 对于含有linux的行不执行替换即对所有不含有linux的行执行替换。执行后终端输出如下

linux
unitt
windows
linux

命令分组

如果要对匹配到的行执行多次编辑,可以用大括号{}括起来作为一个组。 sed 'linux/{s/l/L;s/i/I/}' test.txt ,对于含有linux的行,先替换l为L,再替换i为I,执行后终端输出如下

LInux
unix
windows
LInux

sed命令解释

s 替换命令
d 删除模式空间的内容
p 打印模式空间的内容
a 后面追加一行
i 前面插入一行
c 更改模式空间的内容
y 转换命令
n 读取输入的下一行
r 读取文件
w 写入文件
q 退出命令
N 追加下一行到模式空间
D 删除模式空间中直到第一个换行符的内容
P 打印模式空间中直到第一个换行符的内容
h 将模式空间的内容复制到保持空间
H 将模式空间的内容追加到保持空间
g 将保持空间的内容复制到模式空间
G 将保持空间的内容追加到模式空间
x 交换保持空间和模式空间的内容
b 无条件跳转
t 有条件跳转

n 读取输入的下一行

n命令会首先输出模式空间的内容,然后读取输入的下一行,接着继续执行下面的命令。

比如 sed -n 'n;p' test.txt -n选项抑制了sed的默认输出,n命令将下一行读到模式空间,由于-n选项所以看不到默认输出,接着执行p输出模式空间的内容,整个sed命令的作用其实是输出文本的偶数行,终端输出如下

unix
linux

d 删除模式空间的内容

d命令如果删除模式空间全部内容,会改变脚本的控制流程,导致读取新一行然后从编辑脚本开头重新执行。 可以用来删除空行,比如 sed '/^$/d' test.txt 会删除文本中所有的空行。

r w 读取和写入文件

r命令其实和a命令一样,区别在于r命令是将文件的内容增加到匹配到的行之后

比如 sed '/linux/r comment.txt' test.txt 将含有linux的那行下面添加comment.txt的内容 comment.txt的内容如下

****
is very good
****

命令执行后,终端输出如下

linux
****
is very good
****
unix
windows
linux
****
is very good
****

w命令可以将匹配到的行写入到相应文件

比如 sed '/x$/w unix.txt' test.txt ,将以x结尾的行,写入到unix.txt中

多行模式空间

之前的模式空间中只有一行,这里的多行模式空间可以包含文本的多行内容,行间用\n连接,其实就是组成了一个长行。

前面讲过 sed -n 'n;p test.txt 我们把n p换成多行模式的相应命令 sed -n 'N;P' test.txt 执行后终端输出如下

linux
windows

在这里,整个命令输出了文件的奇数行。

简单解释一下执行过程:第一行先被读入模式空间,N 将第二行追加到模式空间,这就是上面所说的多行模式空间,接着P输出多行模式空间到第一个\n的内容,结尾会默认输出模式空间的内容(这里我们用 -n 抑制了默认输出),将第三行读入模式空间,回到脚本开头,N将第四行最佳到模式空间,P输出模式空间到第一个\n的内容,结尾默认输出模式空间的内容。到达文件结尾,结束。

sed -n 'N;D;P' test.txt 看这个命令,N将下一行追加到模式空间,删除模式空间到第一个\n的内容,最后输出模式空间到第一个\n的内容,貌似它也是输出文本的偶数行,执行后终端没有输出。这里的D其实和d一样,都会改变执行流程,使得重新从脚本开头处理模式空间内容。所以D后面的P,永远不会执行,自然也不会有输出。

保持空间

保持空间就像一个寄存器,可以暂存文本内容。比如 sed '/unix/{h;d};/windows/G' test.txt ,对于含有unix的行和含有windows的行交错的文本,可以将含有unix的行,放到含有windows的行之后。执行后终端输出如下

linux
windows
unix
linux

该脚本先将含有unix的行复制到保持空间,然后删除模式空间的内容,这样就不会被默认输出,接着模式空间读入下一含有widnows行,此时将保持空间的内容追加到模式空间,最后默认输出模式空间,得到我们想要的结果。

sed '/unix/{h;d};/windows/x' test.txt 对于含有unix的行和含有widnows的行交错的文本,可以用含有unix的行替换含有windows的行。执行后终端输出

linux
unix
linux

t b流程控制命令

  • [address]b[label]
  • [address]t[label]

label都是可选的,如果没有,就会转到脚本的结尾处。不同的是b命令是无条件跳转,而t命令需要在替换命令,当成功替换后进行跳转。

比如 sed 'linux/{s/l/L;t;s/i/I/;t}' test.txt 将含有linux的行的l替换成L,因为t跳转到脚本结尾,所以后面i的替换并没有发生。执行后终端输出如下

Linux
unix
windows
Linux

最后看一个复杂点的脚本, 文本内容是这样的test.xml

<dict>
<key>Name</key>
<string>bbb</string>
</dict>
<dict>
<key>Name</key>
<string>ccc</string>
</dict>

脚本将含有<string>bbb</string>的dict节点删除, sed -n ':a;/^<dict>/{h;:c;n;H;/^<\/dict>/ba;bc};g;/<string>bbb<\/string>/!p' test.xml 把脚本内容放到单独的文本sed.script中

:a
/^<dict>/{
h
:c
n
H
/^<\/dict>/ba
bc
}
g
/<string>bbb<\/string>/!p

匹配到<dict>开头的文本后,将模式空间的内容复制到保持空间,n把下一行读入模式空间,H然后追加这行内容到保持空间,如果模式空间的内容是以</dict>开头,那么跳转到a即脚本开头,否则转到标签c,继续读取下一行进行c下面的命令。当读到</dict>开头的行,跳到脚本开头,此时模式空间中是</dict>开头的行,所以不会被^/<dict>/匹配到,脚本就到了g,将保持空间的内容复制到模式空间,最后一行命令的意思是,如果模式空间中没有/<string>bbb<\/string>就输出。我们也可以这样执行, *sed -n -f sed.script test.xml*,执行后终端输出如下

<dict>
<key>Name</key>
<string>ccc</string>
</dict>

最后推荐一篇实用的文章SED单行脚本 。“条条大路通罗马”,有很多方法可以达到目的,有些逻辑复杂的编辑用sed实现比较困难,文章里的单行的sed脚本可能是最实用和常用的。