前两天在群里聊天,有个刚入行的朋友问我:现在 IDE 和图形化工具都做得这么顺手,为什么还天天泡在黑乎乎的终端里敲命令。我当时的回答是,我不是喜欢黑屏,我是离不开那十个把老命令重新做了一遍的命令行工具。它们没有一个是全新的概念,eza 是 ls,fd 是 find,ripgrep 是 grep,但每一个都在"默认行为"上做了巨大的改进——默认好看、默认顺手、默认少敲一半字符。
这篇东西就是把我这两年配置里一直没删掉的那十个工具摊开讲一遍。不是罗列功能清单,而是讲清楚三件事:它替掉了谁、为什么值得换、实际用起来会踩哪些坑。适合天天连服务器改配置的运维、写脚本和后端的开发者、经常要在几十万行日志里捞东西的数据同学,也适合刚接触 Linux、被 find 和 sed 的参数劝退过的新手。我不会假设你之前用过任何一个,配置片段都能直接抄。
1. 先聊聊我为什么把老命令一个个挨着换掉
1.1 老一代命令行的三个真实痛点
第一个痛点是默认输出对人类不友好。ls丢给你一屏一模一样的白色文件名,目录和文件混在一起;du -sh *在目录多的时候刷出几百行,你还得自己去排序;git diff只有加减号,改动落在哪个函数里全靠眼睛扫。这些工具诞生在上世纪七十年代,那会儿终端是电传打字机,能输出字符就已经很奢侈,可读性根本不是设计目标。
第二个痛点是参数记忆成本高得离谱。find . -type f -name "*.py" -not -path "*/node_modules/*" -exec wc -l {} \;这条命令我背了七八年,每次写还要查一下-exec结尾到底是\;还是+。sed -i 's/old/new/g'里的正则转义规则和它的捕获组语法\1更是反直觉到顶点。问题不在于难,而在于这些参数我一周要用几十次,每次都消耗一点注意力。
第三个痛点是交互能力几乎为零。我想"先看看有哪些匹配,再从里面挑一个",老工具的做法是先跑命令、输出一屏、眼睛扫、重新写一条更精确的命令再跑一次。这个来回在排查线上问题时格外难受,因为每一步都要重新输入、重新等待。很多人说命令行效率高,但效率高的前提是工具本身允许你边看边筛,而不是一条命令一个坑地试。
1.2 现代 CLI 工具的四个共同特征
我把这十年冒出来的新一代工具总结成四个共同点,理解了这四点,你就能自己判断一个新工具值不值得学了。第一是默认输出即成品,不需要额外加一堆--color=always和格式化参数,装完直接跑就很好看,颜色、图标、对齐、单位换算全都替你做好了。第二是常见操作零参数,rg 关键词就直接递归搜索整个目录,不用再写-r。
第三是支持交互式筛选。这一点是被 fzf 带起来的,现在的工具越来越倾向于"先给你一个可以缩小范围的界面",而不是"一次跑完给你一坨结果"。第四也是最重要的一点是遵守统一的组合约定:能读标准输入、能写标准输出、--help写人话、支持NO_COLOR环境变量、错误信息输出到 stderr 而不是 stdout。正是这些约定让命令行真正成为"管道",你可以把 rg 的结果喂给 fzf,再把 fzf 的选择喂给 bat。
注意:不是所有"新工具"都遵守第四条。判断方法很简单,把它接到管道里试试,如果输出变了颜色或者格式乱了,说明它是为终端交互设计的,不适合进脚本。
1.3 我的筛选标准:什么才配进"离不开"清单
我给自己定了四条硬标准。第一,每天至少用一次,一周用一次的再炫也不进清单。第二,换成它之后我不想回去,如果我只是"知道有这么个东西"但实际还是习惯敲ls,那它就不算。第三,在一台干净机器上我能用一条命令装回来,需要手动编译半小时的工具我会犹豫。第四,它不绑架我的工作流,不需要常驻守护进程、不需要改系统级配置、卸载了不留残渣。
按这四条筛下来,最后留在配置里的就是下面十个。清单里没有一个是 2024 年以后才出现的爆款,全是经过时间检验、社区活跃、跨平台支持良好的选手。
| 序号 | 替换掉的老命令 | 现代工具 | 我最常用的场景 |
|---|---|---|---|
| 1 | ls | eza | 看目录结构和文件大小,一眼扫完 |
| 2 | find | fd | 按扩展名、路径批量找文件 |
| 3 | cd | zoxide | 用关键词跳到常用项目目录 |
| 4 | cat | bat | 读配置、看代码片段 |
| 5 | grep | ripgrep | 全项目搜关键字,秒出结果 |
| 6 | sed | sd | 批量正则替换,不背转义 |
| 7 | Ctrl+R | fzf | 任何列表变成可模糊搜索的界面 |
| 8 | 无 | jq / yq | 处理 JSON 和 YAML 配置 |
| 9 | du | dust | 找出到底是谁把磁盘吃满了 |
| 10 | top | bottom | 看进程和资源占用的实时面板 |
2. 文件浏览与查找:ls、cd、find 的现代接棒者
2.1 eza:让 ls 长出颜色、图标和 Git 状态
eza 是 exa 的社区延续版本,安装很简单:macOS 上brew install eza,Debian 和 Ubuntu 24.04 之后可以直接sudo apt install eza,Arch 用pacman -S eza,Fedora 用dnf install eza。如果你的发行版版本太旧装不上,可以用cargo install eza从源码编译,或者直接去 GitHub Release 下预编译的二进制扔到~/.local/bin。
装完先别急着改别名,跑几个参数感受一下。eza --icons会给每个条目加上文件类型图标,eza -lh --git会在每一行右边显示这个文件在 Git 里的修改状态,eza --tree --level=2直接替代tree命令而且性能更好。最让我回不去的是--group-directories-first --header这个组合,目录永远排在最上面,而且表头会标出权限、大小、日期分别对应哪一列。
# 放在 ~/.bashrc 或 ~/.zshrc 里 alias ls='eza --icons --group-directories-first' alias ll='eza -lh --icons --git --group-directories-first --header' alias la='eza -lah --icons --group-directories-first' alias lt='eza --tree --level=2 --icons'注意:
--icons显示乱码是字体问题,不是 eza 的问题。你需要装一款 Nerd Font 并在终端设置里选中它。服务器上一般没有字体,所以远程机器上我把--icons去掉,只保留颜色和分组。另外一个坑是--git参数在超大仓库里会明显变慢,因为它每显示一次目录就要调一次 git 状态查询,几十万个文件的仓库别在根目录用。
2.2 fd:比 find 少敲一大半的那种爽
fd 的定位非常明确:把find里 90% 的日常用法变成零思考操作。对比一下就懂了,找当前目录下所有 Python 文件并排除 node_modules,老写法是find . -type f -name "*.py" -not -path "*/node_modules/*",用 fd 就是fd -e py -E node_modules。前者我要回忆-type f的位置,后者只需要记住"扩展名用 -e,排除用 -E"。
它的参数设计得很有规律:-e指定扩展名,-E排除路径,-H包含隐藏文件,-I忽略 .gitignore 和 ignore 规则,-t f只找文件,-t d只找目录,-x对每个结果执行命令,-X把所有结果一次性传给命令。这几个参数覆盖了我日常九成五的查找需求。
# 找出所有日志文件并按大小排序 fd -e log -x ls -lh {} # 找出所有 Python 文件统计行数(-X 批量传参,比 -x 快很多) fd -e py -X wc -l # 在配置文件里找(一定要加 -HI) fd -HI -e conf . /etc # 对每个找到的文件单独执行命令 fd -e jpg -x convert {} {.}.webp注意:fd 默认不显示隐藏文件、默认遵守 .gitignore 规则,这是它的设计取向,但对从 find 转过来的人是最大的坑。我第一次找
/etc下的配置文件,跑fd nginx什么也没找到,还以为是没装。记住两组参数,-H管隐藏文件,-I管忽略规则,找系统配置的时候无脑-HI就对了。另外-x后面那个{}是占位符,{.}表示去掉扩展名的路径,{/}是文件名,{//}是目录部分,这几个简写能省掉大量 awk 和 basename。
2.3 zoxide:目录跳转从"记路径"变成"记关键词"
zoxide 解决的是一个特别具体的痛:我在一台机器上可能有十几个项目目录,散落在~/work、~/projects、/data/repos三个不同层级下面,每次切过去都要敲一长串路径。zoxide 的思路是记录你访问过的目录,按"频次加最近使用"排序,你只要敲一个关键词就能跳过去。初始化只需要在配置文件里加一行:bash 用eval "$(zoxide init bash)",zsh 用eval "$(zoxide init zsh)",fish 用zoxide init fish | source。
用起来是这样:z blog会跳到最匹配 blog 的目录,z api v2支持多关键词依次匹配路径片段,zi打开交互式选择界面让你从候选里挑,z -回到上一个目录,z ..回上级。它维护的那个数据库放在~/.local/share/zoxide/下面,你可以随时用zoxide query -l看排在最前面的目录有哪些。
实操心得:刚装上的头两三天,你会觉得它一点都不聪明,因为数据库是空的。这时候千万别急着卸载,老老实实用 cd 走一遍你常去的目录,一般一周之后命中率就非常高了。这跟输入法词库是一个道理,前期投入后期收益。另外我强烈建议不要把它初始化在脚本或非交互式 shell 里,因为它会劫持 cd 的行为,在 Makefile 和 CI 里跑可能出怪问题。
2.4 这三个工具串起来的一套日常动线
讲个具体的场景你就知道组合起来是什么感觉了。早上到工位,接到一个告警说订单服务报错,我先z order直接跳到项目目录(zoxide),然后lt看一眼目录结构确认服务在哪个子目录(eza --tree),接着fd -e yaml -e toml找出这个服务的所有配置文件(fd),最后rg "timeout"在配置里搜超时相关的项(ripgrep)。
整个流程大概是十五秒,而且中间没有任何一步需要我回忆完整路径。换回老工具的话,第一步cd ~/work/backend/services/order-service就得敲二十多个字符还得记得住。这四步里我换了三个工具,而它们彼此的配合是完全透明的,因为都遵守前面说的那套标准输入输出约定。这也是我每次换新工具都会优先考虑"能不能和其他工具串"的原因。
3. 文件内容处理:cat 和 grep 的替代方案
3.1 bat:让 cat 带语法高亮和行号
cat最大的问题是它真的只是"把文件内容倒出来",几万行的日志刷屏刷到你怀疑人生,而且没有任何高亮。bat 本质上是cat加了个less式的分页器和语法高亮引擎,打开一个 config.yaml 你能立刻看清层级结构,打开一个有语法错误的 py 文件能直接看出哪一行不对劲。
安装:macOSbrew install bat,Archpacman -S bat,Fedoradnf install bat,但 Debian 和 Ubuntu 用户要注意一个坑——因为这个包名和另一个软件冲突,可执行文件叫batcat而不是bat。我见过太多人在这一步卡住,以为装失败了。
# Ubuntu / Debian 上要这样写别名 alias cat='batcat --paging=never' alias catp='batcat'几个常用参数:--paging=never让输出像 cat 一样直接刷出来不分页,-p等同于--style=plain只显示内容不显示行号和文件头,--line-range 120:160只看指定行区间,-l yaml手动指定语言(有些配置文件没有扩展名时高亮会失效),--diff配合 git 用能把改动的行标出来。
坑点提示:如果你在管道里用 bat,比如
bat file | grep foo,不加--paging=never会直接卡住等按键,因为 bat 检测到输出不是终端但依然启用了分页器。最省事的办法是在配置里加export BAT_PAGER="",一劳永逸。另外我一般不把 bat 设成 cat 的别名放在脚本环境里,只放在交互式 shell 的配置里,原因后面讲兼容性的时候会细说。
3.2 ripgrep:递归搜索的事实标准
ripgrep(命令名rg)到现在几乎是我唯一在用的搜索工具。它的默认行为解决了我过去几个最大的困扰:默认递归搜子目录,不用加-r;默认忽略 .gitignore 里列的东西,搜代码时不会被 node_modules 和 build 目录里几万个文件拖死;默认跳过二进制文件和隐藏文件,不会因为搜到某个二进制文件就给你吐一堆乱码。
速度上它确实比 GNU grep 快,但更值得说的是为什么快。一是它用了 Rust 的正则引擎并且对多文件搜索做了并行化,能跑满多核;二是它在遍历目录时先应用忽略规则再读文件,省掉了大量无谓的 IO;三是对大文件用了内存映射。实际感受是在一个几十万文件的仓库里搜关键词,grep 要几秒,rg 基本是瞬时出结果。
# 基础搜索,默认递归、默认忽略 gitignore rg "func main" # 只看文件名列表 rg -l "TODO" # 带上下文,显示匹配行的前后 3 行 rg -C 3 "panic:" # 只搜某种类型,rg 内置了类型定义 rg -t py "import requests" # 搜索时包含被 gitignore 的文件(CI 环境里很常用) rg --no-ignore "config" # 只输出匹配部分,配合 -r 做提取 rg -o -r '$1' 'rid=(\w+)' app.log它还有一个被低估的能力是配置文件。设一个环境变量export RIPGREP_CONFIG_PATH="$HOME/.ripgreprc",然后在这个文件里写你每次都要加的参数,比如--smart-case(有小写就忽略大小写、有大写就精确匹配,比单纯加-i聪明)、--hidden、--glob=!.git/。这样你敲的每一条 rg 都自带这些优化。
注意事项:ripgrep 默认用的是 Rust regex 引擎,它为了保证线性时间性能,故意不支持反向引用和环视这类高级语法。如果你的正则里用了
(?<=...)或者\1,会报错。解决办法是加-P参数切到 PCRE2 引擎,但要注意这时候性能会下降,而且 PCRE2 需要编译时支持,某些预编译包没开这个选项。
3.3 sd:正则替换不用再背反斜杠
sed是我最想换掉但又最难换掉的一个,因为它在脚本里无处不在。但交互式地做批量替换时,sd 的体验好太多了。看一个对比:把所有文件里的localhost:8080换成api.internal:8080,sed 写法是sed -i 's/localhost:8080/api.internal:8080/g' *.yaml,sd 写法是sd 'localhost:8080' 'api.internal:8080' *.yaml。少了一个s/.../.../的外壳,也少了那个让人紧张的-i。
捕获组的差异更明显。sed 用\1\2,sd 用$1${2},而且支持命名捕获。举个实际例子,把日志里的ts=1712345678 level=error重排成error 1712345678,sed 里我要写sed -E 's/ts=([0-9]+) level=(\w+)/\2 \1/',sd 里是sd 'ts=(\d+) level=(\w+)' '$2 $1',可读性立刻上去了。
几个关键参数:-p预览模式,只打印替换结果不写文件,我强烈建议所有批量替换先用-p跑一遍;-s字面量模式,不进正则引擎,搜带特殊符号的字符串时特别有用;-f指定文件列表。
这是一个必须记住的差异:sd 默认就是全局替换,等价于 sed 的
g标志,你不需要也不应该再加g。我第一次用的时候习惯性写成sd 'foo' 'bar' g结果发现它把文件里所有的字母 g 单独替换掉了。这个坑踩过一次就忘不了。另外 sd 的-i参数在它的语义里是"就地修改",跟 sed 一样,但因为它默认就是全文件替换,用之前一定先-p看一眼。
3.4 三者组合的实战:在海量日志里定位一次报错
说个真事。上周排查一个线上问题,手上有个 2.3GB 的应用日志,需要找出所有返回 500 的请求,把它们的时间戳和请求 ID 提取出来,看看有没有规律。用老的grep加awk组合,光跑一遍就要几十秒;用 rg 加 sd 加 bat 的组合,整个过程不到一分钟。
第一步先用 rg 摸清规模:rg -c "status=500" app.log,先知道大概有多少条,几十条还是几万条决定了后面策略完全不同。第二步提取关键字段:rg -o -r '$1 $2' 'ts=(\S+) .*?status=500.*?rid=(\S+)' app.log | head -50,这里-o只输出匹配部分,-r做捕获组重排,输出就很干净。第三步统计规律:把上一步的结果接| awk '{print substr($1,1,13)}' | sort | uniq -c | sort -rn,按小时聚合看是不是集中在某个时间窗口。
最后一步是看上下文。假设上一步发现请求 ID 是a3f9c2e1,我要看它前后发生了什么,用 bat 直接定位:rg -n "a3f9c2e1" app.log拿到行号是 12847392,然后batcat --line-range 12847380:12847420 app.log精准看这四十行。整个排查过程的核心思路是"先用 rg 缩小范围,再用 bat 精读",这个组合比翻文件快一个数量级。
4. 交互、结构化数据与系统观测
4.1 fzf:把任何列表变成可模糊搜索的交互界面
fzf 是这份清单里最难用一句话解释清楚的工具,因为它的定位是"通用模糊筛选器"。它读标准输入,在终端里给你一个可以用方向键和模糊输入筛选的界面,选中的结果写到标准输出。就这一个能力,配上各种命令之后能变出几十种用法。
安装完第一件事是启用快捷键绑定。bash 用户在新版里可以eval "$(fzf --bash)",旧版需要 source 那个 examples 目录下的 key-bindings 脚本。启用之后你会得到三个神仙快捷键:Ctrl+R模糊搜索历史命令(比默认的反向搜索好用十倍,因为它支持模糊匹配,你只记得命令里包含"docker log"这几个字就能翻出来),Ctrl+T在当前命令行里插入一个选中的文件路径,Alt+C变成一个可以模糊搜索的 cd。
# 用 fzf 选文件再打开 vim "$(fzf)" # 带预览的选文件,右边实时显示文件内容 fd -t f | fzf --preview 'batcat --color=always --style=numbers {}' --height 60% # 从进程列表里挑一个杀掉 ps -ef | fzf | awk '{print $2}' | xargs -r kill -9 # 切换 git 分支 git checkout "$(git branch --format='%(refname:short)' | fzf)" # 搜索并跳转到历史命令里的某个参数位置 history | fzf --tac | sed 's/^ *[0-9]* *//'要让 fzf 真正好用,必须改两个环境变量。export FZF_DEFAULT_COMMAND='fd -t f -H'把默认的查找命令从 find 换成 fd,速度差距在几十万文件的仓库里非常明显。export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border --info=inline'把界面变成底部弹窗式而不是全屏,这样不打断你的上下文。
4.2 jq 与 yq:结构化数据的命令行手术刀
到 2025 年了,配置文件大部分不是 JSON 就是 YAML,用 grep 去处理这两种格式基本等于自虐,因为一个字段可能跨好几行、缩进一变规则就失效。jq 处理 JSON,yq 处理 YAML,它们本质上是把结构化数据变成可以精确寻址的对象。
jq 的基本功:jq '.'格式化输出,jq '.items[].name'取出数组里每个元素的 name,jq '.[] | select(.age > 30)'条件筛选,jq -r去掉字符串外面的引号(不加这个参数拿到的结果会带引号,直接喂给下一步命令会出错),jq -s把多个 JSON 合并成一个数组处理。实际用得最多的场景是读接口返回和查配置文件:
# 从接口返回里提取所有实例的 IP curl -s http://internal-api/instances | jq -r '.data[].ip' # 找出所有副本数大于 3 的服务 kubectl get deploy -o json | jq -r '.items[] | select(.spec.replicas > 3) | .metadata.name' # 把嵌套 JSON 拍平成一行行 jq -r 'to_entries[] | "\(.key)=\(.value)"' config.jsonyq 处理 YAML 更贴近我们的日常,尤其是批量改配置。yq '.spec.containers[].image' deploy.yaml可以一次把所有容器的镜像列出来,加上-i直接原地修改文件:yq -i '.spec.template.spec.containers[].image = "registry.internal/app:v2.3.1"' *.yaml,一条命令改一批文件的镜像版本,比手改或者写 sed 靠谱得多。
这是一个团队协作里真实踩过的坑:yq 有两个主流实现,一个是 Python 写的(
pip install yq,底层其实是把 YAML 转成 JSON 再调 jq),另一个是 Go 写的(mikefarah/yq)。两者的命令行语法有差异,比如 Python 版更强调 jq 表达式,Go 版有很多自己的子命令。我们组里两个人照着同一份文档操作,结果一个成功一个报错,排查半天才发现装的不是同一个东西。装完先跑yq --version看清楚是哪个版本,团队里统一一下。
4.3 dust 与 bottom:磁盘和进程的现代观测面板
磁盘满了这件事,用du -sh *排查过的人都懂那种痛苦:输出一屏目录名加数字,没有排序、没有百分比、没有可视化,你还得自己sort -h再看。dust 把这些全做了,它默认就按大小排序,用条形图直观显示每个目录占总量的比例,还会自动递归到合适的深度。
常用参数:dust -d 2 /var限制只显示两层深度,避免输出太长;dust -n 20只显示前 20 项;dust -r /var反向排序,有时候反而能发现小文件扎堆的问题;dust -s只显示总大小不展开。实际排查磁盘告警的动线是dust -d 2 /看哪个顶层目录异常,然后一路-d往下降,通常三层之内就能定位到罪魁祸首。
bottom(命令名btm)是 top 和 htop 的现代替代,界面信息密度高很多,而且支持鼠标操作。核心快捷键:t切换树状进程视图(能看清父子关系,排查 fork 爆炸时特别有用),dd杀掉选中进程(先按一次 d 进入删除模式再确认,避免误杀),e展开某个进程的详细信息,/搜索进程名,方向键和鼠标滚轮直接滚动。它还带网络、温度、磁盘的实时图表,一台机器上想看什么基本都有。
注意:在容器里跑 bottom 经常显示不全,因为容器默认限制了 /proc 的可见范围,你看不到宿主机上的其他进程,CPU 和内存数据也可能是宿主机视角而不是容器的。这时候改用
docker stats或者专门给容器用的监控方案更准确。另外--battery参数在某些虚拟机上要额外权限,报错的话去掉这个参数就行。
4.4 delta:让 git diff 真正能读
这个严格来说是我清单里的第十一个,标题写着十个我得诚实,但 delta 我实在舍不得删,就当彩蛋放在这。它做的事情是把git diff的输出渲染成带语法高亮、行号、左右分栏的对比视图。原来一段 Python 代码改动你只能看到加减号,现在函数名、缩进层级、改动块的位置一目了然,代码审查效率直接翻倍。
配置就写在~/.gitconfig里,一次配好终身受用:
[core] pager = delta [interactive] diffFilter = delta --color-only [delta] navigate = true line-numbers = true side-by-side = true syntax-theme = Monokai Extended [merge] conflictstyle = diff3 [diff] colorMoved = defaultnavigate = true是个隐藏神技,配好之后在 diff 视图里可以用n和N在改动块之间跳转,代码审查时不用滚轮滚半天。side-by-side左右分栏在宽屏终端上非常爽,但终端宽度小于 100 列的时候会挤成一团,我现在是写了个判断终端宽度的小函数动态切换。
5. 装好只是开始:配置、别名与 GUI 的边界
5.1 包管理器选型与跨平台安装
同一个工具在不同系统上的安装方式差异挺大,而且坑主要出在 Debian 系。macOS 用 Homebrew,版本总是最新的,几乎不会有问题。Arch 用 pacman,滚动更新所以版本也很新。Fedora 用 dnf,基本够用。真正需要留意的是 Debian 和 Ubuntu,因为包名冲突和版本滞后两个问题都存在。
| 系统 | 包管理器 | 典型坑点 |
|---|---|---|
| macOS | Homebrew | 基本无坑,版本最新 |
| Arch / Manjaro | pacman | 基本无坑 |
| Fedora / RHEL | dnf | 部分工具要加 copr 源 |
| Ubuntu / Debian | apt | bat 装完叫 batcat,fd 装完叫 fdfind |
| 通用兜底 | cargo / 预编译包 | 放进 ~/.local/bin 并确保在 PATH 里 |
Ubuntu 上那个改名问题的原因很简单,bat和fd这两个短名字在 Debian 仓库里已经被别的老软件占用了,所以新工具只能用batcat和fdfind这两个名字。解决办法是加别名,或者在~/.local/bin里做个软链接:ln -s $(which batcat) ~/.local/bin/bat。我选后者,因为这样脚本里也能用bat这个名字。
还有一个版本滞后的现实问题。apt 仓库里的工具版本通常落后上游一到两年,比如某个 fd 的新参数在老版本里压根没有。碰到这种情况我一般走 cargo:cargo install fd-find,编译出来的二进制直接扔在~/.cargo/bin,版本是最新的。用 cargo 装的前提是你机器上有 Rust 工具链,只是偶尔装一两个工具的话,去 GitHub Release 页面下预编译二进制更省事,解压放到~/.local/bin加个执行权限就能用。
5.2 别名、函数与配置文件的组织方式
我在.bashrc里堆过两千行配置,最后的结果是每次改东西都像在雷区里走。现在的做法是把配置按主题拆成多个文件,放在~/.config/shell/下面,主配置里用循环 source 进来。
# ~/.bashrc 里只留这一小段 if [[ $- == *i* ]]; then for f in "$HOME"/.config/shell/*.sh; do [[ -r "$f" ]] && source "$f" done fi那个[[ $- == *i* ]]的判断很关键,它的意思是"只在交互式 shell 里加载这些别名和函数"。因为大量别名会破坏脚本行为,比如你把ls设成eza之后,某个脚本里写ls | awk '{print $9}'就会因为输出格式变了而拿到错误的列。用这个判断把别名圈在交互式 shell 里,脚本照旧用原生命令,两边都安全。
简单的东西用别名就够了,稍微复杂一点的用函数。比如我不喜欢每次rg都手动加排除目录,就写了个函数:
# ~/.config/shell/search.sh rgs() { rg --hidden --glob '!.git' --glob '!node_modules' --glob '!dist' "$@" } # 找到文件后直接看内容 look() { local f f=$(fd -HI -t f "$1" | fzf --preview 'batcat --color=always {}') [[ -n "$f" ]] && batcat --paging=never "$f" }5.3 远程机器上怎么快速铺开
我经常要登到临时机器上排查问题,那些机器上什么都没有,这时候就需要一套快速铺开的办法。我准备了一个 bootstrap 脚本,先检测发行版,然后分别走 apt 或 dnf 装基础包,接着把预编译二进制拷到~/bin,最后 source 一份精简的配置。整个脚本不到五十行,跑完大概两分钟。
有几个经验值得分享。第一,不要把整套 dotfiles 推上去,尤其是别去改别人的~/.bashrc,很容易覆盖掉别人的配置,而且是不可逆的。我现在的做法是全部塞进一个独立目录~/.local/cli-kit/,然后让用户自己决定要不要 source。第二,没有 root 权限时别硬装,预编译二进制是你唯一的选择,下到~/bin再export PATH="$HOME/bin:$PATH"就行,注意放在 PATH 前面避免和系统里的老版本冲突。
第三,远程机器的终端环境要提前确认。有些机器TERM变量是dumb或者没设置,颜色和图标全废,跑出来的东西比原生命令还难看。我的脚本里会检测一下TERM,如果是 dumb 就自动关掉所有花哨参数。第四,注意 .gitignore 规则的影响。有些生产环境的仓库里配了很激进的忽略规则,rg和fd默认会跳过那些文件,导致你以为文件不存在。这种情况在排查问题时很危险,脚本里我给rg加了--no-ignore的快捷别名。
5.4 哪些事交给图形化文件管理器更省事
说到这我想聊个有点反直觉的观点:不是所有事都该在命令行做。这些年桌面环境的文件管理器(比如深度桌面环境里的 dde-file-manager、GNOME 的 Nautilus、KDE 的 Dolphin)在权限管理这块做得其实很方便,右键属性里勾选权限的界面,比敲chmod 755直观太多了。
尤其是不常写八进制权限数字的人,rwxr-xr-x和644之间换算经常要停下来想一下。在图形界面里,你可以一次框选几十个目录,右键属性里统一勾上"所有者可读写执行、组可读可执行、其他可读可执行",一次操作搞定一批。而命令行里要写fd -t d -x chmod 755 {},写错一个数字就是权限事故。
还有一个典型场景是管理远程文件权限。桌面文件管理器一般内置了连接远程服务器的能力(FTP、SFTP 这类),连上之后整个目录树是可视化的,你想知道某个文件为什么访问不了,点开属性看权限列,一眼就能发现是不是少了执行位。相比之下在终端里ls -l看一屏权限字符,然后stat一个个查,效率差不少。
| 场景 | 推荐方式 | 理由 |
|---|---|---|
| 批量统一几个目录的权限 | 图形化文件管理器 | 一次框选右键搞定,不怕数字写错 |
| 查看远程目录的权限异常 | 图形化 + 终端辅助 | 图形看权限列,终端里 fd 定位文件 |
| 权限规则要写进部署脚本 | 命令行 chmod | 可复现、可版本管理、有审计记录 |
| 跨几十台机器统一权限 | 命令行 + 配置管理工具 | 图形界面无法批量化 |
| 临时给同事开个目录权限 | 图形化 | 操作快,不涉及脚本化需求 |
我的实际分工是这样的:探索和调试阶段用图形界面,因为反馈直观、试错成本低;固化和批处理阶段回到命令行,因为要可复现、要能进版本库、要能跨机器跑。这两者不是对立的,最舒服的状态是终端里用 fd 和 rg 负责"找",文件管理器负责"改",各干各擅长的事。
6. 踩坑记录与问题速查
6.1 颜色、字体与终端渲染问题
这类问题的表现高度一致:图标变成方块或者问号,颜色要么全没了要么刺眼得不行。原因基本就三个。第一是字体没装 Nerd Font,eza 的--icons、fzf 的部分符号、bottom 的图表都会受影响。解决办法是去下任意一款 Nerd Font 并在终端设置里选中它,注意改的是终端的字体设置,不是系统字体。
第二个原因是TERM变量设置不对。很多人在服务器上的TERM是xterm,某些工具就只给你 8 色。可以试试export TERM=xterm-256color,现代终端一般支持 256 色甚至 truecolor。如果想彻底解决,检查一下COLORTERM变量是不是truecolor,不是的话设一下。第三个原因是某些工具的颜色选择和你的终端主题冲突,比如浅色背景配白色高亮文字。这种时候可以用NO_COLOR=1环境变量全局关掉颜色,或者单独调某个工具的配色主题。
一个隐蔽的坑:bat 在管道里不加
--paging=never会卡住,这个前面提过。但还有个更隐蔽的版本——某些工具检测到 stdout 不是终端时会自动关掉颜色,结果你rg --color=always强制开颜色喂给下一个工具,下一个工具输出就乱了。判断原则很简单:给终端看的输出带颜色,给管道看的输出不带颜色,别强行覆盖。
6.2 与老脚本、老管道的兼容冲突
这是替换命令时最需要警惕的一类问题,因为它不会立刻报错,而是悄悄给你错误结果。最常见的是别名污染脚本。你把ls设成eza之后,脚本里任何ls -l | awk '{print $5}'取文件大小的写法都可能失效,因为 eza 的默认列顺序和 ls 不一样。
解决办法有三个层次。最稳的是脚本里用绝对路径或者command前缀,写command ls就会绕过别名。其次是在脚本开头unset掉可能有影响的别名。最后也是我推荐的,用前面讲的[[ $- == *i* ]]把别名限制在交互式 shell 里,从源头上避免脚本看到别名。
第二类冲突是默认行为的差异。rg 和 fd 默认忽略 .gitignore,这个设计在大部分时候是优点,但在 CI 里可能让构建脚本找不到文件。我在一个项目里遇到过:打包脚本用rg -l "version"找版本文件,本地跑没问题,CI 上因为 .gitignore 恰好把那个生成目录排除了,直接找不到文件。排查了半天才想到加--no-ignore。记住这个原则:交互式探索用默认行为,脚本里一律显式声明你要什么。
6.3 性能与资源占用的误解
很多人以为新版工具"用 Rust 写的所以一定快",这个结论只在一部分场景成立。rg 快是真的,但它快在并行和提前过滤,而不是单纯的语言优势。如果你的搜索范围在单个超大文件里,rg 和 grep 的差距其实没有想象中那么大。同理 fd 在本地磁盘上比 find 快,但在 NFS 或者网络挂载的目录上,它的并行遍历反而可能更慢,因为网络延迟放大了并发查询的开销。在网络盘上记得用fd -j1限制并发数。
第二类误解是"新工具占内存"。实际上 rg、fd 这类工具的内存占用和 grep、find 是一个量级的,真正占资源的是 eza 的--git参数(每个目录都要调一次 git)和 bottom 的实时采样(默认每秒刷新一次,可以调成更低频率)。eza --git 在几十万文件的仓库根目录跑,能让你明显感觉到等待,这时候要么缩小范围,要么干脆去掉这个参数。
第三类误解发生在容器里。bottom 在容器中显示的 CPU、内存数据经常不准,因为 /proc 看到的是宿主机视角或者被 cgroup 限制过,这不是 bottom 的 bug,是容器隔离机制决定的。容器里的资源观测应该用容器平台自己的工具,别指望 top 系的工具能看出真相。
6.4 一张速查表收尾
把最常用的组合整理成一张表,贴在你自己的备忘录里,前两周对着用,之后基本就刻进肌肉记忆了。
| 我想干什么 | 用什么命令 |
|---|---|
| 看目录,目录排前面带图标 | ls(已别名到 eza) |
| 看目录树两层 | lt(eza --tree --level=2) |
| 找某类型文件,排除干扰目录 | fd -e py -E node_modules |
| 在系统目录里找配置 | fd -HI -e conf . /etc |
| 跳到某个项目目录 | z 项目关键词 |
| 全项目搜关键词 | rg 关键词 |
| 搜被 gitignore 的文件 | rg --no-ignore 关键词 |
| 提取匹配部分 | rg -o -r '$1' 'pattern' file |
| 读某个文件的某段 | batcat --line-range 100:140 file |
| 批量正则替换(先预览) | sd -p 'old' 'new' *.yaml |
| 选一个文件再操作 | vim "$(fzf)" |
| 从进程里挑一个杀 | ps -ef | fzf | awk '{print $2}' | xargs -r kill |
| 看 JSON 字段 | jq -r '.data[].name' resp.json |
| 批量改 YAML | yq -i '.spec.x = "v2"' *.yaml |
| 找磁盘占用大头 | dust -d 2 /var |
| 看实时进程面板 | btm |
这十个工具我差不多花了两年时间才把清单固定下来,中间换掉过几个:一开始用 exa,后来因为项目停止维护换成了 eza;grep 和 rg 并存过很长一段时间,直到某次在一个大仓库里被 grep 卡了三秒才彻底倒向 rg。现在再看这台机器的配置,别名、函数、环境变量加起来也就两百行,但它们每天帮我省下的时间很难量化。你要是只打算试一个,我建议从 fd 开始,因为它替换掉的 find 语法最反人类,收益最直观;要是想一次装齐,就把上面这张表里的命令挑几个写进你的 shell 配置,先跑一周看看哪个用得上、哪个用不上,用不上的删掉就行,配置文件里留着不用的别名反而是负担。