Linux grep / rg / glob 命令是做什么用的?
grep 搜文件内容、rg 是 grep 的现代升级、glob 匹配文件名——三者名字长得像,但 各管一摊。
- grep:POSIX 自带文本搜索,
-r/-n/-i/-E是高频选项 - rg (ripgrep):Rust 写的现代版,默认跳过 .gitignore 和二进制
- glob:shell 通配符(
*/?/[...]),没加引号会被 shell 展开 - globstar:
shopt -s globstar后,**才递归匹配任意层级 - find -name:find 自己的类 glob 语法,不走 shell 展开
- 别乱混:
grep *.js被 shell 拆成多文件,只搜当前目录
三个工具各管什么
| 工具 | 搜的对象 | 是不是命令 | 默认行为 |
|---|---|---|---|
| grep | 文件内容 | 是(POSIX 自带) | 只搜当前文件,要 -r 才递归 |
| rg | 文件内容 | 是(要装) | 自动递归,跳过 .gitignore 和二进制 |
| glob | 文件名 | 不是,是 shell 功能 | 命令运行前由 shell 自动展开 |
glob 不是命令,是 bash/zsh 自带的 通配符匹配规则——你按下回车之前,shell 就把 * ? 这些先展开成实际的文件名,再喂给命令。所以 grep "TODO" *.js 里的 *.js 不是 grep 处理的,是 shell 处理的。
grep:经典文本搜索
是 POSIX 标准工具,所有 Linux/macOS/Unix 发行版都有,不用装。基础用法一行:
grep "pattern" file.txt
日常 90% 的场景用这几个选项就够:
| 选项 | 作用 |
|---|---|
-r | 递归子目录 |
-n | 显示行号 |
-i | 忽略大小写 |
-v | 反向(输出不匹配的行) |
-c | 统计匹配行数 |
-l | 只列出文件名 |
-E | 启用扩展正则(等同于 egrep) |
-w | 全词匹配 |
-A/-B/-C N | 匹配行后/前/上下 N 行 |
--include="*.js" | 只搜特定后缀 |
--exclude-dir=node_modules | 排除目录 |
实战:搜项目里所有 TODO:
grep -rn --include="*.js" --exclude-dir=node_modules "TODO" src/
Git 项目用
git grep:只搜 git 跟踪的文件,比系统 grep 快,但功能跟 grep 一致。
最容易踩的坑:shell 会提前展开你的通配符
grep "TODO" *.js
按下回车的瞬间,shell 会先把 *.js 展开成 a.js b.js c.js(假设当前目录有这三个文件),然后命令变成:
grep "TODO" a.js b.js c.js
只在当前目录生效,不会递归到子目录——很多人以为是 grep 在搜所有 JS 文件,其实是 shell 在帮你"挑文件",grep 根本不知道有 *.js 这种语法。
想递归搜子目录别用 glob,让 grep 自己来:
# 错的:shell 展开后还是只在当前目录
grep -r "TODO" *.js
# 对的:让 grep 自己递归
grep -rn "TODO" --include="*.js" .
下节介绍的 rg 用 -g "*.js" 限制文件类型,能一行搞定同样的事。
rg (ripgrep):现代首选
ripgrep 是用 Rust 写的 grep 替代品,作者 BurntSushi。装一下:
brew install ripgrep # macOS
apt install ripgrep # Ubuntu 20.04+
相对 grep 的核心优势:
- 跳过
.gitignore:默认不搜node_modules、dist、target,不会卡死 - 跳过二进制:不会把 PNG/PDF 当文本乱匹配
- 自动递归:不需要
-r,默认就递归 - 并行搜索:Rust + 多线程,比 grep 快几倍
- 智能大小写:搜索词全小写忽略大小写,含大写严格匹配
- 文件类型过滤:
-t js-t rust-t py不用写 glob - Unicode 默认正确:中文匹配不会乱
实战对比:
# grep:手动排除 node_modules + -r
grep -rn --include="*.js" --exclude-dir=node_modules "TODO" .
# rg:默认就跳过 node_modules
rg "TODO" -t js
我之前用 grep 搜一个 monorepo,
node_modules卡了 30 秒;换成 rg 一秒出结果。这是装 ripgrep 最直接的理由。
偶尔需要"无视 .gitignore 全搜一遍",比如查 secret 是否泄漏:
rg -uu "secret"
-uu 是 "no ignore",禁用所有 ignore 规则。
VSCode 的全局搜索(Cmd+Shift+F)底层用的就是 ripgrep,所以它搜得快。
glob:shell 通配符
glob 是 shell 在命令执行前做的 文件名展开,跟 grep / rg 没关系。三种核心通配符:
| 通配符 | 含义 |
|---|---|
* | 任意字符(不含 /) |
? | 单个字符 |
[abc] | 字符类,匹配 a/b/c 任一 |
[a-z] | 字符范围 |
日常例子:
ls *.js # 当前目录所有 .js
ls ?.txt # 当前目录所有 1 个字符的 .txt
ls [A-Z]*.md # 当前目录首字母大写的 .md
ls src/*.js # src/ 下(不含子目录)的 .js
globstar:让 ** 真正递归
bash 默认 ** 跟 * 一样不跨目录。要让它递归匹配任意层级:
shopt -s globstar
ls src/**/*.js # src 下所有子目录的 .js
zsh 默认就支持 **,不用额外开。
find -name 不是 glob
最后 澄清一个常被搞混的:find -name。-name 后面的模式长得像 glob,但 是 find 自己在内部匹配,不走 shell 展开:
find . -name "*.js" # shell 看到引号不展开,find 自己匹配
find . -name '*.js' # 同上
find . -name *.js # 无引号,shell 会展开,但结果一样(如果当前目录没 .js)
所以 find -name 的引号是 保险 不是必须——加引号只是告诉 shell "别动这个模式"。
实战组合:
# find 找文件 + grep 搜内容(传统写法)
find . -name "*.js" -not -path "*/node_modules/*" -exec grep -n "TODO" {} +
# rg 一行搞定(现代写法)
rg "TODO" -t js
小结
- grep:POSIX 自带,能用但得手动
--exclude-dir排 node_modules - rg:现代首选,装上 ripgrep 后基本可以扔掉 grep
- glob:shell 通配符,命令运行前被 shell 自动展开,记得加引号
日常排查代码,装上 ripgrep 就完事了,grep 只在远程服务器没法装东西时用一下。