为什么不用 oh-my-zsh
oh-my-zsh 不是变差了,它解决的问题依然真实:开箱即有合理的 history 配置、几百个插件的补全、一个能看的 prompt。对刚从 bash 迁过来的人,这省掉几小时摸索。
代价是启动时间和不透明度。OMZ 默认加载一大堆用不到的东西,冷启动通常在 200–500ms。单看不算什么,但每开一个 tmux pane、每跑一次 zsh -c,这个成本都要付一遍。更麻烦的是出问题时难以定位——它的 lib/ 目录里十几个文件都在改 setopt、alias、compinit 行为,你很难知道某个诡异表现是谁造成的。
而真正让人上瘾的那两个功能(自动建议、语法高亮),本来就是独立项目,根本不需要 OMZ。
什么时候 OMZ 仍然合理: 你不想在 shell 配置上花任何时间;或者你需要它某几个插件的复杂补全(kubectl、gcloud 之类)而懒得单独找。这不丢人,只是如果你已经会读 zshrc,自己搭一套更划算。
安装
|
|
macOS 自带 zsh 5.9,够用。除非需要 5.10+ 的新特性,不必 brew install zsh。Apple Silicon 上 Homebrew 前缀是 /opt/homebrew,写死 /usr/local 的老教程要注意。
完整的 .zshrc
顺序有讲究,先给出全貌:
|
|
三条硬性约束
fpath的修改必须在compinit之前,否则 compinit 扫描时看不到那些补全函数。zsh-syntax-highlighting必须最后 source,它要 hook 所有已定义的 widget;放前面会漏掉后续插件注册的东西。compinit只调用一次。如果之前装过 OMZ 或别的框架,它们内部会自己调,重复调用会明显拖慢启动。
compinit 那行在干什么
~/.zcompdump(#qN.mh+24) 是 zsh 的 glob qualifier:
#q声明这是一个 qualifier(需要extended_glob)N= nullglob,文件不存在时不报错而是返回空.= 只匹配普通文件mh+24= 修改时间超过 24 小时
所以逻辑是:缓存超过一天,跑完整的 compinit(重新扫描 + 权限安全检查);否则 compinit -C 跳过检查直接读缓存,省下几十毫秒。
测启动时间
|
|
想知道具体是谁慢,在 zshrc 顶部加 zmodload zsh/zprof、底部加 zprof,开一个新 shell 就能看到逐项耗时。
五个包分别在做什么
zsh-autosuggestions
打字时根据历史记录在光标后显示灰色建议。输入 git com 会淡淡地补出你上次跑的 git commit -m "fix ingress"。→ 或 End 接受整条,Ctrl+→ 只接受一个词。
这是我认为 zsh 体验里价值最高的单个插件。日常命令重复率极高,长参数的 kubectl -n xxx logs -f deploy/yyy 尤其受益。
|
|
zsh-syntax-highlighting
实时给命令行上色:命令存在是绿色,不存在是红色,引号没闭合、路径不存在也会标出来。
真正的用处是回车前就发现错误。敲错命令名当场变红,不用等 command not found;粘贴一段带引号的长命令时,颜色立刻告诉你引号有没有配平。
fzf
模糊查找器。source <(fzf --zsh) 之后绑三个快捷键:
| 快捷键 | 作用 |
|---|---|
Ctrl+R |
历史搜索,替代默认那个只能逐条回溯的版本 |
Ctrl+T |
当前目录下模糊选文件,结果插入命令行 |
Alt+C |
模糊 cd 到子目录 |
Ctrl+R 是改变最大的一个。另外它还能当通用管道用:
|
|
zoxide
学习型 cd。访问过的目录会记录频率和时间,之后 z uts 就直接跳到 ~/uts-git/uts-server,不用打全路径。zi 打开 fzf 界面挑。
用一段时间后它基本能取代大部分 cd。想让它直接接管 cd 这个名字:
|
|
Starship
跨 shell 的 prompt,Rust 写的,git 状态异步获取。下面单独展开。
Starship 配置
配置文件在 ~/.config/starship.toml,没有就新建。starship config 可以直接打开它。
显示用户名和主机名
Starship 默认把这两个藏起来了——username 只在 root 或 SSH 会话显示,hostname 只在 SSH 显示。
|
|
为什么要改 format: 两个模块的默认格式各自都以 in 结尾,直接开启会得到 henry in hostname in ~/dir 这种重复。上面把 username 的尾巴去掉、用 @ 接上 hostname,只保留一个 in。
SSH 时想更显眼(同时开一堆节点窗口时很有用):
|
|
ssh_symbol 只在 SSH 会话里出现,本地不显示,一眼能分清自己在哪台机器上。
macOS 上 hostname 的坑: 默认可能显示成 Henrys-MacBook-Pro 或带 .local,连不同网络时还会变。固定下来:
|
|
Starship 也支持 trim_at = "." 只保留第一个点之前的部分。
读懂默认 prompt
一个典型的输出:
|
|
拆开看:
| 片段 | 模块 | 含义 |
|---|---|---|
hzhou@henry-mbp |
username + hostname | 上面配的 |
in some-repo |
directory | 默认 truncate_to_repo = true,进 git 仓库后只显示仓库根往下的路径 |
on dev |
git_branch | 分支名,前面是 nerd font 的分支图标 |
更值得注意的是没显示什么。 Starship 的 git 信息分成几个独立模块:
git_status— 脏文件、未跟踪、ahead/behind 计数。没出现说明工作区干净且与远端同步;有改动时会变成[!2 ?1 ⇡3]git_state— 只在 rebase / merge / bisect / cherry-pick 进行中才出现git_commit— 只在 detached HEAD 时显示 commit hashgit_metrics— 默认关闭,开了显示+120 -30的增删行数
关掉不需要的模块
|
|
与其一个个列,不如反过来用 format 做白名单,只声明要的,其余一律不显示:
|
|
这样以后进任何 Python / Node / Rust 项目都不会突然冒出新东西。代价是想加模块得手动改这一行。
starship explain 会列出当前 prompt 每个模块和它的来源,starship timings 显示各自耗时——超过 100ms 的基本都值得考虑关掉版本探测。
那个 Java 超时警告
|
|
Starship 渲染 prompt 时要执行 java -version 来拿版本号,默认 command_timeout 是 500ms,JVM 冷启动经常超过。
最省事的修法,在配置文件顶层(所有 [section] 之前)加:
|
|
必须放在文件最开头,放到某个 [xxx] 块下面 TOML 会把它当成那个块的字段。
但更好的做法是别让它跑 java -version。 每次 cd 进 Java 项目都启一次 JVM 只为拿版本号,代价太大:
|
|
同类问题在 [nodejs]、[python]、[ruby] 上也有,只是没 JVM 这么夸张。
command_timeout 和 cmd_duration 不是一回事
名字像,管的东西完全不同:
command_timeout— Starship 自己的内部预算。它执行java -version这类探测命令时最多等多久,超了就放弃并打 WARN。这是输入参数。cmd_duration— 纯展示模块。统计你上一条命令跑了多久,超过min_time就显示出来。这是输出。
|
|
顺带一提:如果已经把所有会执行外部命令的模块都关了,command_timeout 就没有作用了,可以删掉。
cmd_duration 值得留着——跑构建或长任务时能知道花了多久,不用手动掐表。
Starship 还能干什么
Kubernetes 上下文
|
|
contexts 数组按 context 名字匹配改样式——生产环境标红,能实实在在防住误操作。它读的是 kubeconfig 里的当前 context,不执行任何命令,没有性能开销。
自定义模块
最灵活的部分,任何 shell 能拿到的信息都能塞进 prompt:
|
|
when 是守卫条件,返回非零就整个跳过,不会执行 command。写自定义模块时守卫一定要写好,否则每次渲染都跑一遍外部命令。
其他值得开的模块
| 模块 | 用处 |
|---|---|
[status] |
上条命令的退出码,失败时显示 ✘127,比手动 echo $? 强 |
[shlvl] |
嵌套 shell 层数,进了容器再开 shell 时不容易迷路 |
[jobs] |
后台任务数 |
[time] |
时间戳,回溯"这条命令是几点跑的"很有用 |
[battery] |
低电量才显示,可按百分比设不同颜色 |
右侧 prompt
zsh 支持内容右对齐,不占左边空间:
|
|
转换字符
|
|
error_symbol 变红是个低成本的好设计——不用看输出就知道上条命令失败了。
现成的 preset
|
|
图标显示成豆腐块
装个 Nerd Font:
|
|
然后在终端配置里指定字体。Ghostty 是 font-family = JetBrainsMono Nerd Font。
小结
|
|
三十行 zshrc,一个 starship.toml,启动时间 30–60ms。没有框架,没有黑盒,每一行都知道在干什么。
出问题时的排查顺序:zprof 看谁慢 → starship timings 看哪个模块慢 → starship explain 看 prompt 上的东西是谁画的。