Dev essentials: how to search code using grep title. Learn how to use fast and powerful search in the terminal subtitle. A vibrant gradient behind artwork of a terminal with some example commands and a keyboard.

开发人员必备:如何使用 grep 搜索代码

阅读时间 9 分钟

无论您处于 Web 开发的哪个阶段,您都会在代码中搜索文本或模式。您可能想搜索一个变量、一个错误消息的来源、一个 CSS 类、HTML 或 markdown 源代码中使用的图片、应用程序的日志——这个列表无穷无尽。

在代码和文本中进行搜索是您在构建 Web 时执行的最常见任务之一。您可能会使用集成开发环境 (IDE) 来搜索项目文件、操作系统的文件搜索,甚至可以通过 GitHub 或其他代码托管服务进行代码搜索。您很快就会意识到,您需要一个高效的工具来帮助您完成各种搜索;这时 grep 就派上用场了。

在这篇文章中,我们将了解 grep 是什么、它能做什么,以及为什么我认为它是您在处理代码时最强大的命令行工具之一。如果您不熟悉 grep,这篇文章将涵盖基础知识、一些常见示例,包括我每天如何使用它,以及为什么我认为它是开发人员必不可少的工具。那么,让我们深入了解一下,看看如何让 grep 助您一臂之力!

什么是 grep?

grep 是一个 命令行工具,它允许您使用 正则表达式 来搜索模式。基本用法如下所示:

bash
grep pattern file

grep 有不同的版本,具有不同的选项和功能,但核心行为在大多数情况下是相同的。您可以通过运行 grep --version 来查看您安装的是哪个版本。

bash
grep --version
# (on macOS)
# grep (BSD grep, GNU compatible) 2.6.0-FreeBSD

专业提示:如果您卡住了或需要提醒 grep 如何工作,请使用 man (manual) 命令查看文档,并找出您可以使用的选项。

bash
man grep
# you can exit by pressing "q" 😁

grep 入门

让我们通过一些实际示例来学习 grep,使用 mdn/content GitHub 仓库。要跟随本文中的示例,请克隆 mdn/content 仓库并 cd 进入目录(如果您不使用 git,也可以从 GitHub 下载 zip 文件)。

bash
git clone https://github.com/mdn/content.git
cd content

进入内容仓库的 shell 后,您可以搜索一些关键词并查看 grep 的输出。例如,在这里我正在 CONTRIBUTING.md 文件中搜索单词 "Communication",在 package.json 文件中搜索单词 "node",在 CODE_OF_CONDUCT.md 文件中搜索短语 "Mozilla Community"。

bash
grep "Communication" CONTRIBUTING.md
# [get in touch with us]: https://mdn.org.cn/en-US/docs/MDN/Community/Communication_channels
grep "node" package.json
#    "node": ">=18.0.0"
grep "Mozilla Community" CODE_OF_CONDUCT.md
# [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).

如您所见,grep 的每个输出都是与搜索的单词或短语匹配的行或行列表。虽然您可以运行 grep Communication CONTRIBUTING.md 这样的命令并获得相同的输出,但建议将要搜索的模式用双引号括起来,以避免因空格或特殊字符被 shell 解释而引起任何问题。

这些示例对于检查 node 版本或我们指向社区以便他们联系我们的 URL 等内容非常有用。此时,我们知道如何在单个文件中搜索特定的单词或一组单词。

递归搜索

递归搜索意味着在多个文件、目录和子目录中搜索您的模式。您不仅想在单个文件中搜索模式,还想在整个树形结构中搜索。这时 grep 就开始变得非常有用。要进行递归搜索,请在模式前使用 -r 标志。

bash
grep -r "my pattern" ./directory

在搜索内容仓库的上下文中,我们可以搜索所有 markdown 文件中的关键词。

bash
grep -r "TOFU" ./files
# ./files/en-us/web/security/index.md:- TOFU# ./files/en-us/glossary/tofu/index.md:title: TOFU
# ./files/en-us/glossary/tofu/index.md:slug: Glossary/TOFU

输出显示 Web 安全页面 包含指向 TOFU 的术语表条目 的链接,并且该术语表条目与模式有多个匹配项,正如预期的那样。为了使输出更易读,我省略了一些其他匹配项,但总的来说,我们可以找到模式在多个文件中的出现,并发现它的使用位置。

在您的开发项目中,您很可能拥有已生成的目录,例如 node_modules 或构建目录,如 distbuild,您希望在对模式进行递归搜索时忽略这些目录。一些 grep shell 插件可以帮助 忽略版本控制目录,例如 .git,但最好还是知道在需要时如何显式地从搜索中忽略特定目录。

要忽略目录,您可以使用 --exclude-dir 选项。在此示例中,我将从当前目录开始递归搜索 "cli-progress",但排除 node_modules 目录。命令末尾的句点 . 是要搜索的路径,在这种情况下是当前目录。

bash
grep -r --exclude-dir="node_modules" "cli-progress" .
# ./yarn.lock:    cli-progress "^3.12.0"
# ./yarn.lock:cli-progress@^3.12.0:
# ...

这个 grep 命令的输出是一个不错的开始,但 yarn.lock 文件中存在许多额外的匹配项,有点分散注意力。让我们使用 --exclude 选项也忽略 yarn.lock 文件。

bash
grep -r --exclude-dir="node_modules" --exclude="yarn.lock" "cli-progress" .
# ./package.json:    "cli-progress": "^3.12.0",
# ./scripts/front-matter_linter.js:import cliProgress from "cli-progress";

这更有用了,因为我有两个相关的匹配项,并且可以一目了然地看出:

  • 我们正在使用 cli-progress 的版本 ^3.12.0
  • 我们在 front-matter_linter.js 脚本中将 cli-progress 导入为 cliProgress

如果我愿意,我可以继续我的调查,搜索 cliProgress 以查看它在脚本中的使用位置和方式。

在搜索内容仓库时,我经常需要检查关键词是否出现。但是,由于我不知道它是否是句子的开头、URL 的一部分,还是遵循了不同的大小写约定,我应该搜索什么模式?幸运的是,可以通过使用 grep 的 -i 标志进行不区分大小写的搜索来处理这种情况。

bash
grep -ri "github actions" ./files
# ./files/en-us/mdn/community/contributing/our_repositories/index.md:  A growing collection of reusable GitHub Actions for use on MDN Web Docs repositories.
# ...

忽略二进制文件

如果您正在搜索特定的字符串,并且项目中有二进制文件,您可能会遇到一些意外的匹配项。

bash
grep -ri "linux" ./files/en-us/web/http
# oh no:
# Binary file ./files/en-us/Web/HTTP/Guides/Content_negotiation/httpnegotiation.png matches

如果您要查找的字符串较短(两个或三个字符),并且存在大量二进制文件(如图片、PDF 或其他媒体文件),这可能会偶然发生。幸运的是,您可以使用 --binary-files 选项忽略二进制文件。

bash
grep -ri --binary-files=without-match "linux" ./files/en-us/web/http

您还可以使用 -I 标志来忽略二进制文件,但您可以自行决定是使用显式选项还是不显式。以下标志对您来说易读吗?还是您更喜欢显式?

bash
grep -riI "linux" ./files/en-us/web/http

将正则表达式与 grep 结合使用

当然,正则表达式是 grep 的核心,所以让我们看看如何使用它们来查找模式的变体。假设我很好奇我们是否有标题以数字开头的页面,但我不知道这些数字是什么。让我们通过匹配 \d 字符类转义 来获得一些想法。

bash
grep -r "title: \d\d\d" ./files/
# ./files//en-us/Web/HTTP/Reference/Status/307/index.md:title: 307 Temporary Redirect
# ./files//en-us/Web/HTTP/Reference/Status/300/index.md:title: 300 Multiple Choices
# ...

现在我知道我们有大量的页面遵循 HTTP 状态码的这种模式。接下来,我将查找使用已弃用宏的页面,特别是 {{SpecName}}{{spec2}} 宏。

我想使用像 SpecName|spec2 这样的正则表达式,它将 使用“或” 来匹配 SpecNamespec2。我的 grep 版本默认未启用此功能,因此我需要启用扩展正则表达式才能使用 -E 标志。

bash
grep -riE "SpecName|spec2" ./files
# files/en-us/mdn/writing_guidelines/howto/json_structured_data/index.md:The `{{SpecName}}` and `{{Spec2}}` macros ...

这很棒,我们找到的宏仅在我们的写作指南中,描述了如何替换它们。我们已经学会了如何使用更复杂的正则表达式,并且通过结合这些选项,我们构建了一个强大的搜索命令。

  • -r 用于递归搜索
  • -i 用于不区分大小写的搜索
  • -E 用于启用扩展正则表达式

将 Unix 管道与 grep 结合使用

在 Unix 世界中,有一个常见的约定,即程序只需做好一件事。这允许我们构建一种程序,它是一个命令的管道,将一个命令的输出作为另一个命令的输入。您可能会将这称为“进程间通信”,但其思想是使用 | 字符来管道化或链接命令。

bash
command1 | command2

为了探索管道,为什么不使用 grep 来找出我在 shell 历史记录中使用了多少次 grep 呢?让我们将三个命令结合起来,找出我在 shell 历史记录中运行了多少次 grep

bash
history | grep "grep" | wc -l
# 1164

要理解发生了什么,让我们逐步查看每个命令的输出。history 命令输出我运行过的所有命令的列表。

bash
history
#    1   ls
#    2   mkdir ~/Code
#    ... 10000+ lines later
# 10098  man grep

使用 grep "grep",只输出包含字符串 "grep" 的行。

bash
history | grep "grep"
#    91  grep -r "prettier"
#    92  grep -r "inline-size" .
#    ...
# 10098  man grep

为了得到完整的命令,我们将其管道传输到 wc -l (word count, lines) 来计算输出中的行数。

bash
history | grep "grep" | wc -l
# 1164, magic ✨

我们构建了一个小型程序,使用 Unix 管道执行以下操作:

  • history:输出我运行过的命令历史记录。
  • grep "grep":在 history 命令的输出中搜索字符串 "grep"。
  • wc -l:计算 grep "grep" 命令输出中的行数。

grep 的速度有多快?

我每天多次在 mdn/content 中搜索关键词、API、文档标题或代码片段中某个功能的使用情况。使用 grep 在大约 13,500 个文件中搜索一个模式,大约需要 0.7 秒。

bash
time grep -r "\`\`\`plain" files/en-us/web/ | wc -l
     252
grep -r  0.43s user 0.29s system 99% cpu 0.730 total
wc -l  0.00s user 0.00s system 0% cpu 0.729 total

当然,还有更快的替代方案,例如 ripgrepag,但 grep 对于大多数用例来说通常足够快。在下一节中,我将解释为什么我认为学习 grep 比这些替代方案更值得。

为什么要学习 grep?

如果这篇文章仍然没有说服您 grep 是您值得花费时间学习的最佳代码搜索工具,那么以下是我认为它值得的原因:

  1. grep 可以相当快速地处理大量文本的搜索。
  2. 它无处不在。在您 SSH 或登录的服务器或机器上,它很可能默认可用。
  3. 您的 shell 历史记录允许您重用之前的命令或将其用作起点。
  4. 初学者可以从简单的用例开始,高级模式可以随着时间的推移学习。
  5. 您可以将其他程序的输出定向到 grep,并创建强大的管道。
  6. 您将学习正则表达式,它在编程语言和其他工具中非常有用。

总结

grep 对我来说非常有用,以至于我习惯性地输入 grep -r 来准备给定模式的搜索。我认为学习 grep 将是提高编码、调试、检查新项目或对项目进行快速分析时生产力的最佳步骤之一。

如果您认为 grep 可能对您感兴趣,我们最近更新了正则表达式参考页面,这将帮助您在搜索时检查模式。最近的博客文章 MDN 上关于 JavaScript 正则表达式的新参考页面 描述了我们对文档所做的更新,以帮助您找到所需的内容并理解语法。

如果您喜欢这篇文章,请在我们的社区 DiscordGitHub 上告诉我们,以分享您的想法、提出问题或只是打个招呼!如果您觉得这篇文章对您有帮助,如果您正在使用 grep 的其他我未在此提及的方式,或者我遗漏了您认为重要的事情,请告知我们。祝您 /(hack|grep)ing/ 愉快!🖥️