使用Grep无法高效地完成这个任务。这里有一个简单的Sed结构可以记住:
sed -n '/before/ {h;n;}; /after/ {x;p;x;p;}' < input.txt
这个程序会存储最近匹配模式before
的行,然后在遇到后续匹配模式after
的行时将其打印出来。然后,它也会打印出匹配after
的行。具体来说:
-n
标志抑制了每一行的输出——我们将告诉Sed手动输出我们想要的内容。
/before/
- 当我们找到一个被模式before
匹配的行时...
h
- 将其保存到保持空间缓冲区以备后用。
n
- 继续到下一行。
/after/
- 当我们找到一个被模式after
匹配的行时...
x;p
- 用保持缓冲区(before
)中的内容交换该行并将其打印出来。
x;p
- 将after
再次从保持缓冲区中交换出来并将其打印出来。
这个程序运行非常快,因为我们可以在一次过滤输入的情况下完成操作,而不需要先将输出进行管道传输或反转文件。
现在,让我们将其应用到问题的示例中:
sed -n '/^date pattern$/ {h;n;}; /ALARM/ {x;p;x;p;}' < input.txt
这只是将特定的模式插入到我上面描述的Sed程序中 - 每次看到ALARM
时,它输出最近看到的日期和匹配的行。因为问题只想在每个日期后显示包含ALARM
的最后一行,所以我们需要稍微修改程序:
sed -n '
/^date pattern$/ {
:alarm
x
/ALARM/ {s/^\(date pattern\)\n.*\n\(.*ALARM.*\)$/\1\n\2/;p;n;}
}
/ALARM/ H
$ b alarm
' < input.txt
这里的缓存不仅包含日期,还有包含ALARM
的每一行,直到Sed遇到下一个日期。然后它将打印出在缓存中保留的最后一条ALARM
和该日期。我们检查ALARM
是否存在,以便在没有警报发生时不打印日期。:alarm
声明了一个分支标签,我们可以使用b alarm
返回到该标签,就像我们对文件的最后一行(用$
表示)处理剩余的任何内容一样。
在这些示例中,我使用了[A-Z][a-z]\+day [0-9]\{4\}
作为date pattern
,但根据需要进行调整。
编辑:我认为我误解了问题。看起来我们只想要整个文件中的最后一个日期和最后一行警报。如果是这样,首先使用Tac反转文件更快,但会消耗更多的内存:
tac input.txt | sed -n '/ALARM/ {h;:a;n;/^date pattern$/ {p;x;p;q;}; ba;}'
采用这种方法,我们将最后一个警报存储在文件中,并在找到并打印文件中的最后一个日期后进行打印。我们使用q
尽快退出,以避免处理其余部分。如果我们的系统上没有Tac,我们也可以使用Sed来反转文件:
sed '1!G;h;$!d' < input.txt | sed ...
grep -B10 'ALARM' myfile.log | grep 'Monday'
,但即使这种解决方案也存在一些缺陷。 - 0x5453