Contents

在匹配特定条件的文件上使用grep

1. 概述

在 Linux 命令行中,grep 是我们用来在文件中搜索文本的便捷实用程序。但是,grep无法先根据特定条件过滤文件,然后再检查其内容。

我们在日常工作中经常有这个需求,比如在一个目录下递归地搜索所有 *.txt 文件中的一些文本,或者在所有名称中包含时间戳的文件中搜索一些模式。

在本教程中,我们将看到如何对一组过滤文件执行grep

2. 示例文件

为了更容易理解教程中的命令,让我们创建一个示例目录结构进行测试:

$ tree test 
test
├── app
│   ├── change_log.log
│   └── readme.md
├── archive
│   ├── app_20200101.log.archive
│   └── app_20200201.log.archive
└── log
    ├── app_20200301.log
    ├── app_20200401.log
    └── app.log
3 directories, 7 files

我们测试了三个子目录,每个目录都有几个文件。

**在这个例子中,test目录下的所有文件都包含“ Exception”这个词。**我们可以通过grep命令验证这一事实:

$ grep -R 'Exception' test
test/app/change_log.log:Fix the NullPointerException Problem when calling external APIs
test/app/readme.md: - Exceptions are well handled
test/archive/app_20200101.log.archive:DATETIME - [Error] NullPointerException has Occurred
test/archive/app_20200201.log.archive:DATETIME - [Error] NullPointerException has Occurred
test/log/app_20200401.log:DATETIME - [Error] ClassCastException has Occurred
test/log/app_20200301.log:DATETIME - [Error] SQLException has Occurred
test/log/app.log:DATETIME - [Error] NullPointerException has Occurred

现在,让我们以这个 test 目录为例,说明如何在过滤后的文件集上执行grep命令。

3. grep仅在具有某些扩展名的文件上

3.1. 使用grep命令的 –include=GLOB选项

首先,让我们看看如何仅在具有 *.log 扩展名的文件中搜索模式“ Exception ”:

$ grep -R --include=*.log 'Exception' test
test/app/change_log.log:Fix the NullPointerException Problem when calling external APIs
test/log/app.log:DATETIME - [Error] NullPointerException has Occurred
test/log/app_20200401.log:DATETIME - [Error] ClassCastException has Occurred
test/log/app_20200301.log:DATETIME - [Error] SQLException has Occurred

如上面的输出所示, grep命令只检查 文件扩展名为 “log” 的文件。

我们使用了两个选项来告诉 grep命令这样做:

  • -R将递归搜索文件。也就是说,它将在任何被测子目录中的文件中搜索给定的模式
  • –include=*.log是 –include=GLOB选项的一个示例,它告诉grep仅搜索其基本名称与给定GLOB表达式匹配的文件**

此外,我们可以使用多个 –include=GLOB选项来要求 grep命令搜索匹配多个扩展名的文件。

现在,让我们在 *.log 和 *.md 文件中搜索单词*“Exception”* :

$ grep -R --include=*.log --include=*.md 'Exception' test
test/app/readme.md: - Exceptions are well handled
test/app/change_log.log:Fix the NullPointerException Problem when calling external APIs
test/log/app.log:DATETIME - [Error] NullPointerException has Occurred
test/log/app_20200401.log:DATETIME - [Error] ClassCastException has Occurred
test/log/app_20200301.log:DATETIME - [Error] SQLException has Occurred

正如我们所见,文件test/app/readme.md 也出现在输出中。

或者,我们也可以使用一个*–include*选项,让 GLOB 表达式包含多个扩展名。以下命令将打印相同的输出:

$ grep -R --include=*.{log,md} 'Exception' test

3.2. 使用 Bash GLOB 过滤文件

我们了解到grep的*–include=GLOB*选项可以指示 grep仅搜索具有特定扩展名的文件。

或者,如果我们的 Bash 版本是 4 或更高,我们可以使用 Bash 的globstar ( ** ) 递归匹配文件

$ grep 'Exception' test/**/*.log
test/app/change_log.log:Fix the NullPointerException Problem when calling external APIs
test/log/app_20200301.log:DATETIME - [Error] SQLException has Occurred
test/log/app_20200401.log:DATETIME - [Error] ClassCastException has Occurred
test/log/app.log:DATETIME - [Error] NullPointerException has Occurred

这一次,  grep命令看起来很简单。它只负责在给定文件上搜索“Exception”模式,而 Bash GLOB 负责过滤文件。

此外,我们还可以扩展 Bash GLOB 表达式以匹配多个文件扩展名。让我们对“ *.log ”和“ *.md ”文件进行相同的搜索:

$ grep 'Exception' test/**/*.{log,md}
test/app/change_log.log:Fix the NullPointerException Problem when calling external APIs
test/log/app_20200301.log:DATETIME - [Error] SQLException has Occurred
test/log/app_20200401.log:DATETIME - [Error] ClassCastException has Occurred
test/log/app.log:DATETIME - [Error] NullPointerException has Occurred
test/app/readme.md: - Exceptions are well handled

如果我们检查上面的输出,我们可以看到*“test/app/readme.md”*也在列表中。

4. 结合find命令和grep命令

我们已经学会了如何只搜索具有特定扩展名的文件,这是一个非常常见的用例。但是,有时,我们希望搜索按不同条件过滤的文件。

例如,我们可能只想在某个用户拥有的文件上搜索一些文本,或者文件名与给定模式匹配的文件,或者最后访问时间早于或晚于时间戳的文件,等等。

当我们想使用各种标准过滤文件时,我们不应该忘记我们的好朋友:find 命令。

在本教程中,我们不会深入探讨如何使用复杂标准查找文件的详细信息。相反,我们将专注于如何使grep和 find一起工作。也就是说,grep将搜索 find结果中的文件。

我们将在文件名带有时间戳的文件上搜索单词“ Exception ”,例如app_20200301.logapp_20200101.log.archive

首先,让我们看一下获取这些文件的find命令:

$ find test -type f -a -regextype 'egrep' -regex '.*_[0-9]{8}.*' 
test/archive/app_20200201.log.archive
test/archive/app_20200101.log.archive
test/log/app_20200401.log
test/log/app_20200301.log

如上面的输出所示,我们使用find的*-regex*选项来过滤我们想要搜索的文件。

现在,让我们看看如何让 grep处理 find命令的结果。

4.1. 使用 find的*-exec*动作

使grepfind的结果起作用的一种方法是使用 find-exec 操作:

$ find test -type f -a -regextype 'egrep' -regex '.*_[0-9]{8}.*' -exec grep -H "Exception" '{}' \;
test/archive/app_20200201.log.archive:DATETIME - [Error] NullPointerException has Occurred
test/archive/app_20200101.log.archive:DATETIME - [Error] NullPointerException has Occurred
test/log/app_20200401.log:DATETIME - [Error] ClassCastException has Occurred
test/log/app_20200301.log:DATETIME - [Error] SQLException has Occurred

每个找到的文件都将在*-exec*操作中填充占位符“ {} ”。这样,grep命令将搜索每个找到的文件。

该命令需要以“ ; ”结尾。这是因为它表示grep命令的终止。

**值得一提的是,-exec操作将在find命令找到的每个文件上执行。**换句话说,如果 find 提供了一百万个文件,我们将运行 -exec 操作一百万次。

显然,如果我们想在大量文件中搜索一些文本, -exec操作的性能不会很好。

接下来,让我们看看如何让find和 grep更高效地工作。

4.2. 使用 xargs结合findgrep

与 find的*-exec*操作不同,/xargs 会将找到的文件构建到包中,并尽可能少地通过命令运行它们

与*-exec*操作相比,这是一个很大的优势,尤其是当我们搜索大量文件时。

最后,让我们使用 xargs命令将findgrep结合起来:

$ find test -type f -a -regextype 'egrep' -regex '.*_[0-9]{8}.*' | xargs grep "Exception" 
test/archive/app_20200201.log.archive:DATETIME - [Error] NullPointerException has Occurred
test/archive/app_20200101.log.archive:DATETIME - [Error] NullPointerException has Occurred
test/log/app_20200401.log:DATETIME - [Error] ClassCastException has Occurred
test/log/app_20200301.log:DATETIME - [Error] SQLException has Occurred