命令行工具这件事很容易上瘾:刷到一篇「10 个让你效率翻倍的 CLI 工具」就装两个,用两次,然后忘了自己装过。我现在的做法是先问「它替掉了哪个我每天要用的命令」——替不掉的就不留。按这个标准筛下来,我的 dotfiles 里最后稳定剩了 8 个。这篇把它们和它们的缺点一起写下来,也附上我删掉的那些和删掉的理由。
先说一个安装上的区别,后面不再重复:Ubuntu/Debian 上这些工具多半是 apt install,但发行版仓库里的版本经常偏旧(ripgrep 尤其明显),我一般走各自的 release 页或者 cargo install;macOS 上一律 brew install,省事且版本新。下文的版本号只是我写这篇时的状态,版本会变,以官方文档为准。
一、ripgrep (rg):搜索只留一个
替代:grep -r、ack、ag(the_silver_searcher)。
# Debian/Ubuntu
sudo apt install ripgrep
# macOS
brew install ripgrep
# 真实用法:在代码里找某个接口被谁调用了,只看 .go 文件,带上下文
rg -n -C 3 'FetchUser\(' --glob '*.go'
我每天用得最多的一类命令是「找调用点」和「找配置」。ripgrep 的三个默认行为把它和 grep -r 彻底分开了:默认遵守 .gitignore(不会去翻 node_modules)、默认是递归的、默认支持按文件类型过滤(-tgo、-tjs)。要搜被忽略的文件就加 -uu,我偶尔搜 .env、构建产物时才用。
不适合的场景:需要在没有这些约定的机器上搜(比如刚 pull 的容器里),rg 可能根本没装,这时候 grep -rn 才是保底;另外需要 Perl 正则的高级回溯、或者要搜二进制文件内部时,rg 默认会跳过,得显式加 --text。
二、fd:找文件不要再拼 find 的参数
替代:find 的日常用法(不是全部)。
sudo apt install fd-find # 注意 Debian/Ubuntu 上命令名是 fdfind
brew install fd
# 真实用法:找出最近改过的配置文件,排除 vendor 和 node_modules
fd -e yaml -e yml --changed-within 7d
find . -name '*.yaml' -not -path '*/vendor/*' 这种命令我每次都要现查语法,fd 的默认值已经是我 90% 时候想要的行为:忽略 gitignore、递归、大小写不敏感(智能大小写)、正则默认开启。
不适合的场景:需要按 inode、权限位、-exec 组合复杂条件时,find 还是更全;脚本里如果要保证可移植性,find 的 POSIX 语义更保险,fd 得额外装。另外 Ubuntu 上命令名是 fdfind,很多人装完敲 fd 报 command not found 就是这个原因,可以 alias fd=fdfind 或者装官方 deb 包。
三、fzf:把「选择」这件事变成打几个字母
替代:手动 ls 加复制粘贴。
sudo apt install fzf
brew install fzf && "$(brew --prefix)/opt/fzf/install" # macOS 要跑一次安装脚本接上键绑定
# 真实用法 1:交互式定位文件
vim "$(fzf)" # Ctrl-T 也可以
# 真实用法 2:从 git 历史里捞一个 commit
git log --oneline | fzf
# 真实用法 3:杀掉某个进程
ps aux | fzf | awk '{print $2}' | xargs kill
上面第三条我真实用了很多次。fzf 的价值不在命令本身,而在它把「列表 + 过滤 + 确认」压成一次按键,Ctrl-R 历史搜索、Alt-C 目录跳转这两个绑定装上之后就离不开。
不适合的场景:脚本、CI、任何非交互环境——它本质是给人看的 TUI;另外在超大列表(百万行以上)上它虽然能用,但第一次索引会有明显停顿,这时先 rg 缩小范围再交给 fzf 更稳。
四、bat:看文件而不是 cat
替代:cat(阅读场景,不是管道场景)。
sudo apt install bat # Ubuntu 上命令名可能是 batcat
brew install bat
# 真实用法:看一个文件,带行号和语法高亮,并只显示某个函数附近
bat -n --line-range 120:180 internal/service/order.go
我拿 bat 看日志片段的次数比看代码还多:带行号、会折叠大段空白、显示 git 修改标记(侧边的增删标识),而且自动分页,看长文件不用 less。
不适合的场景:管道里绝不用 bat——它会加高亮转义和行号,喂给 grep、awk 就全乱了(真要管道里用,加 --plain 或 -p);脚本里同样不要用。另外它比 cat 慢,看一个几 KB 的小文件这点差别无感,扫几万行日志的时候就能感觉到了。
五、jq:JSON 从「看得懂」到「能断言」
替代:把 JSON 复制到编辑器里手动看。
sudo apt install jq
brew install jq
# 真实用法:从接口响应里挑字段,顺便验证结构
curl -s http://127.0.0.1:8080/api/v1/posts?limit=3 \
| jq -r '.data.items[] | "\(.id)\t\(.title)\t\(.publishedAt)"'
jq 是我列表里唯一一个「会改变工作方式」的工具。以前排查接口问题是把响应贴进某个在线格式化网站,现在是直接一条命令选出关心的字段、排序、去重、统计:
# 统计各状态码出现次数(日志里常见)
jq -r '.status' access.log | sort | uniq -c | sort -rn
两个我自己踩过的细节:加 -r 才输出裸字符串,不加会带引号,后面接 xargs 或写进文件时很难受;往 jq 里传变量要用 --arg name value,别用字符串拼接,否则值里带引号就会拼出非法表达式。
不适合的场景:超大文件(几百 MB 以上)它会把整个文档读进内存,这时用 jq --stream 或者干脆换个工具;另外 YAML/TOML 它不管(那是 yq、dasel 的地盘),我没有同时留下 yq,因为配置文件我一般直接在编辑器里看。
六、tree:把目录结构说清楚
替代:ls -R(后者的输出完全不可读)。
sudo apt install tree
brew install tree
# 真实用法:写文档、给别人说明项目结构时生成一份
tree -L 2 -I 'node_modules|.git|dist|vendor' --dirsfirst
我用它的场合很集中:要跟别人解释结构的时候(写 issue、写文档、给新人说明目录),以及检查「这个目录里为什么有一堆我没见过的文件夹」。-I 排除项和 -L 层数这两个参数是必配的,不然一个 Node 项目能刷屏几千行。
不适合的场景:海量文件的目录(比如一个有几十万文件的缓存目录)会卡住并刷满终端输出,这种时候 du -sh * | sort -h 比它有用;另外它是纯展示工具,输出不适合给脚本解析(要解析就用 fd 或 find)。
七、tmux:不是「多窗口」,是「会话活着」
替代:不替代谁,它补的是 SSH 断线这件事。
sudo apt install tmux
brew install tmux
# 真实用法:跑一个长任务,然后放心关掉笔记本
tmux new -s build
# 在会话里跑长时间命令,然后 Ctrl-b d 分离
tmux attach -t build
我对 tmux 的依赖全部来自一个场景:在服务器上跑耗时任务时断线。以前是用 nohup ... & 加翻日志,现在是 tmux new -s xxx,SSH 断了会话还在,重连 tmux attach -t xxx 直接看结果。第二个用途是留上下文:一个窗口跑服务、一个窗口看日志、一个窗口敲命令,下次连回来布局还在。
配置上我只改了很少的东西:Ctrl-b 换成 Ctrl-a、开鼠标支持、把 history-limit 调大(默认 2000 行,翻长日志不够)。我不装 tmux 插件管理器,因为插件装多了在别人的机器上就完全用不惯,而 tmux 本来就是我需要在陌生环境里用的工具。
不适合的场景:日常在本地终端工作、又不跑长任务的话,真没必要——它有一层学习成本,而且默认的滚动和复制粘贴跟终端自己的行为不一致(开了鼠标支持才顺手);用 IDE 内置终端的话,IDE 本身就有会话恢复,这时候 tmux 是重复的。
八、starship:提示符该说的话,只留一句有用的
替代:oh-my-zsh 的主题 + 一堆手写 PROMPT 拼接。
# 安装(macOS/Linux 通用,脚本会自己改 shell 配置)
curl -sS https://starship.rs/install.sh | sh
# 在 ~/.zshrc 或 ~/.bashrc 末尾加:
eval "$(starship init zsh)"
它给我的是三件即时信息:当前目录的 git 分支和脏状态、上一条命令的耗时(超过阈值才显示)、当前目录生效的运行时版本(Go/Node 各自的版本)。最后一条在大项目里特别有用——切到某个老项目时会明确告诉你「这里在用 Node 18」。
配置我压得很短,只关掉了不需要的模块,并让语言版本在非项目目录不显示。它的代价是启动变慢:默认配置里模块很多,每开一个新 shell 要跑一遍检测,在慢机器(尤其是网络挂载的 home 目录)上能感觉出来。所以我的建议是装完先砍模块,而不是先加。
不适合的场景:把 shell 启动时间看得极重的场景(我见过有人的做法是回到手写 PROMPT,确实快);以及服务器上——那上面我从来不装,默认提示符反而更不容易出错。
| 工具 | 替代 | 一句话留下它的理由 |
|---|---|---|
| ripgrep | grep -r | 默认忽略 gitignore,找调用点一步到位 |
| fd | find 的日常用法 | 默认行为就是我想要的那个 |
| fzf | 手动 ls + 复制粘贴 | 把「列表+过滤」变成一次按键 |
| bat | cat(阅读场景) | 行号、高亮、分页都有了 |
| jq | 肉眼格式化 JSON | 字段可以选出来、断言、统计 |
| tree | ls -R | 要跟人解释结构时的通用语言 |
| tmux | 不替代,补 SSH 断线 | 会话不随连接消失 |
| starship | oh-my-zsh 主题 | 分支、耗时、运行时版本一眼可见 |
哪些我没留
反面清单同列表一样重要,这些我都装过、用过、最后删了:
- oh-my-zsh:功能确实多,但它把启动时间、插件版本、主题更新全绑在一起,一出问题(提示符变慢、补全报错)就要在它自己的框架里排查。我用
starship+ 少量手写 alias 覆盖了它的核心价值,启动也快得多。 lsd/exa这类ls替代:图标好看,但在没有 Nerd Font 的机器上会显示成方块,在别人的机器上、在服务器上就是个负担。而且我ls用得越来越少——真要看清结构我用tree或fd。htop/btop:本机上看进程确实舒服,但我真正排查看的是容器和远程机器,用的还是docker stats和top,多留一个工具在容器镜像里没必要。z/autojump这类目录跳转:需要时间积累访问历史,换机器就失效,而我在跨项目跳转时基本靠fzf(Alt-C)——不依赖历史状态这点很重要。curl的替代品(httpie、xh):语法确实更友好,但我的脚本、文档、issue 里写的都是curl,两套并存的结果是每次都要想一下用哪个。工具链里有一条我越来越信服的规则:能进脚本的那个留下。- 一堆「美化版」的 coreutils(
GNU sed/coreutils别名替换 macOS 自带版本):装完之后我写的命令在 Linux 服务器上就跑不通了——参数不一样。这类只在本地生效的差异是长期成本,不值得。 - 各种 dotfiles 框架(
chezmoi之外的裸 symlink 方案我都试过):结论是配置文件越少越容易理解,我现在的 dotfiles 只有一个install.sh加几个文件的软链接,能一眼看完。
结尾
这 8 个工具加起来大概十几分钟就能装完,真正花时间的是决定不留哪些。我的判断标准最后收敛成三条:它是否替掉了我每天都会敲的命令;它是否在不属于我的机器上也能用(或者容易不带);它的输出是否还能进管道、进脚本。三条里有一条不满足,我基本就不留了——工具链的价值不在于有 20 个厉害的工具,而在于你在任何一台机器上敲命令时不用想「这个参数在这里是不是不一样」。