sed篇总共分成6章:(简书版)
在上一篇中介绍的基础命令都是面向行的,一般情况下,这种处理并没有什么问题,但是当匹配的内容是错开在两行时就会有问题,最明显的例子就是某些英文单词会被分成两行。
幸运地是,sed允许将多行内容读取到模式空间,这样你就可以匹配跨越多行的内容。本篇笔记主要介绍这些命令,它们能够创建多行模式空间并且处理之。其中,N/D/P这三个多行命令分别对应于小写的n/d/p命令,后者我们在上一篇已经介绍。它们的功能是类似的,区别在于命令影响的内容不同。例如D命令与d命令同样是删除模式空间的内容,只不过d命令会删除模式空间中所有的内容,而D命令仅会删除模式空间中的第一行。
读下一行:N
N命令将下一行的内容读取到当前模式空间,但是下n命令不一样的地方是N命令并没有直接输出当前模式空间中的行,而是把下一行追加到当前模式空间,两行之间用回车符\n连接,如下图所示:
模式空间包含多行之后,正则表达式的^/$符号的意思就变了,^是匹配模式空间的最开始而非行首,$是匹配模式空间的最后位置而非行尾。
书中给的第一例子,是替换以下文本中的"Owner and Operator Guide"为"Installation Guide",正如我们在本篇开头说的Owner and Operator Guide跨越在两行:
Consult Section 3.1 in the Owner and Operator
Guide for a description of the tape drives
available on your system.
我们用N命令试下手:
$ sed'/Operator$/{N;s/Owner and Operator\nGuide/Installation Guide/}'textConsultSection3.1intheInstallationGuidefora description of the tape drives
available on your system.
不过这个例子有两个局限:
我们知道Owner and Operator Guide分割的位置;
执行替换命令后,前后两行拼接在一起,导致这行过长;
第二点,可以这样解决:
$ sed'/Operator$/{N;s/Owner and Operator\nGuide /Installation Guide\n/}'textConsultSection3.1intheInstallationGuidefora description of the tape drives
available on your system.
现在去掉第1个前提,我们引入更加复杂的测试文本,这段文本中Owner and Operator Guide有位于一行的,也有跨越多行的情况:
$ cat textConsultSection3.1intheOwnerandOperatorGuidefora description of the tape drives
available on your system.LookintheOwnerandOperatorGuideshipped with your system.Twomanuals are provided including theOwnerandOperatorGuideand theUserGuide.TheOwnerandOperatorGuideis shipped with your system.
相应地修改下之前执行的命令,如下所示:
$ sed's/Owner and Operator Guide/Installation Guide/
/Owner/{
N
s/ *\n/ /
s/Owner and Operator Guide */Installation Guide\
/
}'textConsultSection3.1intheInstallationGuidefora description of the tape drives
available on your system.LookintheInstallationGuideshipped with your system.Twomanuals are provided including theInstallationGuideand theUserGuide.TheInstallationGuideis shipped with your system.
这里我们首先将在单行出现的Owner and Operator Guide替换为Installation Guide,然后再寻找匹配Owner的行,匹配后读取下一行的内容到模式空间,并且将中间的换行符替换成空格,最后再替换Owner and Operator Guide。
解释起来很简单,但是中间还是有些门道的。比如你可能觉得这里最前面的s/Owner and Operator Guide/Installation Guide/命令是多余的,假设你删除这一句:
$ sed'/Owner/{
> N
> s/ *\n/ /
> s/Owner and Operator Guide */Installation Guide\
> /
> }'textConsultSection3.1intheInstallationGuidefora description of the tape drives
available on your system.LookintheInstallationGuideshipped with your system.Twomanuals are provided including theInstallationGuideand theUserGuide.TheOwnerandOperatorGuideis shipped with your system.
最明显的问题是最后一行没有被替换,原因是当最后一行的被读入到模式空间后,匹配Owner,执行N命令读入下一行,但是因为当前已经是最后一行,所以N读取替换,告诉sed可以退出了,sed也不会继续执行接下来的替换命令(注:书中说最后一行不会被打印输出,我这边测试是会输出的)。所以这里需要在使用N的时候加一个判断,即当前行为最后一行时,不读取下一行的内容:$!N,这一点在很多场合都是有用的,更改后重新执行:
$ sed'/Owner/{
> $!N
> s/ *\n/ /
> s/Owner and Operator Guide */Installation Guide\
> /
> }'textConsultSection3.1intheInstallationGuidefora description of the tape drives
available on your system.LookintheInstallationGuideshipped with your system.Twomanuals are provided including theInstallationGuideand theUserGuide.TheInstallationGuideis shipped with your system.
上面只是为了用例子说明N的用法,可能解决方案未必是最好的。
删除行:D
该命令删除模式空间中第一行的内容,而它对应的小d命令删除模式空间的所有内容。如果模式空间中内容不为空,D不会导致读入新行,相反它会回到最初的编辑命令,重要应用在模式空间剩余的内容上。
后面半句开始比较难以理解,用书中的一个例子来解释下。现在我们有一个文本文件,内容如下所示,行之间有空行:
$ cat textThisline is followed by1blank line.Thisline is followed by2blank lines.Thisline is followed by3blank lines.Thisline is followed by4blank lines.Thisis the end.
现在我们要删除多余的空行,将多个空行缩减成一行。假如我们使用d命令来删除,很简单的逻辑:
$ sed'/^$/{N;/^\n$/d}'textThisline is followed by1blank line.Thisline is followed by2blank lines.Thisline is followed by3blank lines.Thisline is followed by4blank lines.Thisis the end.
我们会发现一个奇怪的结果,奇数个数的相连空行已经被合并成一行,但是偶数个数的却全部被删除了。造成这样的原因需要重新翻译下上面的命令,当匹配一个空行是,将下一行也读取到模式空间,然后若下一行也是空行,则模式空间中的内容应该是\n,因此匹配^\n$,从而执行d命令会将模式空间中的内容清空,结果就是相连的两个空行都被删除。这样就可以理解为什么相连奇数个空行的情况下是正常的,而偶数个数就有问题了。
这种情况下,我们就应该用D命令来处理,这样做就得到预期的结果了:
$ sed'/^$/{N;/^\n$/D}'textThisline is followed by1blank line.Thisline is followed by2blank lines.Thisline is followed by3blank lines.Thisline is followed by4blank lines.Thisis the end.
D命令只会删除模式空间的第一行,而且删除后会重新在模式空间的内容上执行编辑命令,类似形成一个循环,前提是相连的都是空行。当匹配一个空行时,N读取下一行内容,此时匹配^\n$导致模式空间中的第一行被删除。现在模式空间中的内容是空的,重新执行编辑命令,此时匹配/^$/。继续读取下一行,当下一行依然为空行时,重复之前的动作,否则输出当前模式空间的内容。造成的结果是连续多个空行,只有最后一个空行是保留输出的,其余的都被删除了。这样的结果才是我们最初希望得到的。
打印行:P
P命令与p命令一样是打印模式空间的内容,不同的是前者仅打印模式空间的第一行内容,而后者是打印所有的内容。因为编辑命令全部执行完之后,sed默认会输出模式空间的内容,所以一般情况下,p和P命令都是与-n选项一起使用的。但是有一种情况是例外的,即编辑命令的执行流程被改变的情况,例如N,D等。很多情况下,P命令都是用在N命令之后,D命令之前的。这三个命令合起来,可以形成一人输入输出的循环,并且每次只打印一行:读入一行后,N继续读下一行,P命令打印第一行,D命令删除第一行,执行流程回到最开始重复该过程。
$ echo-e"line1\nline2\nline3"|sed'$!N;P;D'
不过多行命令用起来要格外小心,你要用自己的大脑去演算一遍执行的过程,要不然很容易出错,比如:
$ echo-e"line1\nline2\nline3"|sed-n'N;1P'
你可能期望打印第一行的内容,事实上并没有输出。原因是当N继续读入第二行后,当前行号已经是2了,我们在第二篇笔记中曾经说过,行号只是sed在内部维护的一个计数变量而已,每当读入新的一行,行号就加一:
$ echo-e"line1\nline2\nline3"|sed-n'$!N;='23
我们依然用替换Unix System为Unix Operating System作为例子,介绍N/P/D三个命令是如何配合使用的。
示例文本如下所示,为了举例方便,这段文本仅有三行内容,刚好可以演示一个循环的处理过程:
$ cat textTheUNIXSystemand UNIX...
执行的命令:
$ sed'/UNIX$/{
> N
> s/\nSystem/ Operating &/
> P
> D
> }'textTheUNIXOperatingSystemand UNIX...
执行过程如下图所示:
以上三个命令的用法与之前介绍的基础命令是截然不同的,有些同学可能都没有接触过。在下一篇中,我会介绍更多高级的命令,同时为引入一个新的概念:保持空间(Hold Space)。
转自:团子的小窝, 本文固定链接:Sed and awk 笔记之 sed 篇:高级命令(一)