Contents

在指定的行号后使用grep

1. 概述

当我们想在 Linux 命令行中 搜索文本文件 中的某些模式时, grep命令将是第一个想法。

事实上,凭借 Regex 的强大功能,  grep擅长模式匹配工作。但是,有时,我们想在文件中指定行号之后搜索一些模式

在本教程中,我们将探讨如何实现这一目标。

2. 问题介绍

像往常一样,让我们通过一个例子快速理解问题。假设我们有一个日志文件:

$ cat -n app.log
     1	[INFO] Application started
     2	[INFO] Invoking remote API ... Successful
     3	[WARN] User "Eric" gave wrong password: 5 times
     4	[ERROR] RuntimeException: File not found: /foo/bar/aFile
     5	... stack trace ...
     6	[INFO] Application refreshed
     7	[ERROR] RuntimeException: File not found: /foo/bar/newFile
     8	... stack trace ...
     9	[WARN] TemplateNotFoundException: Template PRETTY not found, loading the default template
    10	[INFO] Default template loaded
    11	[WARN] Cleanup job done with IOException: Disk is full

如上面的输出所示,我们有一个应用程序的日志文件。我们使用带有*-n*选项的cat 命令 来显示带有行号的内容。

有一些日志条目包含单词*“Exception”。如果我们想找到这些条目,我们可以简单地使用命令grep ‘Exception’ app.log*。这对我们来说根本不是挑战。

但是,我们想添加一个额外的要求:我们需要进行精确搜索,但只搜索第六行之后的行。也就是说,只有第 7、9 和 11 行应该出现在输出中。

接下来,让我们看看如何解决这个问题。

3. 使用 tailgrep命令

解决问题的第一个想法是先提取我们要查看的行,然后对这些行执行grep

我们可以使用tail 命令“ tail -n +x input ”从第x行到文件末尾取行。因此,例如,我们可以从第 6 行提取到app.log文件末尾的行:

$ tail -n +6 app.log 
[INFO] Application refreshed
[ERROR] RuntimeException: File not found: /foo/bar/newFile
... stack trace ...
[WARN] TemplateNotFoundException: Template PRETTY not found, loading the default template
[INFO] Default template loaded
[WARN] Cleanup job done with IOException: Disk is full

接下来,让我们在上面的输出中执行 grep命令以获取所需的日志条目:

$ tail -n +6 app.log | grep 'Exception'
[ERROR] RuntimeException: File not found: /foo/bar/newFile
[WARN] TemplateNotFoundException: Template PRETTY not found, loading the default template
[WARN] Cleanup job done with IOException: Disk is full

如我们所见,上面的命令已经解决了这个问题。我们得到了预期的日志条目。

但是,有时,我们希望执行带有*-n*选项的 grep命令 来打印每个匹配项的行号。例如,这有助于我们定位带有“ERROR”的日志条目,并仔细查看堆栈跟踪以分析原因。因此,让我们使用*-n*选项执行命令:

$ tail -n +6 app.log | grep -n 'Exception'
2:[ERROR] RuntimeException: File not found: /foo/bar/newFile
4:[WARN] TemplateNotFoundException: Template PRETTY not found, loading the default template
6:[WARN] Cleanup job done with IOException: Disk is full

正如我们所见,这一次,该命令打印了匹配行的行号。但是,由于我们将 tail命令的输出通过管道传输到grep因此grep命令报告的行号并不是原始输入文件中的实际行号

当然,我们可以通过这个计算得到实际的行号:LINE_NO_BY_GREP + 6 – 1。例如,第一个匹配“ RuntimeException… ”位于 app.log文件中的第 2 + 6 – 1 = 7 行。

此计算有效,但如果我们正在处理大型输入文件,则可能会很不方便

接下来,让我们看看我们是否可以从原始文件中获取所需的匹配行以及实际的行号。

4. 使用 awk命令

awk 是命令行文本处理的有力武器。使用awk命令,我们可以一键解决问题:

$ awk 'NR >= 6 && /Exception/{ print NR, $0 }' app.log
7 [ERROR] RuntimeException: File not found: /foo/bar/newFile
9 [WARN] TemplateNotFoundException: Template PRETTY not found, loading the default template
11 [WARN] Cleanup job done with IOException: Disk is full

上面的 awk命令非常简单。如果两个条件都满足,它会打印一行及其行号:

  • NR >= 6 – 其行号必须大于或等于六。
  • /Exception/ – 该行必须包含单词*“Exception”*。

正如我们在输出中看到的那样,我们得到了包含单词*“Exception”*的所需行。此外,输出中的行号是 app.log文件中的实际行号。

5. 使用 sed命令

sed 是另一个方便的命令行实用程序,用于在 Linux 中处理文本。首先,我们来看看sed命令是如何解决问题的:

$ sed -n '6,${ /Exception/{=;p} }' app.log
7
[ERROR] RuntimeException: File not found: /foo/bar/newFile
9
[WARN] TemplateNotFoundException: Template PRETTY not found, loading the default template
11
[WARN] Cleanup job done with IOException: Disk is full

如输出所示,sed已经解决了这个问题。但输出格式与我们使用awk得到的略有不同:匹配的行号和匹配的行在两个连续的行中

最后,让我们通过紧凑的单线来了解它是如何工作的:

  • sed -n – 禁止自动打印 – 换句话说,我们将自行控制何时打印一行
  • 6,$ - 定义从第 6 行到文件末尾的地址
  • /Exception/{… } – 如果一行在上面的地址中并且与*/Exception/模式匹配,则将执行{…}*操作
  • {=;p} - 我们在这里执行两个操作:打印当前行号(=)和打印当前行(p