拿到一台 Apple Silicon 的 Mac,重装开发环境,第一步几乎都是 Homebrew。但这个"第一步"现在坑特别多:网上教程有的让你改~/.bash_profile,有的让你把 brew 装到/opt/homebrew,还有人说环境变量要写进~/.zprofile——等你真的敲brew install xxx时,可能又撞上command not found或者安装脚本跑到一半就中断。这篇文章专门解决一个具体问题:macOS(Apple Silicon)上 Homebrew 环境变量到底怎么配,以及如果想把 Homebrew 放到~/Develop/Tools/Homebrew这种自定义目录,应该怎么做。后面会把实际踩过的报错和修复过程完整列出来,覆盖从终端找不到 brew 到镜像源、Rosetta 前缀冲突、系统版本不支持这些高频问题。
1. Apple Silicon 上 Homebrew 的"地盘"全变了:/opt/homebrew、zsh 与 .zprofile
1.1 Intel 时代和 Apple Silicon 时代的路径差异
先搞清楚一件事:同一句export PATH="/opt/homebrew/bin:$PATH",在 Intel Mac 上反而不对。Intel 时代 Homebrew 的默认前缀是/usr/local,Apple Silicon 上强制使用/opt/homebrew。为什么?根本原因是两种架构的二进制不能混用。
Apple Silicon 的 Homebrew 会下载、编译 arm64 架构的软件包,如果把这类软件塞进/usr/local,和 Intel 时代遗留的 x86_64 工具混在一个目录,which python3、which node这类命令的结果会变得不可控。所以 Homebrew 官方的做法是:在 Apple Silicon 上直接把"地盘"划到/opt/homebrew,彼此隔离。
这也是为什么很多老教程会坑人。你拿一篇 2019 年之前的教程照做,它让你往 PATH 里加/usr/local/bin,在 Apple Silicon 上当然看起来什么都没发生——因为你的 brew 根本不在那里。安装时如果看到Cannot install in Homebrew on ARM processor in Intel default prefix (/usr/local)!,也是这个逻辑在保护你。
1.2 zsh 接管默认 shell 后,配置文件也跟着变了
第二个容易混淆的点是配置文件。macOS Catalina 之后默认 shell 从 bash 换成了 zsh,Apple Silicon 机器出厂就是 zsh。所以那些教你改~/.bash_profile的教程,在 Apple Silicon 上基本可以直接忽略了——zsh 默认不会读 bash 的配置文件。
zsh 的配置读取顺序大致是:
| 配置文件 | 读取时机 | 适合放什么 |
|---|---|---|
~/.zshenv | 所有 zsh 进程启动时 | 尽量少用,容易污染非交互环境 |
~/.zprofile | 登录 shell 启动时(比如打开新的终端窗口) | PATH、全局环境变量 |
~/.zshrc | 每个交互式 zsh 启动时 | 别名、提示符、插件、函数 |
~/.zlogin | 登录 shell 启动的后期 | 需要等前面配置就绪的命令 |
~/.zlogout | 登录 shell 退出时 | 收尾清理 |
Homebrew 官方安装脚本提示你写入的是~/.zprofile,因为 macOS 的 Terminal、iTerm2 开新窗口默认走登录 shell,.zprofile会在.zshrc之前加载,适合放PATH这类全局环境变量。
1.3 环境变量到底写进哪个文件:.zprofile 与 .zshrc 的取舍
这里我想多说两句实战取舍,因为网上观点经常打架。
如果你只在 Terminal.app 或 iTerm2 里用命令行,写~/.zprofile就够了,官方安装脚本也是这么建议的。但如果你经常用 VSCode、IntelliJ 这类 IDE 的内置终端,或者喜欢在 tmux 里开新窗格,情况就不一样了:IDE 内置终端不一定以登录 shell 方式启动,tmux 新窗格很多时候也不走登录 shell,它们只读~/.zshrc。这时候你只配了.zprofile,brew 命令就会"时灵时不灵"。
我的做法是:环境变量直接写在~/.zshrc里。.zprofile留给其他必须"登录时只执行一次"的逻辑。如果你担心 IDE 内置终端不读.zshrc,其实绝大多数 IDE 的终端都按交互 shell 启动,是读.zshrc的。当然,两个文件都写同一行export PATH也不会出错,只是 PATH 里会有重复项,查找时不影响结果。二选一的话,我更推荐.zshrc,覆盖场景更广。
2. 为什么有人要把 Homebrew 装进 ~/Develop/Tools/Homebrew,以及这样做的代价
2.1 自定义目录的真实动机
标题里那个路径~/Develop/Tools/Homebrew,一看就是想把开发工具收敛到统一目录。我见过不少开发者的目录规划是这样的:
~/Develop ├── Projects ├── Archive └── Tools ├── Homebrew ├── Nodejs └── ...好处很明显:备份时整个~/Develop一起打包;换机器时知道自己的工具链都装在哪;公司安全策略要求开发工具不能散落到系统目录,只能放用户目录;还有一些人有"根目录洁癖",不想动不动就往/opt下写东西。这些动机我都理解。
但必须把丑话说在前面:自定义路径不是 Homebrew 官方主推的玩法,它主要服务于标准安装/opt/homebrew。你选择自定义路径之后,网上大部分默认安装的教程都会"部分失效",遇到问题排查时也不能直接抄答案。
2.2 自定义路径后,Homebrew 是怎么定位"前缀"的
Homebrew 的"前缀"(prefix)不是靠某个环境变量硬编码的,而是由bin/brew这个可执行文件的位置倒推出来的。brew脚本运行时会找到自己所在的bin目录,父目录就是 prefix。
也就是说:只要brew这个脚本在~/Develop/Tools/Homebrew/bin/brew,Homebrew 就会认为~/Develop/Tools/Homebrew就是它的前缀,后续的 Cellar、Cask、Taps 都会以它为根。这一点其实是自定义安装可行性的基石。你不需要去设置HOMEBREW_PREFIX来指定前缀,设置了反而可能造成混乱。真正需要的,只是让 shell 能找到brew,也就是 PATH 里包含~/Develop/Tools/Homebrew/bin。
2.3 代价与我的建议
说点实在的代价:
- 少数 formula 在编译或运行时假设了标准路径,会有关联问题,虽然不频繁,但遇到一次就够你折腾半天。
- 网上关于
/opt/homebrew的报错解决方案多,自定义路径的少,排查问题时往往要把别人的答案"翻译"一遍。 - 官方安装脚本不支持你指定这样的自定义前缀,必须走 git clone 方式,安装步骤要多几步。
所以我通常建议:如果是个人电脑,老老实实装到/opt/homebrew;如果是公司统一开发机、或你确实有统一目录管理的强需求,才考虑自定义路径。安装方式都是可逆的,装到自定义目录以后想切回标准路径也不难,把所有环境变量配置清掉、重新跑官方脚本就行。
3. 自定义路径安装实操:git clone、目录结构与环境变量一步到位
3.1 用 git clone 方式安装 brew 本体
先确保已经安装了 Xcode Command Line Tools。没有的话,装一半会报xcrun: error。打开终端执行:
xcode-select --install弹窗提示时点安装,等它跑完。装完可以验证一下:xcode-select -p,能看到/Library/Developer/CommandLineTools就算就绪。
然后开始安装 brew 本体:
mkdir -p ~/Develop/Tools cd ~/Develop/Tools git clone https://github.com/Homebrew/brew Homebrew这会在~/Develop/Tools下面创建一个Homebrew目录,里面就是 brew 命令本体。注意:这一步只是把管理工具拉下来了,不包含软件包仓库。如果你直接用brew install可能会发现找不到 formula。
3.2 补齐 homebrew-core 目录
Homebrew 4.0 之后默认从 JSON API 获取 formula 信息,理论上不拉homebrew-core也能跑。但自定义安装时我建议一次补齐,避免后续出现"tap 缺失"之类的玄学报错。
mkdir -p ~/Develop/Tools/Homebrew/Taps/homebrew git clone https://github.com/Homebrew/homebrew-core ~/Develop/Tools/Homebrew/Taps/homebrew/homebrew-core如果你所在的网络环境拉 GitHub 很慢,clone 的仓库地址也可以换成国内镜像,我后面第 6 章会专门说镜像配置。先按官方地址来。
cd ~/Develop/Tools/Homebrew brew update第一次brew update会拉取元数据,可能比较慢,跑完以后 brew 本体就具备完整的安装能力了。
3.3 写入 .zprofile 并用 brew shellenv 接管环境变量
这里不要手写export PATH="$HOME/Develop/Tools/Homebrew/bin:$PATH",官方更推荐用brew shellenv动态生成环境变量。它会一次性输出HOMEBREW_PREFIX、HOMEBREW_CELLAR、HOMEBREW_REPOSITORY、PATH、MANPATH、INFOPATH这些内容,比自己手工 export 干净得多。
执行:
echo 'eval "$($HOME/Develop/Tools/Homebrew/bin/brew shellenv)"' >> ~/.zprofile source ~/.zprofile如果你前面的选择是写在.zshrc,就把目标文件换成.zshrc,内容一样。注意:这行里$HOME不要想当然展开成绝对路径,单引号包裹会让它保持变量形式,zsh 每次启动时才展开,这样以后如果系统迁移、用户名变化,配置还能继续用。
为什么不推荐手动export PATH?因为brew shellenv除了 PATH 还会维护MANPATH和INFOPATH,手动写的话很容易漏掉 man 手册路径,导致man brew都直接报错。不过现实中手动写也不是不能用,只是后续维护起来细节多。既然官方给了更稳的方式,建议直接用。
3.4 验证是否安装成功
安装完不要急着装软件,先做三件事:
which brew brew --version brew --prefix期望输出:
which brew指向/Users/你的用户名/Develop/Tools/Homebrew/bin/brewbrew --version正常显示版本号brew --prefix输出/Users/你的用户名/Develop/Tools/Homebrew
如果which brew没输出,说明 PATH 没生效,检查.zprofile或.zshrc是否真的写入成功,然后重新source。如果前缀和预期不一致,多半是目录结构不对,检查brew是否真的在~/Develop/Tools/Homebrew/bin/下。
注意:
brew --prefix是 Homebrew 内部根据可执行文件位置推导出来的,不是从环境变量读的。它输出的内容和你预想的一致性,是自定义安装是否成功的最重要指标。
4. 标准路径 /opt/homebrew 下的环境变量配置姿势:brew shellenv 与 .zprofile/.zshrc 的取舍
4.1 官方安装脚本与自动提示的两条命令
回到默认方案。Apple Silicon 上跑官方安装脚本:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"安装完成以后,终端会明确提示你执行两条命令:
(echo; echo 'eval "$(/opt/homebrew/bin/brew shellenv)"') >> /Users/你的用户名/.zprofile eval "$(/opt/homebrew/bin/brew shellenv)"第二条命令只是在当前窗口立即生效,第一条是永久写入。很多人第一次装完 brew,直接关掉窗口再打开,发现brew不存在,就是因为只执行了第二条、没执行第一条,或者把第一条里的路径写错了。
4.2 为什么官方推荐 eval "$(brew shellenv)" 而不是手动 export PATH
brew shellenv输出大概长这样:
export HOMEBREW_PREFIX="/opt/homebrew"; export HOMEBREW_CELLAR="/opt/homebrew/Cellar"; export HOMEBREW_REPOSITORY="/opt/homebrew"; export PATH="/opt/homebrew/bin:/opt/homebrew/sbin${PATH+:$PATH}"; export MANPATH="/opt/homebrew/share/man${MANPATH+:$MANPATH}"; export INFOPATH="/opt/homebrew/share/info:${INFOPATH:-}";看到区别了吗?它不只会设置 PATH,还设置了HOMEBREW_PREFIX、HOMEBREW_CELLAR、HOMEBREW_REPOSITORY,以及MANPATH和INFOPATH。手动export PATH只能管住命令查找,管不了 man 手册和 info 文档。中途升级 Homebrew 时,brew shellenv会跟着新版本重新计算,手动写死路径则容易留下旧路径残留。
4.3 实际项目中 .zprofile 和 .zshrc 怎么选
正常情况下,官方提示写入.zprofile,这在 Terminal 开新窗口的场景下好使。但如果你也同时用 IDE 内置终端,我前面说过了,直接加到.zshrc更省心。
不放心的话,也可以保持官方推荐不动,额外在.zshrc里补一行:
if [ -f /opt/homebrew/bin/brew ]; then eval "$(/opt/homebrew/bin/brew shellenv)" fi这样两边的场景都覆盖了。注意加了判断,即使/opt/homebrew目录不存在,zsh 启动也不会报错。这算是我在实际项目中比较喜欢的一种写法。
5. command not found: brew——先分清是没装上还是没进 PATH
5.1 最经典的"装完却敲不出 brew"排查链路
zsh: command not found: brew是出现频率最高的报错。很多人第一反应是重装,其实绝大多数情况是 PATH 没配好。
排查顺序很重要,不要一上来就重新执行安装脚本。第一步,先确认 brew 本体到底存不存在:
ls -l /opt/homebrew/bin/brew # 标准路径 ls -l ~/Develop/Tools/Homebrew/bin/brew # 自定义路径文件存在,说明装上了,问题出在 shell 找不到它;文件不存在,才需要考虑重新安装。
第二步,看 PATH 里有没有对应的 bin 目录:
echo $PATH | tr ':' '\n' | grep -n homebrew没输出,说明配置没写进去,或者写了但没加载。第三步,检查当前 shell 到底是哪个、配置文件是否选对了:
echo $SHELL ls -la ~/.zprofile ~/.zshrc ~/.bash_profile 2>/dev/null如果echo $SHELL输出/bin/zsh,那你改.bash_profile就是白改。如果文件存在但没生效,大概率是当前窗口没有重新加载,手动执行:
source ~/.zshrc这一套链路走下来,九成的command not found都能解决。
5.2 PATH 的写入顺序、引号展开和 shell 切换细节
还有几个细节很多人会忽略。第一个是追加顺序。export PATH="/opt/homebrew/bin:$PATH"把 Homebrew 放在最前面,意味着which python3时优先找到 brew 装的版本;如果写成export PATH="$PATH:/opt/homebrew/bin",系统自带的/usr/bin/python3会优先命中,经常出现"明明 brew install 了 python,但还是老版本"的诡异现象。所以要注意把 Homebrew 的 bin 放在前面。
第二个是引号问题。配置里$HOME有没有被正确保留,决定了路径展开是否正确。如果手写配置时把路径写成了~/Develop/Tools/Homebrew /bin(中间不小心多了一个空格),或者用了特殊字符没加引号,PATH 里就会混入错误项。建议粘贴配置时留意,路径中含空格时一定加双引号。
第三个是 shell 切换。有些用户从 bash 切到 zsh,或者从 zsh 切回 bash,但只在一个 shell 的配置文件里写了路径,另一个 shell 里照旧找不到 brew。可以检查一下echo $SHELL,然后确认对应的配置文件。Apple Silicon 上基本建议统一用 zsh,少给自己找麻烦。
5.3 非交互场景(脚本、定时任务)里 brew 消失的问题
还有一种隐蔽情况:终端里brew正常,但在脚本或定时任务里跑却报command not found。
原因在于 cron、launchd 启动的非交互 shell 不会加载.zshrc和.zprofile。这种场景需要在脚本开头显式指定路径,或者先加载配置:
#!/bin/zsh source ~/.zprofile brew update如果你写的是 launchd plist,建议不要在 plist 的ProgramArguments里直接写brew,而是写绝对路径。可以用which brew查出来,比如/opt/homebrew/bin/brew。这个坑我自己在写自动更新脚本时踩过,排查了半天才反应过来是非交互环境根本没加载配置。
6. 安装链路报错实录:镜像源、Rosetta 前缀、老系统与卸载残留
6.1 下载安装脚本超时:换用国内镜像源的标准操作
官方安装脚本是从raw.githubusercontent.com拉取的,这个地址在某些网络环境下经常连接超时,报错形如:
curl: (7) Failed to connect to raw.githubusercontent.com port 443: Operation timed out这种时候别死磕官方地址,换国内镜像源是通用做法。中科大镜像提供的安装脚本地址是:
export HOMEBREW_INSTALL_FROM_API=1 export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.ustc.edu.cn/brew.git" export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.ustc.edu.cn/homebrew-core.git" export HOMEBREW_API_DOMAIN="https://mirrors.ustc.edu.cn/homebrew-bottles/api" export HOMEBREW_BOTTLE_DOMAIN="https://mirrors.ustc.edu.cn/homebrew-bottles" /bin/bash -c "$(curl -fsSL https://mirrors.ustc.edu.cn/misc/brew-install.sh)"执行完以后,brew 本体和 core tap 都会走镜像地址。如果是已经装完 Homebrew、只是后续brew update或brew install很慢,同样可以设置这几个环境变量再加入.zprofile或.zshrc,让每次 shell 启动自动走镜像。清华的 TUNA 镜像也提供类似变量,习惯用哪个都行。
要提醒一点:换镜像时尽量保持HOMEBREW_BREW_GIT_REMOTE、HOMEBREW_CORE_GIT_REMOTE、HOMEBREW_BOTTLE_DOMAIN属于同一家源,不要一股脑混着填,否则可能出现"元数据来自 A 源、二进制来自 B 源"的错位,排查起来更麻烦。
6.2 "Cannot install in Homebrew on ARM processor in Intel default prefix" 的 Rosetta 坑
这个报错很典型:
Error: Cannot install in Homebrew on ARM processor in Intel default prefix (/usr/local)!Apple Silicon 的 CPU 明明是 ARM,安装脚本却想装到 Intel 默认前缀/usr/local,两者冲突,直接中止。出现这种情况,绝大多数是因为终端进程是在 Rosetta 模拟下运行的。最常见的原因是用户在终端 App 的设置里勾选了"使用 Rosetta 打开",或者手动用arch -x86_64 zsh启动了终端。
排查和解决:
uname -m期望输出arm64。如果输出是x86_64,说明当前环境是 Rosetta 模拟的。处理办法:退出终端,右键 Terminal.app 或 iTerm2 的图标,确认没有勾选"使用 Rosetta 打开",或者退出后重新打开。一般这样就能恢复正常。
顺带一提,有些不了解情况的用户会反过来想:既然想装 x86_64 的 Homebrew,那就干脆让它装到/usr/local好了。但 Apple Silicon 上原生方案就是 arm64 Homebrew,别自己制造双架构混乱。日常使用中绝大多数软件都能跑在 arm64 下,没必要用 Rosetta 跑整套工具链。
6.3 老系统版本不再支持、卸载残留与权限类报错
Homebrew 已经不维护旧版 macOS 了。如果你的系统是 macOS 10.15 或更早,安装或更新时可能看到类似Homebrew no longer supports macOS 10.15的提示,之后安装流程直接中止。这种情况官方不会修复,绕过的办法也不建议,最实际的路就是升级系统,或者用相对历史版本的安装方式。自制项目可以固定旧版本 tap,但日常使用不建议在一台老系统上强行维护 Homebrew。
再一个是卸载不彻底的残留问题。有些人以前装过 Homebrew,后来卸载了,但重新安装时报It seems Homebrew is already installed。这时候需要确认残留到底在哪:
ls -ld /opt/homebrew 2>/dev/null cat ~/.zprofile 2>/dev/null | grep -n brew cat ~/.zshrc 2>/dev/null | grep -n brew确认残留目录是 Homebrew 自己的,再删除:
sudo rm -rf /opt/homebrew然后清理配置文件里的brew shellenv相关行,重新安装即可。这里再次强调:不要顺手sudo rm -rf /opt,/opt是系统目录,里面可能还有其他东西。只删/opt/homebrew这一个目录就好。
权限类报错也经常和残留有关,比如No such file or directory @ rb_sysopen - /opt/homebrew/...、Permission denied。常见原因是目录属主不对。标准处理:
sudo chown -R "$USER":admin /opt/homebrew自定义目录同理,确保~/Develop/Tools目录属于你自己,而不是之前用 root 创建留下的。
6.4 brew install redis 等软件时的联动报错
很多人装 redis 也会踩到 Homebrew 的关联坑。最典型的是两个:
一是装完以后redis-cli还是找不到。这其实不是 redis 的问题,是 brew 的 bin 目录没进 PATH,或者 shell 缓存了旧命令。用which redis-cli检查路径,再用brew services start redis启动服务,然后用redis-cli ping验证,能返回PONG就说明环境正常。
二是编译安装失败,报各种make、cc、xcrun错误。这类问题绝大部分是 Xcode Command Line Tools 缺失或路径异常,执行:
xcode-select --install # 或者修复路径 sudo xcode-select --reset sudo xcode-select -s /Library/Developer/CommandLineTools另外一个隐蔽情况:如果你不小心在 Rosetta 终端里执行了brew install redis,装出来的可能是 x86_64 版本,跑起来性能差且和系统的 arm64 生态割裂。所以每当你安装任何软件时,先确认uname -m是arm64,再动手。
7. 配置完成后的健康检查与日常维护习惯
7.1 brew doctor、brew config 和 brew --prefix 三个验证入口
环境配好、安装成功之后,建议养成每次重装环境后"体检"的习惯。三个命令就能看出大问题:
brew doctor brew config brew --prefixbrew doctor会列出当前 Homebrew 环境里它认为有隐患的地方,比如目录权限问题、路径冲突、多余环境变量等。刚装完基本是干净的,如果提示Warning,按提示修一遍再进入下一步。brew config会输出编译环境信息,重点看这几行:
HOMEBREW_PREFIX:前缀,标准路径是/opt/homebrew,自定义路径是你的目标目录HOMEBREW_BOTTLE_DOMAIN:是否指向镜像macOS版本和CLT版本:是否满足要求
7.2 常用维护命令与 Brewfile 备份迁移
日常维护其实不需要太操心,几条常用命令记住就好:
brew update # 更新 brew 自身和 formula 索引 brew upgrade # 升级所有已安装的软件包 brew cleanup # 清理旧版本缓存 brew autoremove # 自动移除不再需要的依赖 brew services list # 查看后台服务状态换机器或备份环境时,强烈建议用 Brewfile。在旧机器上执行:
cd ~/Develop brew bundle dump --file=~/Brewfile新机器装好 Homebrew 以后执行:
brew bundle install --file=~/Brewfile它会把之前所有通过 brew 安装的 formula 和 cask 自动装回来。这个习惯能让你从"手动重装一百个软件"的痛苦里解放出来。自定义路径~/Develop/Tools/Homebrew时,备份逻辑也简单:brew bundle dump生成的清单记录了所有包名,配合统一的~/Develop目录整体 rsync,恢复环境非常快。
7.3 最后一个小技巧:优先用 brew shellenv 而不是手写环境变量
再说一个实际维护心得:不管你在哪篇教程里看到让人手动export PATH="/opt/homebrew/bin:$PATH"的写法,你都可以用eval "$(/opt/homebrew/bin/brew shellenv)"代替。两者在 Apple Silicon 默认路径下效果几乎一样,但前者少设了MANPATH和INFOPATH,后者更接近官方维护的姿态。
自定义路径也一样:
eval "$($HOME/Develop/Tools/Homebrew/bin/brew shellenv)"这句对标准路径、自定义路径都通用,是我个人最推荐的环境变量配法。如果你在配置过程中发现brew --prefix输出的目录和预期不一致,优先检查brew可执行文件的位置,而不是去折腾环境变量。因为 Homebrew 的一切路径逻辑,都是从那个bin/brew倒推出来的。记住这一点,后面遇到再奇怪的路径问题,你都有了一个稳定的排查锚点。