面向开发者的现代命令行工具集:更快搜索、更智能导航、更好理解 Git,并把更少时间浪费在和终端搏斗上
现代软件开发中存在一个奇怪的矛盾。
我们的编辑器越来越聪明。IDE 能够索引整个代码库、自动补全 API、可视化 Git 历史、运行测试、调试容器、检查数据库,甚至还能解释陌生代码。
然而,最终大多数开发者还是会回到终端。
我们打开 shell 来运行服务、查看日志、在仓库里搜索、切换分支、检查 JSON 响应、查找配置文件、连接远程机器,或者排查凌晨 2 点出错的某个问题。
有趣的是,终端本身其实并没有发生多大变化。
我们仍然在使用 ls、cd、cat、find、grep、git,以及一套几十年前设计的 shell 命令。
这些工具极其强大。但它们也是通用工具,界面往往要求你记住相当多的语法。
在过去几年里,新一代命令行工具开始出现。它们保留了 Unix 的可组合性,同时让常见的开发者工作流变得明显更轻松。真正有意思的地方并不是这些工具只是老命令的更漂亮替代品。它们的真正优势在于,它们是围绕现代开发者的实际工作方式设计的:大型 Git 仓库、庞大的依赖树、JSON API、容器化服务、多分支环境,以及大量依赖终端的工作流。
这篇文章所受启发的原文采用的是简洁的逐个工具介绍方式,从受 tmux 启发、面向 Windows 的终端多路复用器 psmux 开始。
而我想从不同角度来切入这个主题。
与其简单地说“这里有十个你应该安装的工具”,不如看看它们解决了哪些令人头疼的开发问题、它们如何彼此配合,以及每个工具在什么场景下真正有用。
因为最好的 CLI 工具,并不是那些你记得住的工具。
而是那些你逐渐不再注意到的工具,因为它们悄无声息地消除了你工作中的摩擦。
1. eza: 让 ls 真正变得有用
大多数开发者打开终端后做的第一件事,就是先看看当前目录里有什么。
传统做法是:
ls
你会得到一个文件名列表。
然后你开始加参数。
ls -la
ls -lah
ls -ltr
ls -lahtr
很快,你就会开始回忆,究竟是哪组参数能显示隐藏文件、人类可读的大小、时间戳、权限,或者你真正想要的信息。
eza 延续了熟悉的 ls 工作流,但提供了更好的默认行为和更多上下文信息。它是 ls 的现代替代品,支持 Git 状态、树状视图、文件元数据、超链接以及更友好的日期显示等功能。
最简单的用法是:
eza
但当你开始浏览项目时,命令就会变得更有意思。
例如:
eza -lah
可以给你一个详细列表,而:
eza --tree --level=2
则能让你在不立即打开 IDE 的情况下理解仓库结构。
当你在 Git 仓库中工作时,Git 感知的输出能提供比普通目录列表更多的信息。
这很重要,因为开发者真正关心的并不是“有哪些文件”。
我们关心的是这些文件现在是什么状态。
一个能同时传达文件类型、时间戳和 Git 状态的目录列表,已经比原始文件名清单有用得多。
eza 最大的优势并不是替代 ls。
而是它减少了你在 ls 之后还需要执行的后续命令数量。
2. bat: 别再像 1987 年那样读代码了
检查文件的经典方式是:
cat app.py
从技术上说,cat 的确做了它名字所暗示的事情。
它把文件内容打印出来。
但源代码并不是真正适合以纯终端输出的形式来阅读的。我们想要语法高亮、行号、分页,以及足够的结构来快速理解文件。
这就是 bat 的用武之地。
bat 被设计为 cat 的替代品,具备语法高亮、分页以及其他对开发者友好的特性。它会自动分页较长的输出,同时在输出被管道传给别处时,仍然可以像普通 cat 一样工作。
试试:
bat app.py
你看到的就不再是一大坨文本,而更像是一个可读的代码视图。
这在调试时尤其有用。
假设某个日志告诉你:
config.yaml:42
你不必在编辑器里打开整个文件,只需直接查看相关区域:
bat --line-range 35:50 config.yaml
这样,终端就变成了一个轻量级的源代码浏览器。
而当 bat 与其他工具结合使用时,它会变得更有意思。
例如,fzf 可以把 bat 当作预览器:
fzf --preview 'bat --color=always --style=numbers --line-range=:500 {}'
这样就会得到一个交互式文件选择器,候选列表和文件内容会同时出现在同一个终端界面中。bat 项目的官方文档明确介绍了这种与 fzf 的集成方式。
这说明了现代 CLI 设计中的一个重要特征。
最好的工具并不是孤立的实用程序。
它们是可组合的构建块。
3. fd: 不用写论文也能找到文件
经典的 find 非常强大。
但它也是那种有时会让你觉得,在输入任何内容前都得先翻一下手册的命令。
例如:
find . -type f -iname "*.py"
这没问题。
但随着查询变得更具体,find 的语法会越来越难记。
fd 采取了更有主张的方式。它被设计为 find 的更简单、更友好的替代方案,默认行为合理、遍历速度快、大小写处理智能,并且会自动遵守 .gitignore 规则。
与其写:
find . -type f -iname "*.py"
你通常只需要写:
fd "\.py$"
或者,如果你在找某个特定文件名:
fd config
这在大型仓库中特别有用,因为直接搜索整个文件系统通常会产生大量无关输出。
由于 fd 默认会忽略隐藏路径和被 Git 忽略的路径,仓库搜索通常会更聚焦于你真正关心的文件。
然后你还可以把它组合起来。
例如:
fd "\.log$" /var/log
或者:
fd Dockerfile
甚至:
fd '\.json$' | xargs bat
这是现代 CLI 生态反复出现的主题之一:
当一个简单命令的输出是为被其他命令消费而设计时,它就会变得非常强大。
4. rg: 代码库应当配得上更好的搜索工具
如果说 fd 帮你找到文件,那么 ripgrep 就帮你找到文件里的内容。
而这正是终端工作流开始显著提速的地方。
传统命令是:
grep -R "DATABASE_URL" .
它能工作。
但源代码仓库里有很多你通常并不想搜索的内容:
.git生成文件
二进制文件
构建目录
依赖目录
被忽略的文件
ripgrep,即 rg,是专门为快速递归文本搜索设计的。它默认遵守 Git ignore 规则,并且会自动避开隐藏文件和二进制文件,除非你另有要求。
所以你通常可以直接写:
rg "DATABASE_URL"
这已经更好了。
但真正的价值会在你调试一个陌生仓库时体现出来。
假设你知道有个叫 MAX_CONNECTIONS 的配置值存在于某处,但你完全不知道它在哪。
rg "MAX_CONNECTIONS"
可能会立刻显示:
config/settings.py:17
docker-compose.yml:42
helm/api/templates/configmap.yaml:18
tests/test_config.py:11
这样你只用几秒钟就理解了配置路径。
你还可以按文件类型搜索:
rg "retry" -g '*.py'
或者包含行号:
rg -n "timeout"
或者只搜索某个特定目录:
rg "redis" backend/
由于 rg 的设计就是围绕递归仓库搜索,它几乎可以成为你代码库的第二套导航机制。
很多开发者在用了一段时间 rg 之后,就不再去想:
“这东西大概在哪个文件夹里?”
而是开始想:
“我对我正在找的东西已经知道些什么?”
这是一种更快的陌生软件导航方式。
5. fzf: 把终端变成交互式界面
fzf 与本列表中的大多数工具都不一样。
它并不是某个单一命令的替代品。
它是一个通用的模糊搜索工具,可以把文本列表变成交互式终端界面。你可以把它用于文件、Git 分支、命令历史、进程、主机名,以及几乎所有可以表示为文本行的内容。
假设你有 80 个 Git 分支。
与其:
git branch
然后在列表中滚动,不如这样:
git branch | fzf
现在你就拥有了一个交互式选择器。
想找文件?
fd | fzf
想打开选中的文件?
vim "$(fd | fzf)"
想和 bat 一起用?
fd | fzf --preview 'bat --color=always {}'
现在终端就成了一个可搜索的文件浏览器。
这也是 fzf 开始变得远不止一个模糊搜索工具的地方。
它会变成构建小型交互式开发工具的基础原语。
官方文档把它描述为命令行模糊搜索器和终端工具包,而它的 shell 集成则提供了常见工作流中的快捷键和模糊补全功能。
理解 fzf 的一个好方法是:
传统 Unix 命令大多假定的是:
输入 → 命令 → 输出
而 fzf 给你的是:
输入 → 交互式选择 → 输出
多出来的这一步,让 shell 对人类使用起来舒适得多。
6. zoxide: 记住你真正工作的地方
cd 命令对开发者有一个非常不现实的假设。
它假设我们愿意记住目录路径。
其实我们不愿意。
我们记住的是项目。
你大概不会去想:
~/work/company/projects/payments/backend/services/api
你会想:
“支付服务。”
zoxide 是一个更聪明的目录导航工具,它会学习你最常访问的目录。你不必记住完整路径,只要通过部分名称就能跳转。
例如:
z payments
可以把你带到一个与该词匹配、且经常使用的目录。
或者:
z api
可以跳转到你反复访问、包含 api 的目录。
你还可以使用:
zi
进行交互式选择,在安装了 fzf 集成时尤为方便。
在你用了几周之后,这看起来像是一个很小的改进。
然后你回到普通 shell,突然发现自己以前竟然总是在输入:
cd ..
cd ..
cd ..
cd project
cd backend
cd services
cd api
这里真正提升生产力的,并不是节省了几个按键。
而是减少了导航时的上下文切换。
你是在告诉 shell 你想去哪里,而不是每次都手工拼出精确的文件系统路径。
7. delta: 让 Git diff 真的能看懂
Git 是软件开发中最重要的工具之一。
但它也是那种终端输出会变得异常难读的工具之一。
一个大的 diff 很快就会变成一片:
+ lines
- lines
@@ hunks
当你看了几百行变更后,大脑就会开始自动过滤掉有用信息。
delta 是一个带语法高亮的 pager,适用于 Git、diff、grep、rg 以及相关输出。它可以提供并排视图、行号、词级高亮、更好的 merge conflict 显示,以及在大型 diff 中的导航功能。
安装后,你可以把它配置为 Git 的 pager:
git config --global core.pager delta
现在:
git diff
就会变成一个更易读的审查体验。
它最好用的功能之一就是并排输出。
与其在脑中对比:
- old implementation
+ new implementation
不如直接把两个版本并排可视化比较。
这在审查重构、配置变更或大型后端修改时尤其有用。
在代码审查过程中,它会变得更加有价值,因为 delta 能突出显示实际改动的单词,而不是把整行都当成一次变化。
而且 Git diff 的可读性并不是外观问题。
如果你每天都要 review 代码,让 diff 更容易解析,就能减少发现错误所需的认知负担。
8. lazygit: 不必把 Git 的整本字典都背下来
问题其实不在 Git 本身。
Git 的数据模型非常优秀。
问题在于,Git 通过命令、参数、状态和工作流暴露了太多能力,而这些都需要开发者记忆。
交互式 rebase 就是一个完美例子。
其底层概念并不难理解。
难的是界面。
lazygit 为 Git 操作提供了一个终端 UI,包括逐行 stage、交互式 rebase、cherry-pick、分支管理、提交历史、冲突解决、worktree 等功能。
在仓库中运行:
lazygit
就会在不离开终端的情况下给你一个仓库状态的可视化表示。
你可以选择文件、查看变更、对 diff 的一部分进行 stage、审查提交,并在同一个界面里操作分支。
有意思的是,这并不一定意味着 Git 变得不那么强大。
它只是让 Git 的强大能力更容易被发现。
你不再需要在执行操作之前先记住每一个命令。
而这对那些强大但不常用的操作尤其有价值。
例如,很多开发者都理解 interactive rebase 的作用,但实际使用频率不足以让他们记住每一个按键。
UI 会把这个概念变成你可以安全探索的东西。
这也是优秀开发者工具的一个常见模式:
最好的界面,往往是那种能让强大操作更容易被发现,同时又不剥夺底层系统访问能力的界面。
9. jq: 把 JSON 当作数据,而不是文本
现代开发涉及大量的 JSON。
API 返回 JSON。
云 CLI 返回 JSON。
Kubernetes 返回 JSON。
Docker 工具产生 JSON。
配置文件包含 JSON。
但开发者仍然经常用下面这些工具来处理它:
grep
sed
awk
cut
这意味着我们把结构化数据当成了非结构化文本来处理。
jq 解决了这个问题。
它是一个命令行 JSON 处理器,专门用于过滤、转换、映射和重塑 JSON 数据。
假设你运行了一个 API 请求:
curl -s https://api.example.com/users
并收到:
{
"users": [
{
"id": 101,
"name": "Alice",
"active": true
},
{
"id": 102,
"name": "Bob",
"active": false
}
]
}
你可以把输出通过管道传给:
jq '.users[] | select(.active) | .name'
然后得到:
"Alice"
这一条命令本质上就是在表达:
取出 users 数组,遍历其元素,只保留 active 的用户,并打印他们的名字。
当你处理基础设施时,这会变得非常强大。
例如:
aws ... --output json | jq '.Reservations[].Instances[] | .InstanceId'
或者:
kubectl ... -o json | jq '.items[] | .metadata.name'
与其学习每个命令的输出格式,再用文本工具手工解析,不如直接在结构上操作。
这个心智模型要好得多。
JSON 是数据。把它当数据来处理。
10. psmux: 别再打开十二个终端窗口了
最后这个工具解决的是另一类问题。
有时问题不在命令本身。
而在于你需要同时运行多少东西。
一个典型的后端开发环境可能包括:
Terminal 1 → API server
Terminal 2 → Frontend
Terminal 3 → Worker
Terminal 4 → Database logs
Terminal 5 → Redis
Terminal 6 → Git
Terminal 7 → Shell
到了某个时候,你的桌面就会变成一个伪装成开发环境的窗口管理系统。
这就是终端多路复用器变得有用的地方。
启发本文的原文重点介绍了 psmux,一个受 tmux 启发、面向 Windows 的终端多路复用器。它在一个终端环境中提供窗格、会话和多个 shell。
无论你使用的是 psmux、tmux 还是其他多路复用器,其整体理念都是一样的。
与其想着:
“我需要再开一个终端窗口。”
你开始想着:
“我需要在这个开发会话里再开一个窗格。”
例如:
┌─────────────────────────┬─────────────────────┐
│ │ │
│ API Server │ Frontend │
│ │ │
├─────────────────────────┼─────────────────────┤
│ │ │
│ Worker / Logs │ Shell │
│ │ │
└─────────────────────────┴─────────────────────┘
真正的优势在于 sessions。
你可以创建一个开发会话,detach,然后稍后重新连接,并保留正在运行的进程和工作区。
这会让远程开发尤其舒适。
终端不再只是命令集合,而开始成为一个工作区。
真正有意思的部分在于组合它们
最大的错误,莫过于把这十个工具都装上,然后把它们当作十个彼此独立的实用程序。
真正的生产力提升来自组合。
想象一下,你刚接手一个陌生仓库。
你先这样浏览它:
eza --tree --level=2
你想找到应用配置:
rg "DATABASE_URL|REDIS_URL|SECRET_KEY"
你找到一个可疑的配置文件并检查它:
bat config/settings.yaml
你发现有多个名字相似的文件:
fd "settings"
你想交互式打开其中一个:
fd "settings" | fzf --preview 'bat --color=always {}'
你跳转到相关的项目目录:
z payments
然后你查看当前改动:
git diff
并让 delta 对 diff 进行格式化。
你发现变更分散在几个提交里,于是打开:
lazygit
并可视化地查看历史。
然后你命中了一个 endpoint,需要检查 JSON:
curl -s http://localhost:8000/api/orders | jq '.orders[] | select(.status == "failed")'
这一切都无需离开终端。
这才是真正的故事。
这些工具的价值并不在于每个都能帮你省五秒钟。
它们有价值,是因为整个工作流变短了。
现代 CLI 栈,本质上就是一个可组合性栈
传统命令行之所以极其强大,是因为 Unix 工具具有可组合性。
你可以把一个程序的输出传给另一个程序:
command_a | command_b | command_c
现代 CLI 工具延续了这一理念。
fd 可以生成文件列表。
fzf 可以交互式过滤它们。
bat 可以预览它们。
rg 可以搜索它们的内容。
jq 可以转换结构化输出。
delta 可以渲染 diff。
这意味着,真正的生产力单位不是单个工具。
而是管道。
例如:
rg --files | fzf --preview 'bat --color=always {}'
这个小命令实际上就给你提供了一个可搜索的代码浏览器。
或者:
fd '\.json$' | fzf | xargs jq .
现在 shell 就可以充当一个轻量级的结构化数据浏览器。
而且由于每个组件都遵循 Unix 哲学——从标准输入读取、向标准输出写入——这些小工作流可以被直接拼装起来,而无需编写完整应用。
这也是为什么即使在高级 IDE 时代,终端依然如此重要的原因之一。
终端并不是单一界面。
它是一个用于组合界面的界面。
不要因为能替换就把一切都替换掉
这里有一个重要的提醒。
你不需要这十个工具全都装上。
而且你也绝对不需要立刻把所有标准 Unix 命令都替换掉。
有些开发者确实更喜欢:
ls
cat
find
grep
git
这没什么问题。
现代 CLI 工具的目的,并不是创造一层新的部落知识,让开发者必须记住:
eza = ls
bat = cat
fd = find
rg = grep
那样就完全误解了它的意义。
只有当某个工具能改进你经常执行的工作流时,它才值得采用。
如果你持续不断地搜索源代码,rg 很可能会成为你工作流中的永久成员。
如果你整天都在浏览 Git 仓库,lazygit 或 delta 可能更有价值。
如果你的工作涉及 API 和基础设施,jq 会变得不可或缺。
如果你反复在相同目录间跳转,zoxide 往往会让你在某个时刻忍不住想:我以前到底是怎么活下来的?
合适的终端,不是拥有最多工具的终端。
而是思想和行动之间摩擦最小的终端。
终端正在重新成为开发者环境
曾经有一段时间,终端被视为一种你必须忍受的东西,然后才会进入图形化开发环境。
这种区分正变得越来越没意义。
现代终端工具越来越复杂。
它们提供模糊匹配界面、语法高亮、交互式工作流、Git 可视化、结构化数据处理、更智能的导航、会话管理,以及高度可组合的自动化能力。
与此同时,它们仍然轻量且可脚本化。
这是一个很强的组合。
GUI 应用可以为某一种工作流提供很棒的界面。
而 CLI 工具则可以成为数百种工作流的一部分,因为另一个命令可以调用它、把数据管道给它,或者使用它的输出。
这就是为什么终端在软件开发中依然如此根深蒂固。
并不是因为开发者喜欢背命令。
而是因为可组合性具有极强的规模扩展能力。
我的实用起步栈
我不会一开始就把所有工具都装上。
我会先从那些能消除你已经在做的工作中的摩擦的工具开始。
对大多数开发者来说,一个不错的起点会是:
rg → 搜索代码
fd → 查找文件
bat → 检查文件
fzf → 与列表交互
zoxide → 导航项目
delta → 阅读 Git diff
lazygit → 管理 Git 工作流
jq → 检查 JSON
eza → 理解目录结构
tmux/psmux → 管理终端会话
一旦这些工具变得熟悉,终端就会开始更像一个可编程工作区,而不是一个命令解释器。
而这,归根结底,才是更大的变化。
最好的 CLI 工具,是能让你少想一步的工具
优秀的开发者工具,并不只是让命令更短。
它们是在消除不必要的决策。
当你想打开一个项目时,不应该每次都记住它的完整路径。
你不应该必须在脑中解析一整屏 Git diff 输出。
对于你每天都要执行的搜索,不应该还得记住复杂的 find 语法。
你不应该把 JSON 当作纯文本来处理。
你也不应该为了运行本地服务栈就开六个终端窗口。
而且,对于一个每月只做两次的操作,你也不该把精确的 Git 咒语背下来。
最好的 CLI 工具,会悄悄吸收这些细小的摩擦点。
它们让常见路径更容易走,同时又不剥夺底层能力。
这正是我认为现代命令行复兴最有趣的地方。
我们并不是在替换终端。
我们终于在给它补上它一直应得的开发者体验。

