7

用sed命令编辑文本

 3 years ago
source link: https://houye.xyz/2017-12/sed/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

用sed命令编辑文本

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脚本可能是最实用和常用的。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK