
开发者必备:如何使用 grep 搜索代码
无论你在 Web 开发旅程中的哪个阶段,你都会搜索代码中的文本或模式。你可能想要搜索变量、错误消息的来源、CSS 类、HTML 或 Markdown 源代码中使用的图像、应用程序日志 - 不胜枚举。
搜索代码和文本是你在构建 Web 应用程序时最常见的任务之一。你可能使用集成开发环境 (IDE) 来搜索项目文件,使用操作系统的文件搜索,甚至通过 GitHub 或其他代码托管服务进行代码搜索。你很快就会意识到你需要一个高效的工具来帮助你完成各种搜索;这就是 grep 的用武之地。
在这篇文章中,我们将了解什么是 grep,它能做什么,以及为什么我认为它是你在处理代码时最强大的命令行工具之一。如果你不熟悉 grep,这篇文章将涵盖基础知识、一些常见示例,包括我每天如何使用它,以及为什么我认为它是开发者的必备工具。所以让我们深入了解并找出如何让 grep 为你服务!
什么是 grep?
grep 是一种 命令行工具,它允许你使用 正则表达式 来搜索模式。基本用法如下
grep pattern file
有不同版本的 grep 可用,具有不同的选项和功能,但核心行为基本相同。你可以运行 grep --version
来检查你安装了哪个版本
grep --version
# (on macOS)
# grep (BSD grep, GNU compatible) 2.6.0-FreeBSD
专业提示:如果你遇到困难或需要了解 grep 的工作原理,请使用 man
(手册)命令查看文档并找出你可以使用的选项
man grep
# you can exit by pressing "q" 😁
grep 入门
让我们使用 mdn/content GitHub 仓库 的一些实际示例来学习 grep。要跟随这篇文章中的示例,请克隆 mdn/content
仓库并 cd
到目录中(如果你不使用 git,也可以从 GitHub 下载仓库的压缩文件)。
git clone https://github.com/mdn/content.git
cd content
一旦你在 shell 中的 content 仓库中,你就可以搜索一些关键字并查看 grep 的输出。例如,我在 CONTRIBUTING.md
文件中搜索 "Communication" 一词,在 package.json
文件中搜索 "node" 一词,在 CODE_OF_CONDUCT.md
文件中搜索 "Mozilla Community" 短语。
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 解释空格或特殊字符造成的任何问题。
这些示例对于检查诸如节点版本或我们指向社区的 URL(以便他们可以与我们联系)等内容非常有用。此时,我们知道如何在单个文件中搜索特定的词或词组。
递归搜索
递归搜索意味着搜索多个文件、目录和子目录中的模式。你不仅想在一个文件中搜索模式,还想在整个树中搜索模式。这就是 grep 开始变得真正有用的地方。要进行递归搜索,请在模式之前使用 -r
标志
grep -r "my pattern" ./directory
在 content 仓库的搜索环境中,我们可以搜索所有 Markdown 文件中的某个关键字
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
或 dist
或 build
等构建目录,你希望在对模式进行递归搜索时忽略这些目录。一些 grep shell 插件可以帮助 忽略版本控制目录(例如 .git
),但始终了解如何在需要时显式地忽略搜索中的某些目录是件好事。
要忽略目录,可以使用 --exclude-dir
选项。在本例中,我从当前目录开始递归搜索 "cli-progress",但从搜索中排除 node_modules
目录。命令末尾的点 .
是要搜索的路径,在本例中是当前目录
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
文件
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
以查看它在脚本中的使用位置和方式。
执行不区分大小写的搜索
在搜索 content 仓库时,我经常需要检查某个关键字是否出现。但是,由于我不知道它是句子的第一个词、URL 的一部分,还是遵循其他大小写约定,我应该搜索什么模式呢?幸运的是,可以使用 grep 的 -i
标志来执行不区分大小写的搜索,从而解决此问题
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.
# ...
忽略二进制文件
如果你正在搜索特定的字符串,并且项目中有二进制文件,你可能会遇到一些意外匹配
grep -ri "linux" ./files/en-us/web/http
# oh no:
# Binary file ./files/en-us/web/http/content_negotiation/httpnegotiation.png matches
如果你的搜索字符串很短(两个或三个字符),并且有很多二进制文件(例如图像、PDF 或其他媒体文件),则这种情况可能会偶然发生。幸运的是,你可以使用 --binary-files
选项忽略二进制文件
grep -ri --binary-files=without-match "linux" ./files/en-us/web/http
你也可以使用 -I
标志忽略二进制文件,但最终取决于你是否希望显式地使用选项。你认为下面的标志易读吗,还是你更喜欢显式地使用选项?
grep -riI "linux" ./files/en-us/web/http
在 grep 中使用正则表达式
当然,正则表达式是 grep 的核心,所以让我们看看如何使用它们来查找模式的变化。假设我对页面标题以数字开头感兴趣,但我不确定数字是什么。让我们通过匹配 \d
字符类转义 来了解一下
grep -r "title: \d\d\d" ./files/
# ./files//en-us/web/http/status/307/index.md:title: 307 Temporary Redirect
# ./files//en-us/web/http/status/300/index.md:title: 300 Multiple Choices
# ...
现在我知道我们有很多页面遵循 HTTP 状态代码的这种模式。接下来,我想查找使用弃用宏的页面,特别是 {{SpecName}}
和 {{spec2}}
宏。
我想使用类似 SpecName|spec2
的正则表达式,它将匹配 SpecName
或 spec2
使用析取。我的 grep 版本默认情况下不支持此功能,因此我需要使用 -E
标志启用扩展正则表达式来使用此功能
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 世界中,有一个常见的程序约定,即每个程序都擅长执行一项任务。这使我们能够构建一种程序,它是一系列命令的管道,将一个命令的输出作为另一个命令的输入。你可能会看到它被称为 "进程间通信",但其理念是使用 |
字符将命令连接起来或串联起来。
command1 | command2
出于探索管道的目的,为什么不使用 grep 来找出我使用 grep 的频率?让我们将三个命令组合在一起,找出我在 shell 历史记录中使用 grep
的次数
history | grep "grep" | wc -l
# 1164
要了解正在发生的事情,让我们逐步查看每个命令的输出。history
命令输出我运行的所有命令的列表
history
# 1 ls
# 2 mkdir ~/Code
# ... 10000+ lines later
# 10098 man grep
使用 grep "grep"
,只输出包含字符串 "grep" 的行
history | grep "grep"
# 91 grep -r "prettier"
# 92 grep -r "inline-size" .
# ...
# 10098 man grep
为了获得完整的命令,我们将管道连接到 wc -l
(字数统计,行数)以计算输出中的行数
history | grep "grep" | wc -l
# 1164, magic ✨
我们已经使用 Unix 管道构建了一个小程序,它执行以下操作
history
:输出我运行的命令历史记录grep "grep"
:在history
命令的输出中搜索字符串 "grep"wc -l
:计算grep "grep"
命令输出中的行数
grep 的速度有多快?
我每天都会多次搜索 mdn/content
中关键字、API、文档标题或代码段中功能的用法。在大约 13,500 个文件中使用 grep 搜索模式大约需要 0.7 秒
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
当然,还有像 ripgrep 和 ag 这样的更快替代方案,但 grep 通常对于大多数用例来说已经足够快了。在下一节中,我将解释为什么我认为学习 grep 比这些替代方案更有价值。
为什么你应该学习 grep?
如果这篇文章还没有让你相信 grep 是你可以花时间学习的最佳代码搜索工具,那么以下是我认为它值得学习的原因。
- grep 可以相当快地处理大量文本的搜索。
- 它无处不在。它可能默认在您 SSH 或登录的服务器或机器上可用。
- 您的 shell 历史记录允许您重用以前的命令或将它们用作起点。
- 初学者可以从简单的用例开始,高级模式可以随着时间的推移学习。
- 您可以将其他程序的输出直接输入 grep 并创建强大的管道。
- 您将学习正则表达式,它们在编程语言和其他工具中很有用。
总结
grep 对我来说非常有用,以至于输入 grep -r
来准备对给定模式的搜索已成为我的肌肉记忆。我认为学习 grep 将是您在编写代码、调试、检查新项目或对项目进行快速分析时提高生产力的最佳步骤之一。
如果您认为 grep 可能对您来说很有趣,我们最近更新了我们的正则表达式参考页面,这些页面将帮助您在搜索时检查模式。最近的博客文章 MDN 上关于 JavaScript 正则表达式的全新参考页面 描述了我们对文档所做的更新,以帮助您找到所需的内容并理解语法。
如果您喜欢这篇文章,请在我们的社区 Discord 或 GitHub 上 告诉我们,分享您的想法、提出问题或只是打个招呼!请告诉我们这篇文章是否对您有帮助,您是否还有其他使用 grep 的方式我还没有在这里提到,或者我是否错过了您认为重要的内容。祝您 /(hack|grep)ing/
快乐!🖥️