为什么你的 grep 越用越慢
每个常年和服务器打交道的人,都有过这样的经历:线上出了故障,要在一个几 GB 的 Nginx access.log 或者几十万行的代码仓库里找一条线索,敲下 grep -r "关键词" .,然后盯着屏幕等上半分钟。更糟的是,一旦目录里混进了日志文件、二进制文件或者 node_modules,grep 会把它们全扫一遍,结果又慢又乱。
这并不是你的机器不行,而是 grep、find、cat 这三个命令都诞生于上个世纪,它们的默认行为是为"正确"服务的,而不是为"快"和"好用"服务的。过去十年里,Rust 生态里冒出了一批专门用来替代它们的新工具,核心思路只有两个:默认忽略你不关心的东西,以及默认并行。这篇文章把最实用的三件套——ripgrep、fd、bat——讲清楚,让你以后在服务器上找东西的时间至少砍掉一半。
ripgrep:替代 grep,默认快十倍
安装
Debian / Ubuntu 直接装:
apt update
apt install -y ripgrep # 命令名是 rg
rg --versionCentOS / Rocky 走 EPEL:
yum install -y epel-release
yum install -y ripgrep如果系统源太旧,直接从 GitHub 下静态二进制,扔进 /usr/local/bin 即可,没有任何依赖。
它为什么快
ripgrep(命令名 rg)基于 Rust 的 regex 引擎,自带多线程,底层用的是内存映射加 SIMD 扫描。官方基准里,在一个 Linux 内核源码树上搜一个词,rg 通常比 grep -r 快 3 到 10 倍。更重要的是,它默认就会跳过 .gitignore 里列出的文件和目录,也会跳过隐藏文件和二进制文件。这意味着你在项目目录里搜东西,不会再被 .git、node_modules、vendor 这些噪音淹没。
最常用的几个姿势
# 递归搜当前目录,默认智能忽略
rg "upstream_timeout"
# 只搜 php 文件
rg -t php "mysqli_connect"
# 列出文件类型简写
rg --type-list | head
# 显示行号(默认已开)、显示列号
rg --column "session_start"
# 搜不可忽略的目录(比如全部文件,含隐藏)
rg -uu "关键词"
# 只列出匹配的文件名,不打印内容(配合管道超有用)
rg -l "TODO" | xargs sed -n '1p'
# 统计每个文件匹配了多少行
rg -c "error" /var/log/nginx/实战:在 Nginx 日志里找可疑 IP
假设网站被刷了,要找出访问量最高的前 20 个 IP。传统写法是 awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20,但当日志有几十 GB 时,瓶颈其实在读文件。用 rg 先做一遍筛选再交给 awk,可以只处理你关心的那部分:
# 只要 5xx 的请求,再统计 IP
rg " 5[0-9][0-9] " access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20
# 找出访问了某个路径最多的人
rg "\"GET /api/login" access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head这里的关键是 rg 的过滤足够快,后面的 awk 才有意义。如果先 awk 再过滤,等于把整个文件都解析了一遍。
几个必须知道的坑
rg默认不搜隐藏文件(以.开头的)。要搜.env、.htaccess得加--hidden,再配合--no-ignore才能无视 gitignore。- 搜二进制文件会提示
binary file matches,用-a强制当文本处理。 - 正则语法默认是 Rust 风格,和 PCRE 略有差别。复杂正则可以加
-P切到 PCRE2 引擎。 - 替换文件内容不建议用
rg做(它没有原地替换),要批量替换用sd或者传统的sed -i。
fd:替代 find,语法短到不用查手册
find 的强大毋庸置疑,但它的语法是出了名的反人类:想找当前目录下所有扩展名是 .log 且修改时间在一天内的文件,你得写 find . -name "*.log" -mtime -1,而想排除某个目录又要加一长串 -not -path。fd 把这件事变得像说话一样。
安装
apt install -y fd-find # 注意:Debian 上命令名叫 fdfind
# 或者建立软链
ln -s $(which fdfind) /usr/local/bin/fdRust 官方包和 Homebrew 装出来的命令名就是 fd,只有 Debian/Ubuntu 为了避让一个同名老包改叫 fdfind,这是新手最容易懵的一点。
对照表:find 怎么写,fd 怎么写
| 需求 | find | fd |
|---|---|---|
| 找所有 .log 文件 | find . -name "*.log" | fd -e log |
| 找名字含 config 的文件 | find . -name "*config*" | fd config |
| 排除 node_modules | find . -name "*.js" -not -path "*/node_modules/*" | fd -e js -E node_modules |
| 只找目录 | find . -type d -name "cache" | fd -t d cache |
| 只找可执行文件 | find . -type f -perm -u+x | fd -t x |
| 一天内改过的文件 | find . -mtime -1 | fd --changed-within 1d |
看最后一行的差距:fd --changed-within 1d 可以直接写 2h、30min,find 的 -mtime 只认天数,要精确到小时还得用 -mmin 自己换算。
实战:找大文件、批量清理
# 找出所有超过 100M 的日志文件
fd -e log --size +100M
# 找出所有 .tmp 文件并删除(先打印确认,再执行)
fd -e tmp -x rm -v
# 找到所有 .php 文件,对每一个执行语法检查
fd -e php -x php -l
# 找所有空的目录
fd -t d --size -1M --max-depth 3 -x rmdir -v-x 后面的命令会对每个结果执行一次,{} 代表文件名占位符,如果不写就自动追加到末尾。-X(大写)则是把所有结果一次性拼成一条命令执行,适合 tar、rm 这种可以一次吃多个参数的场景。
bat:替代 cat,带高亮还会自动分页
cat 的问题在于它把文件原样吐出来,没有行号、没有语法高亮、没有分页,看一个 500 行的配置文件要来回滚。bat 把这三件事都做了,而且在输出被重定向到管道时,会自动退化成纯文本,不会污染你的脚本。
安装
apt install -y bat
# Debian 上新版叫 batcat,同样建软链
ln -s $(which batcat) /usr/local/bin/bat常用场景
# 看一个配置文件(自带行号 + 高亮 + 分页)
bat /etc/nginx/nginx.conf
# 只显示某几行
bat -r 20:40 /etc/nginx/nginx.conf
# 去掉行号和装饰,纯粹为高亮
bat -pp /etc/mysql/my.cnf
# 看非文本文件时用 -A 显示不可见字符
bat -A /tmp/weird.txt
# 和 rg 联动:搜索并高亮上下文
rg -n "worker_connections" /etc/nginx/ | batbat 默认会调用 less 分页,如果输出被重定向到文件或管道,它会自动改为直接输出,这一点比很多人手动写 cat | less -R 省事得多。
让 bat 接管 man(可选但很香)
export MANPAGER="sh -c 'col -bx | bat -l man -p'"
# 加进 ~/.bashrc 后,man ls 会变成彩色带行号的页面把三件套串成一条工作流
单独用每个工具已经能提升效率,真正拉开差距的是把它们和管道串起来。下面是一个"线上故障排查"的典型组合,假设你要在一个老项目的代码里找是谁把数据库连接写死了:
# 1. 先用 fd 找出所有可能的配置文件
fd -e php -e env -e ini -e yml . config/ src/
# 2. 用 rg 搜可疑的关键字,只看文件名
rg -l "max_connections|persistent" --type-add 'conf:*.{ini,yml,env}' -t conf
# 3. 用 bat 带着上下文看这几个文件
bat -r 1:60 $(rg -l "max_connections" | head -1)再比如每天早上做一次日志巡检:
# 找出昨天的错误行,统计最常见的报错
rg " 500 " /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head
# 所有服务器上,改过一天的配置文件的清单(配合 fmt 输出更好读)
fd -e conf --changed-within 1d -x stat -c '%n %y'性能对比与选型建议
| 场景 | 推荐工具 | 理由 |
|---|---|---|
| 大仓库全量搜索 | rg | 并行 + 智能忽略,最快 |
| 需要复杂 Perl 正则 | rg -P 或 ag | PCRE2 兼容性更好 |
| 按名字/时间找文件 | fd | 语法短,支持排除 |
| 需要极复杂的条件组合 | find | 功能最全,脚本里更通用 |
| 看配置文件 | bat | 高亮 + 行号 |
| 脚本里纯文本读取 | cat / head | 无依赖,避免意外 |
需要强调的是:这三个新工具不是要你删掉老的。在写脚本、做无人值守任务、给别人的机器上用的时候,grep、find、cat 依然是最稳妥的选择,因为每台机器都有。它们的价值在于你手动操作、临时排查的那些时刻——那才是真正消耗时间的地方。我的建议是先在本地和跳板机上把别名配好,用顺了再考虑在服务器上也装上。
常见问题排查
1. rg 明明有这个词,却搜不到
最常见的原因是被 gitignore 忽略了,或者文件是隐藏文件。先加 rg -uu 试试;如果还是搜不到,用 rg --debug 看它到底扫了哪些文件,或者确认该文件是不是被当作二进制跳过了(加 -a)。
2. fd / bat 命令找不到
Debian / Ubuntu 下它们分别叫 fdfind 和 batcat,建个软链就行。别去改 PATH 折腾。
3. bat 在服务器上分页导致脚本卡住
这是把 bat 用在了非交互场景。记住:bat 检测到输出不是终端时会自动关掉分页,但如果你显式加了 --paging=always 就会强制分页。脚本里永远不要用 bat,用 cat。
4. 装了 ripgrep 但 rg 报 command not found
用静态二进制安装时忘了加执行权限,或者没放进 PATH。检查 which rg 和 ls -l /usr/local/bin/rg。
5. 搜索结果太多,怎么只保留最相关
用 rg -c 先看每个文件的命中数,或者 rg --sort path 稳定排序,再配合 head 只看前几条。找线索时不要一次看全部,要分层收敛。
小结
ripgrep、fd、bat 这三个工具加起来不到 20MB,却能在每一次手动排查中替你省下几分钟。它们的共同哲学是:不追求功能上的完备,而是把 90% 的日常场景做到极致的快和顺手。对个人站长和运维来说,真正稀缺的是注意力,用更好的工具把重复劳动压缩掉,才能把时间留给真正需要思考的问题,比如架构、性能和内容。建议今天就在你的常用服务器上装一遍,把三条别名写进 .bashrc,用一周你就会回不去了。