第 8 章:安装与更新 —— 从 curl 脚本到 Git 工作树¶
8.1 安装脚本的职责边界¶
tools/install.sh 不是一个包管理器。它主要完成:
- 确定安装目标
ZSH; - 确定 Git 仓库
REPO、REMOTE和BRANCH; - clone 或更新 Oh My Zsh 目录;
- 备份已有
.zshrc; - 复制
templates/zshrc.zsh-template; - 可选地把 zsh 设置为默认 shell,并启动 zsh。
官方 README 允许用 curl、wget 或 fetch 获取脚本,也支持 --unattended。自动化环境应明确传入该 flag,避免安装脚本试图修改默认 shell或打开交互 zsh。
8.2 “先检查脚本,再执行”是合理的¶
安装通常来自网络管道:
这绕过了本地审查,因此官方文档也给出先下载再查看的路径:
curl -fsSL -o install.sh https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh
less install.sh
sh install.sh
这不是多余步骤:脚本会调用 Git、移动配置、改变 shell 入口,属于明显的外部状态变更。
8.3 三个仓库参数¶
| 参数 | 默认/含义 | 用途 |
|---|---|---|
REPO |
ohmyzsh/ohmyzsh |
GitHub owner/repository 形式 |
REMOTE |
根据 REPO 推导 |
完整 clone URL,可用 SSH/非 GitHub |
BRANCH |
master |
要 checkout 的分支 |
REMOTE 优先级高于 REPO。这使 fork、内网 Git 服务和测试分支都能复用同一安装脚本,但也意味着用户必须检查最终 remote,避免把生产 shell 指向意外仓库。
8.4 更新状态机¶
tools/check_for_upgrade.sh 在启动时执行。它把更新过程拆成“是否应该检查”“是否有更新”“是否执行”三层:
flowchart TD
A[读取 :omz:update mode] --> B{disabled/不可写/非 tty/无 git?}
B -- 是 --> X[return]
B -- 否 --> C[读取 .zsh-update]
C --> D{距离上次检查足够久?}
D -- 否 --> X
D -- 是 --> E[比较本地/远程 HEAD]
E --> F{有更新?}
F -- 否 --> G[更新状态文件]
F -- 是 --> H{reminder 或有输入?}
H -- 是 --> I[打印 omz update 提醒]
H -- 否 --> J{auto?}
J -- 是 --> K[执行 upgrade.sh]
J -- 否 --> L[询问 Y/n]
支持的 mode 包括 prompt、auto、reminder、实验性的 background-alpha 和 disabled。频率默认来自 UPDATE_ZSH_DAYS 或 :omz:update frequency。
8.5 更新检查如何避免误判¶
源码首先读取 Git remote 和 branch;只有 remote 指向官方 ohmyzsh/ohmyzsh 时,才调用 GitHub API 获取 branch 的 commit SHA。非 GitHub 或非官方仓库会保守地认为“有更新”,因为无法可靠判断远端状态。
本地 HEAD 与远端 HEAD 相等时没有更新;不等时还会尝试 git merge-base,区分本地只是落后还是已经分叉。网络失败与没有 curl/wget/fetch 时会尽量短路,不应该让整个 shell 初始化失败。
8.6 锁与状态文件¶
两个文件很重要:
$ZSH_CACHE_DIR/.zsh-update:保存LAST_EPOCH、退出码和错误文本;$ZSH/log/update.lock:目录存在即表示更新进行中,防止并发。
锁目录超过一天会被清理。trap 会在 EXIT/INT/QUIT 时删除锁并清理函数,保证 Ctrl-C 不会留下永久阻塞。
8.7 upgrade.sh 的设计¶
升级脚本以 zsh 执行,支持 -i 交互和 -v default|minimal|silent。它会把历史的 robbyrussell/oh-my-zsh remote 迁移到 ohmyzsh/ohmyzsh,然后执行 Git 更新。
值得注意的是它还探测终端是否支持 hyperlinks、truecolor,并据此决定更新输出。这些是用户体验代码,但不应影响升级的实际成功/失败状态。
手动升级入口是:
脚本化场景更适合直接调用 $ZSH/tools/upgrade.sh,不要模拟交互输入。
8.8 更新的风险模型¶
更新本质上是对当前安装目录执行 Git 操作,可能遇到:
- 用户改动了内置文件,导致 merge conflict;
- custom 同名覆盖使升级看似成功但行为不变;
- 插件/主题 API 发生变化;
- 多个 shell 并发启动,触发锁竞争;
- 自动更新在没有观察输出的情况下改变 prompt。
生产或共享机器建议用 mode reminder,由用户明确执行 omz update,并在更新前检查 $ZSH 工作树状态。