本文记录一次在 Apple Silicon Mac 上重装 Homebrew 的完整流程,适用于以下情况:
- 打开终端时 Homebrew 报 Ruby 栈错误,例如
Version value must be a string; got a NilClass /opt/homebrew中的 Homebrew 本体过旧,无法识别新版本 macOS- 想保留旧包清单,重新安装干净的 Homebrew
- 想把 Homebrew 配成国内较快的镜像源
本文以 Apple Silicon 默认路径 /opt/homebrew 为例。如果你的 Mac 是 Intel 芯片,默认路径通常是 /usr/local,不要直接照搬路径。
0. 本次修复后的目标状态
重装完成后,目标状态如下:
lua
brew --version
brew update
brew config
都应该正常执行,不再出现 Ruby NilClass 栈错误。
国内镜像配置采用中科大 USTC:
arduino
export HOMEBREW_API_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles/api
export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles
export HOMEBREW_BREW_GIT_REMOTE=https://mirrors.ustc.edu.cn/brew.git
注意:不要设置 HOMEBREW_CORE_GIT_REMOTE 到 USTC。新版 Homebrew 主要使用 API 元数据,中科大当前不再提供 homebrew-core.git 镜像,设置错误的 core Git remote 反而容易把环境搞坏。
1. 先确认环境
查看 macOS 版本、CPU 架构和 Homebrew 路径:
bash
/usr/bin/sw_vers -productVersion
/usr/bin/uname -m
/usr/bin/which brew
/opt/homebrew/bin/brew --version
/opt/homebrew/bin/brew config
Apple Silicon 正常应看到:
bash
arm64
/opt/homebrew/bin/brew
如果 brew --version 就报错,先继续往下做备份和重装。
2. 如果打开终端就报错,先临时关闭启动调用
很多终端启动报错是因为 ~/.zprofile 中有这行:
bash
eval "$(/opt/homebrew/bin/brew shellenv)"
或者旧写法:
bash
eval $(/opt/homebrew/bin/brew shellenv)
当 brew 本体坏掉时,每次打开 zsh 都会触发错误。先备份配置文件:
bash
cp ~/.zprofile ~/.zprofile.bak.$(date +%Y%m%d-%H%M%S)
然后打开编辑:
bash
nano ~/.zprofile
把 brew shellenv 那一行临时注释掉:
shell
# eval "$(/opt/homebrew/bin/brew shellenv)"
保存后,新开一个终端验证不再自动报错。
重装完成后再恢复这一行。
3. 如果使用 Clash,确认代理端口
如果网络需要 Clash/Clash Verge/Mihomo,先找本机代理监听端口:
bash
/usr/sbin/lsof -nP -iTCP -sTCP:LISTEN | grep -E 'clash|mihomo|789'
本次机器使用的是 Clash Verge 的 mixed-port:
makefile
127.0.0.1:7897
后续命令中如果你的端口不同,把 7897 换成自己的端口。
设置本次终端会话的代理变量:
arduino
export HTTP_PROXY=http://127.0.0.1:7897
export HTTPS_PROXY=http://127.0.0.1:7897
export ALL_PROXY=socks5://127.0.0.1:7897
可以测试 GitHub 和 USTC 是否可达:
bash
curl -I -x http://127.0.0.1:7897 https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh
git -c http.proxy=http://127.0.0.1:7897 ls-remote --symref https://github.com/Homebrew/brew.git HEAD
git -c http.proxy= ls-remote --symref https://mirrors.ustc.edu.cn/brew.git HEAD
如果直连 USTC 很快,git -c http.proxy= 可以绕开代理。
4. 导出旧 Homebrew 包清单
创建备份目录:
perl
STAMP=$(date +%Y%m%d-%H%M%S)
BACKUP="$HOME/Documents/work/homebrew-reinstall-backup-$STAMP"
mkdir -p "$BACKUP"
如果旧 brew 还能启动,导出包清单:
bash
/opt/homebrew/bin/brew list --formula > "$BACKUP/formulae.txt" || true
/opt/homebrew/bin/brew list --cask > "$BACKUP/casks.txt" || true
/opt/homebrew/bin/brew leaves > "$BACKUP/leaves.txt" || true
/opt/homebrew/bin/brew bundle dump --force --file="$BACKUP/Brewfile" || true
说明:
formulae.txt:所有 formula,包括依赖leaves.txt:你主动安装的顶层 formula,更适合用于恢复casks.txt:图形应用Brewfile:Homebrew Bundle 恢复文件
如果之前临时备份过 tap,Brewfile 里可能出现类似下面的行:
arduino
tap "homebrew/cask.bak-brewfix-20260713", "https://github.com/Homebrew/homebrew-cask.git"
tap "homebrew/core.bak-brewfix-20260713", "https://github.com/Homebrew/homebrew-core.git"
这些是临时备份目录,不应该用于恢复,后续执行 brew bundle 前要删掉。
5. 确认没有残留 brew 或 Git 更新进程
perl
ps -axo pid,ppid,stat,etime,command | grep -E '(/opt/homebrew/bin/brew|Homebrew|git .*homebrew|fetch-pack|index-pack)' | grep -v grep
如果没有输出,说明没有明显残留进程。
如果有正在运行的 brew update 或 Git fetch,先等它结束;确认卡死后再手动终止对应 PID。
6. 备份旧 /opt/homebrew 内容,不直接删除
确认 /opt/homebrew 存在且当前用户可写:
bash
ls -ld /opt/homebrew
df -P /opt/homebrew "$HOME/Documents/work"
如果 /opt/homebrew 和备份目录在同一个 APFS 数据卷上,移动会很快,基本是目录改名,不是复制 3G 文件。
移动旧 Homebrew 内容到备份目录:
bash
mkdir -p "$BACKUP/old-prefix"
find /opt/homebrew -mindepth 1 -maxdepth 1 -exec mv {} "$BACKUP/old-prefix/" ;
chmod 775 /opt/homebrew
确认现在 /opt/homebrew 是空目录:
bash
ls -la /opt/homebrew
du -sh "$BACKUP"
这一步的关键点:不要直接 rm -rf /opt/homebrew。先移动备份,出问题还能恢复。
7. 安装新的 Homebrew 本体
方案 A:官方交互安装脚本
如果你愿意输入管理员密码,可以用官方脚本:
bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
如果需要 Clash 代理:
ini
HTTPS_PROXY=http://127.0.0.1:7897 \
HTTP_PROXY=http://127.0.0.1:7897 \
ALL_PROXY=socks5://127.0.0.1:7897 \
/bin/bash -c "$(curl -fsSL -x http://127.0.0.1:7897 https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
注意:官方脚本在 macOS 上通常会检查 sudo 权限。
方案 B:手动克隆 Homebrew 本体
这是本次实际采用的方式。适合 /opt/homebrew 已经存在、为空、且当前用户可写的情况。
使用 GitHub:
bash
git -c http.proxy=http://127.0.0.1:7897 \
-c http.version=HTTP/1.1 \
clone https://github.com/Homebrew/brew /opt/homebrew
如果 USTC 直连更快,也可以直接克隆中科大镜像:
ini
git -c http.proxy= \
-c http.version=HTTP/1.1 \
clone https://mirrors.ustc.edu.cn/brew.git /opt/homebrew
如果 /opt/homebrew 不存在,可以先创建并授权:
bash
sudo mkdir -p /opt/homebrew
sudo chown -R "$(whoami)":admin /opt/homebrew
chmod 775 /opt/homebrew
8. 初始化并验证新版 brew
bash
/opt/homebrew/bin/brew --version
/opt/homebrew/bin/brew shellenv
eval "$(/opt/homebrew/bin/brew shellenv)"
让 Homebrew 初始化 Portable Ruby 和基础配置:
ini
HTTPS_PROXY=http://127.0.0.1:7897 \
HTTP_PROXY=http://127.0.0.1:7897 \
ALL_PROXY=socks5://127.0.0.1:7897 \
/opt/homebrew/bin/brew config
正常时应该看到:
yaml
HOMEBREW_PREFIX: /opt/homebrew
macOS: 15.x-arm64
Homebrew Ruby: ...
再执行:
ini
HTTPS_PROXY=http://127.0.0.1:7897 \
HTTP_PROXY=http://127.0.0.1:7897 \
ALL_PROXY=socks5://127.0.0.1:7897 \
/opt/homebrew/bin/brew update
正常输出类似:
vbnet
Already up-to-date.
9. 恢复 shell 启动配置并设置 USTC 镜像
编辑 ~/.zprofile:
bash
nano ~/.zprofile
加入或替换为下面几行:
bash
export HOMEBREW_API_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles/api #ckbrew
export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles #ckbrew
export HOMEBREW_BREW_GIT_REMOTE=https://mirrors.ustc.edu.cn/brew.git #ckbrew
eval "$(/opt/homebrew/bin/brew shellenv)" #ckbrew
同时把 brew 本体 Git remote 改到 USTC:
arduino
git -C /opt/homebrew remote set-url origin https://mirrors.ustc.edu.cn/brew.git
验证新登录 shell 是否生效:
dart
zsh -l -c 'echo HOMEBREW_API_DOMAIN=$HOMEBREW_API_DOMAIN; echo HOMEBREW_BOTTLE_DOMAIN=$HOMEBREW_BOTTLE_DOMAIN; echo HOMEBREW_BREW_GIT_REMOTE=$HOMEBREW_BREW_GIT_REMOTE; brew config'
git -C /opt/homebrew remote -v
zsh -l -c 'brew update'
期望看到:
ini
HOMEBREW_API_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles/api
HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles
HOMEBREW_BREW_GIT_REMOTE=https://mirrors.ustc.edu.cn/brew.git
ORIGIN: https://mirrors.ustc.edu.cn/brew.git
Already up-to-date.
10. 恢复旧包
优先使用 leaves.txt 恢复顶层包:
bash
[ -s "$BACKUP/leaves.txt" ] && xargs brew install < "$BACKUP/leaves.txt"
恢复 cask:
bash
[ -s "$BACKUP/casks.txt" ] && xargs brew install --cask < "$BACKUP/casks.txt"
如果你更信任 Brewfile,先检查并删除临时 .bak-brewfix tap,再执行:
bash
sed -n '1,160p' "$BACKUP/Brewfile"
brew bundle --file="$BACKUP/Brewfile"
本次旧环境中顶层包是:
helm
nginx
pcre
telnet
zlib
因此本次恢复命令是:
brew install helm nginx pcre telnet zlib
安装过程中可能看到:
pcre已弃用:这是警告,不影响安装;长期可以迁移到pcre2zlib是 keg-only:macOS 自带 zlib,因此 Homebrew 不会把它默认链接到 PATHnginx默认端口是 8080:无需 sudo 即可运行
11. 最终验证
bash
zsh -l -c 'brew --version && brew list --formula'
zsh -l -c 'brew update'
brew info wget
brew doctor
command -v brew
command -v helm
command -v nginx
command -v telnet
helm version --short
nginx -v
brew doctor 可能返回非零状态,但只要是 warning,不代表 Homebrew 重装失败。
本次遇到的 doctor warning 包括:
- Command Line Tools 有新版
pcre已弃用/usr/local/lib/libAliST.dylib是非 Homebrew dylib/usr/local/include/node/*是非 Homebrew header
这些不影响 brew 启动、更新和安装包。
12. 回滚方法
如果新 Homebrew 安装失败,可以把新内容移动到备份目录,然后恢复旧内容。
假设备份目录是:
ini
BACKUP="$HOME/Documents/work/homebrew-reinstall-backup-YYYYMMDD-HHMMSS"
回滚:
bash
mkdir -p "$BACKUP/new-prefix-failed"
find /opt/homebrew -mindepth 1 -maxdepth 1 -exec mv {} "$BACKUP/new-prefix-failed/" ;
find "$BACKUP/old-prefix" -mindepth 1 -maxdepth 1 -exec mv {} /opt/homebrew/ ;
然后恢复之前的 ~/.zprofile 备份:
bash
ls ~/.zprofile.bak.*
cp ~/.zprofile.bak.YYYYMMDD-HHMMSS ~/.zprofile
重新打开终端验证。
13. 清理备份
确认新 Homebrew 用几天都没问题后,再考虑删除旧备份:
bash
du -sh "$BACKUP"
确认路径无误后再删除。建议不要当天立刻删除,至少保留一段时间。
14. 常见问题
14.1 为什么不直接 brew update 原地修?
如果旧 Homebrew 太老,可能无法识别 macOS 15+,启动阶段就报错,brew update 自己也跑不起来。可以尝试手动更新 Git 仓库,但如果旧 tap 或浅克隆状态复杂,重装更干净。
14.2 为什么不设置 HOMEBREW_CORE_GIT_REMOTE?
新版 Homebrew 主要通过 API 获取 core/cask 元数据。中科大当前不再提供 homebrew-core.git,实际测试会返回 repository not found。设置错误的 core Git remote 没有收益,反而可能造成更新失败。
14.3 为什么官方安装脚本会失败?
官方脚本在 macOS 上通常会检查 sudo 权限。非交互环境里无法输入密码时,会看到类似:
csharp
Need sudo access on macOS
此时如果 /opt/homebrew 已经存在且当前用户可写,可以改用手动克隆 Homebrew 本体的方式。
14.4 Clash 开着但命令还是连不上怎么办?
GUI 应用的系统代理不一定会自动进入终端环境。给命令显式加代理变量:
arduino
export HTTP_PROXY=http://127.0.0.1:7897
export HTTPS_PROXY=http://127.0.0.1:7897
export ALL_PROXY=socks5://127.0.0.1:7897
或者在单条命令前临时加:
perl
HTTPS_PROXY=http://127.0.0.1:7897 HTTP_PROXY=http://127.0.0.1:7897 ALL_PROXY=socks5://127.0.0.1:7897 brew update
14.5 如何确认当前镜像配置?
arduino
brew config | grep HOMEBREW
git -C "$(brew --repository)" remote -v
应看到:
arduino
HOMEBREW_API_DOMAIN: https://mirrors.ustc.edu.cn/homebrew-bottles/api
HOMEBREW_BOTTLE_DOMAIN: https://mirrors.ustc.edu.cn/homebrew-bottles
HOMEBREW_BREW_GIT_REMOTE: https://mirrors.ustc.edu.cn/brew.git
origin https://mirrors.ustc.edu.cn/brew.git
15. 参考链接
- Homebrew 官方安装脚本:github.com/Homebrew/in...
- Homebrew 官网:brew.sh/
- 中科大 brew.git 镜像说明:mirrors.ustc.edu.cn/help/brew.g...
- 中科大 homebrew-bottles 镜像说明:mirrors.ustc.edu.cn/help/homebr...
- 清华 TUNA Homebrew 镜像说明:mirrors.tuna.tsinghua.edu.cn/help/homebr...