Contents

在Linux的文件中多行模式搜索

1. 概述

在本教程中,我们将介绍如何在文件中搜索与多行模式匹配的文本。我们将使用各种工具,例如grepawk ,它们在 Linux 中很容易访问。

此外,我们将在整个教程中使用日志日志的内容作为 示例。

2. 使用grep

grep实用程序是我们可以用来在文本文件或标准输入中查找模式的工具。大多数 Linux 发行版都预装了grep 。但是,我们也可以从发行版的官方存储库中安装它。

grep的一般语法非常简单:

grep [OPTIONS] PATTERNS [FILE]

例如,让我们在 journald 生成的日志文件中搜索“ *dhcpc6 *”:

$ grep dhcp6 log
Dec 23 23:25:51 hey NetworkManager[481]: <info>  [1640283951.9930] dhcp6 (enp5s0): activation: beginning transaction (timeout in 45 seconds)
Dec 23 23:26:37 hey NetworkManager[481]: <warn>  [1640283997.0312] dhcp6 (enp5s0): request timed out
...

有不同的grep变体是为不同的目的而构建的。首先,我们将看一下在文件中查找多行模式的标准grep,然后,我们将继续使用pcregrep

2.1.将grep与*-P*选项一起使用

使用grep的正则表达式的问题在于该模式仅限于一行。虽然可以多次使用*grep来获得所需的结果,但使用-P–perl-regexp选项更方便。-P选项为grep启用 PCRE 插件。Perl 兼容正则表达式 (PCRE) 插件使我们能够将 Perl 的正则表达式提供给grep*。**因此,它使我们可以通过模式搜索来做更多的事情。

假设我们想在journald的日志文件中找出 12 月 24 日的最后一个条目和 12 月 25 日的第一个条目。我们可以用grep这样做:

$ grep -Pzo 'Dec 24.*\nDec 25.*\n' log
Dec 24 23:53:31 hey rtkit-daemon[447]: Supervising 8 threads of 5 processes of 1 users.
Dec 25 00:00:31 hey systemd[1]: Starting Daily man-db regeneration...

让我们分解这个命令:

  • -P选项为grep启用 PCRE 插件
  • -z选项通过将NUL字符添加到每行的结尾来将匹配的文本视为行序列
  • -o选项使grep仅打印匹配的文本并忽略尾随空格
  • 该模式表示我们想要的文本应该从12 月 24 日开始
  • 正则表达式中的*.*将匹配任何字符,直到它达到**NUL*字符
  • .*\n在我们的例子中匹配12 月 24 日之后的任何字符,直到它到达换行符

现在,当我们将此正则表达式与 Dec 25.*\n 组合时,我们指示grep匹配日志文件中以Dec 24开始直到行尾的任何行,然后是下一个以Dec 25开头的立即行并以换行符结束。因此,我们可以从与此模式匹配的日志文件中看到两个不同的条目。

2.2. 使用pcregrep

pcregrep实用程序是专门使用libpcregrep变体。libpcre库是 Perl 5 正则表达式的动力。使用pcregrep的优点是它比grep 稍微快一点,而且我们不必使用-Pzo*选项来细化匹配的文本。*

*我们可以通过提供-M选项在pcregrep*中启用多行搜索模式。**例如,让我们在日志文件中查找 12 月 25 日 17:10:16 到 17:44:39 之间的条目:

$ pcregrep -M 'Dec 25 17:10:16.*(\n|.)*Dec 25 17:44:39' log
Dec 25 17:10:16 hey NetworkManager[259]: <warn>  [1640434216.0663] dhcp6 (enp5s0): request timed out
Dec 25 17:10:16 hey NetworkManager[259]: <info>  [1640434216.0664] dhcp6 (enp5s0): state changed unknown -> timeout
...
Dec 25 17:44:39 hey NetworkManager[259]: <info>  [1640436279.6494] manager: NetworkManager state is now CONNECTED_GLOBAL

正如我们所看到的,除了 (\n|.)* 部分之外,模式几乎相同,它基本上匹配两个给定时间之间的每个字符和换行符。

3. 使用awk

AWK 是一种用于文本提取的领域特定语言 (DSL)。这是大多数 Linux 发行版附带的标准 UNIX 功能。**我们选择awk而不是grep的一些原因是它比grep更强大、功能更丰富,有时甚至快得多。**因此,当使用grep无法轻松完成某些事情时,我们继续使用awk

awk命令的基本用法是:

awk [OPTIONS] '/pattern/ { action $COLUMN }' [FILE]

我们根据特定模式匹配文本,然后对匹配的文本采取进一步的处理措施。例如,我们可以从free 命令中提取总物理内存:

$ free
      total   used    free    shared  buff/cache  available
Mem:  8058024 3414968 1713536 61564   2929520     4273328
$ free | awk '/Mem:/ { print $2 }'
8058024

在这里,awk匹配*Mem:*模式,然后打印匹配行的第二列(非空)。

那么,我们如何搜索匹配多行模式的文本呢?好吧,我们可以通过添加开始和结束模式来对多行模式使用相同的语法:

awk [OPTIONS] '/start/,/end/ { action $COLUMN }' [FILE]

注意分隔两种模式的逗号。现在,让我们打印 12 月 25 日 17:00:00 到 18:00:00 之间的所有内核日志:

$ awk '/Dec 25 17:??:??/,/Dec 25 18:??:??/' log | grep kernel
Dec 25 17:09:19 hey kernel: x86/fpu: Supporting XSAVE feature 0x001: 'x87 floating point registers'
...
Dec 25 19:30:39 hey kernel: perf: interrupt took too long (3966 > 3952), lowering kernel.perf_event_max_sample_rate to 50400

** ?在正则表达式中只匹配一个字符。由于awk线性搜索文件,因此该模式应按顺序匹配从时间 17:00:00 到 18:00:00 开始的行。**然后我们将输出交给grep命令,该命令打印出其中包含关键字“kernel”的行。当然,我们可以使用awk 操作来完成grep的工作,但使用grep更有意义,以避免使表达式更复杂。