用sed命令编辑文本
Table of Contents
前言
sed是一个“交互式的”面向字符流的编辑器,起源于unix行编辑器――ed。sed可以将手动的编辑操作过程提取出来,转换成执行脚本来实现。在处理批量大文件时,就能体会到sed命令的强大。
大多数不熟悉sed的人都觉得,编写执行一系列编辑动作的脚本,比手动做一些改动更冒险。这种担心的原因是自动化任务会发生一些不可逆转的事情。学习sed的目的就是更好的理解它从而可以预测执行结果。换句话说,你将逐渐理解编辑的脚本与得到的输出之间的因果关系。
学习sed前,最好先掌握简单的正则表达式。
sed工作原理和基本知识
执行过程
sed维护一个模式空间(也叫工作区或者临时缓冲区),默认sed操作模式空间的内容。sed的处理过程式这样的
- 将文本的下一行内容读入到模式空间
- 将编辑命令作用于模式空间的内容
- 输出模式空间的内容,回到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脚本可能是最实用和常用的。