匹配后仅显示第n行
1. 概述
当我们在输入中搜索模式时,grep 可能是第一个出现的命令。默认情况下,grep命令可以打印匹配的行。此外,它允许我们在 match 之前或之后 打印额外的上下文行。
在本教程中,我们将讨论如何在匹配后仅打印第 n 行。
2. 问题介绍
让我们通过一个例子来理解这个问题。首先,让我们看一个输入文件:
$ cat report.txt
Performance: BAD
- app name: weather-cache
- time: 2021-12-18 21:20:20
- resource usage: CPU: 3%, RAM: 4096MB
- description: RAM usage is too high!
- ... omitted many other lines ...
Performance: OK
- app name: database-import
- time: 2021-12-19 20:20:20
- CPU Usage: 1%, RAM Usage: 200MB
- description: everything runs well!
Performance: BAD
- app name: weather-web
- time: 2021-12-19 21:20:20
- resource usage: CPU: 100%, RAM: 300MB
- description: CPU usage is too high!
- ... omitted many other lines ...
假设我们的监控系统定期扫描所有正在运行的应用程序并创建报告。report.txt是生成的报告之一。
正如我们在输入文件中看到的,我们有“Performance: OK”和“Performance: BAD”块。
通常,我们只需要仔细查看那些“ Performance: BAD ”块。因此,字符串“ Performance: BAD ”将是我们的搜索模式。
但是,我们不想读取整个块。例如,有时,我们只需要知道有性能问题的应用程序的名称,即“ Performance: BAD”行之后的第一行。
但有时,我们也不关心哪个应用程序存在性能问题。相反,我们想知道“资源使用”状态,这是“Performance: BAD”行之后的第三行。
因此,我们可以看出这是一个“每次匹配后只打印第n行”的问题。
在本教程中,我们将从“每次匹配后仅打印下一行”开始,因为这种要求在实践中经常出现。之后,我们将把它扩展到更一般的情况:“每次匹配后的第 n 行”。
此外,我们假设两条匹配线之间的线数总是大于n。
下面我们先来看看如何在每次匹配后只打印下面一行。
3. 每次匹配后只打印下一行
有多种方法可以在每场比赛后只获得下一行。在本节中,我们将介绍三种直接的方法:使用grep、 *sed *和awk 。
接下来,让我们看看它们的实际效果。
3.1. 使用grep命令
如果我们使用选项“ -A1 ”, grep将输出匹配的行及其后的行。现在,我们需要抑制匹配的行。
为此,我们可以将“ grep -A1 ”搜索结果通过管道传递给另一个带有-v选项的grep*命令以反转搜索 *:
$ grep 'Performance: BAD' --no-group-separator -A1 report.txt | grep -v 'Performance: BAD'
- app name: weather-cache
- app name: weather-web
正如我们在上面的输出中看到的,我们得到了预期的结果。
我们使用选项–no-group-separator*来取消行组之间的分隔符*,默认情况下为“ — ”。
3.2. 使用sed命令
一个紧凑的 sed单行代码可以解决这个问题。首先,让我们看一下命令:
$ sed -n '/Performance: BAD/{ n; p }' report.txt
- app name: weather-cache
- app name: weather-web
上面的输出显示 sed一行代码完成了这项工作。
让我们快速浏览一下命令以了解其工作原理:
- -n – 告诉 sed不要自动打印模式空间 ,除非我们要求它打印
- /pattern/{…} –当输入行与*模式匹配时执行**{…}*中的命令
- {n; p} – 当一行与模式匹配时*,*首先用下一个输入行覆盖模式空间(n命令),然后打印模式空间(p命令)
简单地说,这个想法是:一旦找到匹配的行,我们就读取下一行并打印。
3.3. 使用awk命令
当然,我们可以用awk命令实现与**sed解决方案相同的想法。
getline 是awk中的一个多功能语句。如果我们写一个简单的“ getline ”,它的工作方式与sed的“ n ”命令非常相似——读取下一个输入行:
$ awk '/Performance: BAD/{ getline; print }' report.txt
- app name: weather-cache
- app name: weather-web
所以, 上面的awk命令也可以工作。
接下来,让我们扩展这个问题,看看如何在每次匹配后只打印第 n 行。
4. 每场比赛后只打印第 N 行
我们仍将使用 grep、sed和awk命令来解决问题。
4.1. 使用grep命令
我们之前使用grep命令解决了n=1的情况:
grep 'pattern' --no-group-separator -A1 input | grep -v 'pattern'
我们可能认为将*-A1更改为-An会解决“第 n 个”情况。但是,如果 n>1,这个想法将不再有效。这是因为** grep -An将输出n+1行:匹配的行 +它之后的n*行**。
如果我们将此结果通过管道传递给*grep -v ‘pattern’ *,则匹配的行将被抑制。但是我们将有 n行而不是匹配后的第 n 行。
要在每次匹配后获得第 n 行,我们可以先使用grep -An找到每个包含n+1行的块。接下来,我们不是将其通过管道传输到grep -v,而是通过管道将其传输到可以打印每 (n+1) 行的命令。
例如,我们可以使用awk命令轻松地做到这一点:
$ grep 'Performance: BAD' --no-group-separator -A3 report.txt | awk 'NR % 4 == 0'
- resource usage: CPU: 3%, RAM: 4096MB
- resource usage: CPU: 100%, RAM: 300MB
如上面的输出所示,我们在每个“ Performance: BAD ”行之后得到了第 3 行。
此外,我们还使用awk对grep的结果进行后处理 。其实,单凭强大的awk就足以解决这个问题。我们将在后面的部分中看到它。
4.2. 使用sed 命令
首先,让我们回顾一下我们解决了“每次匹配后打印下一行”问题的sed单行代码:
sed -n '/pattern/{ n; p }' input
上面命令的核心部分是:当一行与模式匹配时,获取下一个输入行(n)并打印(p)。
因此,如果我们想在每场比赛后获得第三行,我们可以在一行中再添加两个 ’ n ‘:
$ sed -n '/Performance: BAD/{ n; n; n; p }' report.txt
- resource usage: CPU: 3%, RAM: 4096MB
- resource usage: CPU: 100%, RAM: 300MB
正如我们在上面的输出中看到的,问题已经解决。
但是,值得一提的是,由于sed脚本不支持变量或其他脚本语言功能,例如while、if-else等,因此构建问题的通用解决方案并不容易。
例如,如果要求说“在每次匹配后打印第 15 行”,我们必须键入 15 ’ n ‘。但是,要使sed命令动态化,我们可以 通过其他脚本(例如 shell 脚本)来构建sed命令。
4.3. 将awk命令与getline一起使用
与sed相比,awk脚本支持丰富的函数集和类似 C 的流程控制。
因此,我们可以 通过将 getline语句包装在 for循环中来轻松扩展之前的awk命令,以便在每次匹配后输出第三行:
$ awk -v n=3 '/Performance: BAD/ { for (i = 1; i <= n; i++) getline; print }' report.txt
- resource usage: CPU: 3%, RAM: 4096MB
- resource usage: CPU: 100%, RAM: 300MB
我们可以看到awk命令很容易理解并且可以完成工作。
正如我们所讨论的,getline是一个多功能语句。但是,建议默认情况下最好避免使用getline ,特别是对于初学者。
因此,让我们看看另一种awk方法,它可以在不使用getline 的情况下解决这个问题。
4.4. 在没有getline 的情况下使用awk命令
awk允许我们声明变量。因此,我们可以将匹配的行号存储在一个变量中,比如mLine,并检查当前行号是否等于 ( mLine + n )。如果相等,那么我们将打印该行:
$ awk -v n=3 '/Performance: BAD/ { mLine = NR } mLine && NR == mLine + n' report.txt
- resource usage: CPU: 3%, RAM: 4096MB
- resource usage: CPU: 100%, RAM: 300MB