给定一个 425M 大小的文本文件,内容如下:
--START--
Data=asdfasdf
Device=B
Lorem=Ipsum
--END--
--START--
Data=asdfasdf
Lorem=Ipsum
Device=A
--END--
--START--
Device=B
Data=asdfasdf
--END--
...
任务sed
是打印--START--
和之间的所有内容--END--
,其中Device=A
包含 。这里和这里提供了两种解决方案。两个命令之间存在巨大的执行时间差异。第二个命令相当快,但需要更多的说明来说明它是如何工作的吗?
$ sed -n '/--START--/{:a;N;/--END--/!ba; /Device=A/p}' file
$ sed 'H;/--START--/h;/--END--/!d;x;/Device=A/!d' file
第一条命令的说明:
怎么运行的:
/--START--/{...}
每次我们到达包含 的行时--START--
,运行大括号内的命令{...}
。
:a;
定义标签“a”。
N;
阅读下一行并将其添加到模式空间。
/--END--/!ba
除非模式空间现在包含--END--
,否则跳回标签a
。
/Device=A/p
如果我们到达此处,则意味着模式空间以 开始--START--
并以 结束--END--
。此外,如果模式空间包含Device=A
,则打印 (p
) 它。
2号命令说明:
sed 'H #add line to hold space /--START--/h #put START into hold space (substitute holded in) /--END--/!d #clean pattern space (start next line) if not END x #put hold space into pattern space /Device=A/!d #clean pattern space if it have not "Device=A" ' file
要记住的一件事是正则表达式匹配是“昂贵的”......所以模式缓冲区中的东西越多,搜索速度就越慢。
在这种特殊情况下,
sed
必须找到三种模式(让我们将它们编号为 1、2 和 3):范围 START (1)、范围 END (2) 和该范围内的 MATCH (3)(如果有)。两种解决方案之间的主要区别在于用于存储范围内所有行的缓冲区,这又决定了如何检测范围的结束。
第一个解决方案通过在每一行上搜索 START (1) 来工作,一旦找到它,它就会开始将行附加到模式空间,并且必须在每次迭代时检查范围的 END (2)(iow 每次它添加一个模式空间中数据的新行,它再次在整个缓冲区中搜索 END 以便知道何时停止)。一旦找到它,它就会在整个模式空间中搜索 MATCH (3)。
第二个解决方案的工作方式不同:它通过 无条件地在保留空间中累积行
H
,它对每一行进行两次模式匹配:分别确定范围的开始(1)和结束(2)。这是非常快的。一旦它检测到范围的结束,它就会x
更改缓冲区(因此现在模式空间包含在保持空间中累积的所有行)并在整个模式空间中搜索 MATCH (3)。如您所见,(3)在这两种情况下是相同的:一旦模式空间包含从 START 到 END 的所有行
sed
,两个脚本都会运行一次 MATCH 搜索。因此,将两个解决方案分开的不是对 MATCH 的搜索。这里的主要区别是由 (2) 引起的: 第二种解决方案在每一行上搜索 END - 如果该行不包含 END,它将从模式空间中删除它并重新启动循环,即它拉入另一行,然后再次, 试图找到 END 等等。在找到 END 之前,模式空间中永远不会超过一行。 相比之下,第一个解决方案将执行d
a;N;/--END--/!ba
在越来越大的文本缓冲区上一遍又一遍,即使与上一次运行的差异只包含一行。在处理大型文本文件时,这绝不是一件好事——想象一下 START-END 范围跨越数千行......简而言之:搜索范围的 END 会减慢它的速度。
与第二种技术相比,第一种技术有多慢的一个很好的例子可以在这里找到:
将列表变成带分隔符的单行
正如您在我的测试中看到的那样,第一个解决方案甚至无法完成测试。