zsh — 比 bash 更聪明的兼容派 shell
待复核zsh 全名 Z Shell,是一个多数老 bash 脚本能直接或小改跑、又自带现代体验的命令行 shell。日常类比:bash 像老固话机,fish 是另起炉灶的智能机,zsh 则是把固话机改装成智能机——拨号方式没变,多了来电显示、通讯录、自动补全。
打开它,启用补全后最常见的体验是:
# 常见配置:让补全变成可切换的菜单zstyle ':completion:*' menu select# 然后输入 git che 再按 Tab$ git che<TAB>checkout cherry-pick cherry按 Tab 列出可选项,再 Tab 在选项之间切换,回车选定。这种菜单式补全靠 compinit + 上面那行 zstyle;bash 默认要按两下 Tab 才显示一个粗糙列表。
不学 zsh 也能写代码,但理解它能让你看清三件事:
- macOS Catalina(2019)起 zsh 是系统默认 shell —— 苹果把 bash 换掉,是 zsh 推广史最大的一次顺风
- 兼容优先 + 扩展叠加 —— 和 fish 的”故意不兼容”相反,zsh 选择”多数 bash 脚本可直接或小改运行,新功能往上加”。这是更保守、也更主流的演化路径
- zsh 是基础设施,不是终点 —— oh-my-zsh / prezto / zinit / starship 都把它当宿主,学会底层后这些”全家桶”才不再是黑盒
如果你 macOS 默认就在用 zsh,花一个下午搞懂它的启动文件链和补全系统,回报率比换 fish 还高——因为你已经在用了。
1. 启动文件链(最容易踩的入口)
Section titled “1. 启动文件链(最容易踩的入口)”zsh 启动时按顺序读 5 类文件,分别对应所有用户/当前用户和何种 shell:
/etc/zshenv → ~/.zshenv (所有 zsh 进程都读,含脚本)/etc/zprofile → ~/.zprofile (登录 shell 才读)/etc/zshrc → ~/.zshrc (交互 shell 才读,最常改)/etc/zlogin → ~/.zlogin (登录 shell,在 zshrc 之后)/etc/zlogout → ~/.zlogout (退出时)踩坑提示:~/.zshenv 是非交互脚本也会读的,写慢操作(如 nvm.sh)会让 git status 都变慢。把重活留给 ~/.zshrc。
2. compinit —— 补全引擎入口
Section titled “2. compinit —— 补全引擎入口”zsh 补全不是”读 man page 自动生成”,而是有人手写补全函数(如 _git、_kubectl),放在 $fpath 里。
autoload -Uz compinitcompinit这两行必须在 .zshrc。autoload 把函数延迟到第一次调用才加载;compinit 扫 $fpath 编译补全索引(缓存到 ~/.zcompdump)。
3. 递归 glob **
Section titled “3. 递归 glob **”zsh 比 bash 早多年支持:
ls **/*.md # 当前及所有子目录的 md 文件rm **/*.tmp(.) # (.) 限定只匹配普通文件(.) 这种glob 限定符是 zsh 独有,能在 glob 上加文件类型/时间/大小过滤,省掉一半 find 调用。
4. zle(Zsh Line Editor)
Section titled “4. zle(Zsh Line Editor)”命令行编辑器框架。你装的 zsh-syntax-highlighting / zsh-autosuggestions 都是 zle widget——一段在你按键时被调用的 zsh 函数。理解这个就理解了为什么”插件”能改命令行行为。
5. RPROMPT 与 prompt 系统
Section titled “5. RPROMPT 与 prompt 系统”zsh 支持右侧 prompt:
RPROMPT='%F{cyan}%~%f'bash 没有等价;这就是为什么 starship/powerlevel10k 在 zsh 上能做出更花哨的状态栏。
案例 1:zmv 批量重命名
Section titled “案例 1:zmv 批量重命名”autoload -U zmvzmv '(*).jpeg' '$1.jpg'逐部分解释:
autoload -U zmv:先把内建的批量改名工具加载进来(*)像夹子夹住文件名主体;.jpeg是要换掉的后缀$1是夹子夹到的那一段,拼成新名字.jpg- 不用写 for 循环——这是 zsh 内建的杀手锏
案例 2:参数扩展旗标
Section titled “案例 2:参数扩展旗标”files=(a.txt b.txt c.txt)echo ${(j:, :)files} # → a.txt, b.txt, c.txtecho ${(U)PATH} # → 把 PATH 全大写逐部分解释:
files=(...)先做一个数组,三个文件名各占一格${(j:, :)files}里的(j:, :)是旗标:用逗号+空格把格子拼成一串${(U)PATH}的(U)是”全大写”旗标——bash 没有这种短写法
案例 3:用 typeset -U 给 PATH 去重
Section titled “案例 3:用 typeset -U 给 PATH 去重”typeset -U pathpath=(~/bin /usr/local/bin $path)逐部分解释:
- zsh 里小写
path数组和PATH字符串是联动的 typeset -U path声明:这个数组自动去重,后加的重复值会被忽略- 再往里塞目录时,常年累月加 PATH 也不会爆炸成一长串重复项
-
~/.zshenv拖慢所有命令:非交互脚本也读它。nvm.sh这种重活放进去,每次git commithook 都慢半秒。解决:交互逻辑留给~/.zshrc。 -
compinit 抱怨 insecure directories:补全目录权限松(如
chmod 777)会被拒绝加载。临时绕过compinit -u,正解是chmod 755 $fpath。 -
数组从 1 开始:从 bash 迁移过来最容易翻车的一处。
${arr[1]}是第一个元素,不是第二个。KSH_ARRAYS选项可以切回 0-based,但不推荐——和生态不一致。 -
PATH 越累越长:
~/.zshenv和~/.zshrc都export PATH=...:$PATH,登录 shell 会读两次,PATH 翻倍。用typeset -U path一劳永逸。
适用 vs 不适用场景
Section titled “适用 vs 不适用场景”适用:
- macOS 日常用户(系统默认就是它)
- 本机交互:想要”老脚本大多能跑 + 现代体验”的兼顾派
- 用 oh-my-zsh / starship / powerlevel10k 全家桶的人
不适用:
- CI / 服务器最小 init 脚本——优先
/bin/sh,本机交互再上 zsh - 嵌入式或 BusyBox 环境(zsh 体积比 ash/bash 大)
- 想要极致开箱体验的人——zsh 默认很朴素,要靠插件堆出 fish 那种感觉
历史小故事(可跳过)
Section titled “历史小故事(可跳过)”- 1990 年:普林斯顿学生 Paul Falstad 写出 zsh,名字取自当时助教 Zhong Shao 的首字母
- 1997 年:zsh 3.0,功能闭环成形——补全、glob 限定符、zle 都到位
- 2009 年:Robby Russell 发起 oh-my-zsh,让”配 zsh”从手写 .zshrc 变成
curl | sh一键 - 2019 年:macOS Catalina 默认 shell 从 bash 切到 zsh(bash 停在 3.2 不能升是 GPLv3 许可问题),zsh 一夜成为亿级用户工具
这条线说明 zsh 不是赢在功能新,而是赢在兼容性 + 苹果背书。
- 兼容是策略不是包袱——fish 选断舍离,zsh 选叠加,两条路都活下来
- 基础设施和上层框架要分清——zsh 是 shell,oh-my-zsh 只是配置;混淆会让”我的 shell 怎么这么慢”找不到根
- 默认体验不是终点——zsh 默认朴素,但留了 zle/fpath/补全这些扩展点,让别人能堆出花
- 官方手册:zsh.sourceforge.io/Doc/Release/
- 对照 bash 的迁移指南:scriptingosx.com/2019/06/moving-to-zsh/
- 配置框架入口:oh-my-zsh
- 启动文件可视化:zsh startup files diagram
- fish —— 另一条 shell 路线,故意不兼容 POSIX 换默认体验
- nushell —— 第三条路,把命令之间传结构化数据
- starship —— 跨 shell prompt,常和 zsh 搭
- warp —— 现代终端,底层常驻一个 zsh
- fish —— fish — 装好就比 bash 加插件好用的交互 shell
- oh-my-posh —— oh-my-posh — 一份配置让所有 shell 都长一个样
- starship —— Starship — 一份配置点亮所有 shell 的 prompt
- tmux —— tmux — 一个终端窗口里跑多个会话还能脱离重连
- xonsh —— xonsh — 在同一行里同时写 bash 命令和 Python 代码