在Linux的文件中多行模式搜索
1. 概述
在本教程中,我们将介绍如何在文件中搜索与多行模式匹配的文本。我们将使用各种工具,例如grep 和awk ,它们在 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实用程序是专门使用libpcre的grep变体。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更有意义,以避免使表达式更复杂。