跳转至

第 8 章:安装与更新 —— 从 curl 脚本到 Git 工作树

8.1 安装脚本的职责边界

tools/install.sh 不是一个包管理器。它主要完成:

  1. 确定安装目标 ZSH
  2. 确定 Git 仓库 REPOREMOTEBRANCH
  3. clone 或更新 Oh My Zsh 目录;
  4. 备份已有 .zshrc
  5. 复制 templates/zshrc.zsh-template
  6. 可选地把 zsh 设置为默认 shell,并启动 zsh。

官方 README 允许用 curlwgetfetch 获取脚本,也支持 --unattended。自动化环境应明确传入该 flag,避免安装脚本试图修改默认 shell或打开交互 zsh。

8.2 “先检查脚本,再执行”是合理的

安装通常来自网络管道:

sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

这绕过了本地审查,因此官方文档也给出先下载再查看的路径:

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 包括 promptautoreminder、实验性的 background-alphadisabled。频率默认来自 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,并据此决定更新输出。这些是用户体验代码,但不应影响升级的实际成功/失败状态。

手动升级入口是:

omz update

脚本化场景更适合直接调用 $ZSH/tools/upgrade.sh,不要模拟交互输入。

8.8 更新的风险模型

更新本质上是对当前安装目录执行 Git 操作,可能遇到:

  • 用户改动了内置文件,导致 merge conflict;
  • custom 同名覆盖使升级看似成功但行为不变;
  • 插件/主题 API 发生变化;
  • 多个 shell 并发启动,触发锁竞争;
  • 自动更新在没有观察输出的情况下改变 prompt。

生产或共享机器建议用 mode reminder,由用户明确执行 omz update,并在更新前检查 $ZSH 工作树状态。