Homebrew formula 与 cask 区别:安装、路径、升级与卸载
2026/9/18 2:57:40 网站建设 项目流程

新 Mac 到手第一天,在终端里敲下brew install chrome,等来的是一行Error: No available formula with the name "chrome"。同一个下午,你从某篇几年前的老教程里抄来brew cask install google-chrome,结果更干脆:Error: Unknown command: cask。两条报错看着毫不相干,其实指向同一个知识点——Homebrew 里有一条分类线:命令行工具走 formula,图形应用走 cask。这条线决定了你该敲哪条命令、文件最终落到哪个目录、升级该由谁负责、卸载之后还会剩下什么。

我前后在 Intel 机器和 Apple Silicon 机器上都折腾过 Homebrew,从"照着教程抄命令"到"能自己判断该用哪种安装方式",中间踩的坑基本都跟这条分类线有关。这篇内容适合三类人看:刚接触 macOS 命令行的新手、从旧教程里抄命令结果报错的人、以及想搞清楚 Homebrew 目录结构好在出问题时自己排查的人。不需要你有编程基础,但你需要愿意打开终端,跟着看几个目录和几条命令的实际输出。

1. 从一个报错说起:为什么 brew install chrome 找不到东西

1.1 "No available formula" 这句话的字面意思

这句报错的翻译是:在 formula 的命名空间里,没有叫 chrome 的包。注意它说的是 formula,不是"没有这个软件"。Homebrew 内部维护着两套互相独立的索引:一套叫 formula,一套叫 cask。当你直接写brew install 名字时,默认只在 formula 那套索引里找,找不到就报这个错。Google Chrome 这种图形应用,注册在 cask 索引里,所以 formula 里当然没有。

反过来也一样。你敲brew install --cask wget,大概率会得到一个"没找到对应 cask"的提示,因为 wget 是纯命令行工具,它属于 formula。两边索引各自收录自己该收的东西,互不越界。理解这一点之后,报错信息就不再是天书了,它其实在告诉你:"你找错索引了。"

判断某个名字到底在哪一边,最稳的办法是显式限定搜索范围:

brew search --formula chrome # 在 formula 索引里找 brew search --cask chrome # 在 cask 索引里找 brew info --cask google-chrome # 看某个 cask 的详细信息 brew info --formula wget # 看某个 formula 的详细信息

我在实际使用中发现,养成加--formula--cask的习惯,能让搜索结果的噪音少一大半。不带限定时brew search会把两边结果混着列出来,名字相近的条目挤在一起,新手很容易点错。

1.2 两套索引背后是两类完全不同的包定义

formula 是一段用 Ruby 写的构建配方,里面描述了源码地址、依赖关系、编译参数、以及编译完成后该往哪里放文件。cask 则是另一套定义文件,它描述的不是"怎么编译",而是"从哪下载一个现成的产物、校验值是多少、下载完把里面的什么文件放到哪里去"。一个是从源码(或预编译包)构建出程序,另一个是搬运别人已经打包好的成品。

这个区别带来的直接后果是:formula 能管的事情更多、更细,比如它可以给程序打补丁、指定编译选项;而 cask 能做的事情本质上是下载加摆放,它不参与任何构建过程。所以你去看 cask 的定义文件,会发现字段基本就是 url、sha256、app 名称、卸载时要清理的路径这些,没有编译参数之类的概念。

另外从 Homebrew 4.0 开始,formula 和 cask 的信息默认通过 JSON API 获取,本地不再默认把 homebrew/core 和 homebrew/cask 这两个大仓库完整克隆下来。很多老教程会教你cd $(brew --repo)/Library/Taps/homebrew/homebrew-core去看定义文件,你在新版本上执行会发现目录根本不存在,然后就慌了。这不是你装坏了,是取数据的方式变了,想看某个包的定义,用brew info --json=v2 包名或者直接去它的官方仓库页面上看更省事。

2. 装完之后文件到底去哪了:Cellar 与 /Applications 的分岔

2.1 formula 的落点:Cellar、opt 和 bin 的三层软链

理解 formula 的目录结构,能解释很多"为什么升级没出问题""为什么删了还能找回来"的现象。以$(brew --prefix)作为前缀(Intel 机器通常是/usr/local,Apple Silicon 是/opt/homebrew),一个 formula 装完之后会在三个地方留下痕迹:

  • $(brew --prefix)/Cellar/包名/版本号/是真正的安装目录,同一个包的多个版本会以并列的子目录形式共存。
  • $(brew --prefix)/opt/包名是一个软链接,指向 Cellar 里当前生效的那个版本目录。
  • $(brew --prefix)/bin/可执行文件也是软链接,指向 opt 里的实际文件,而这个 bin 目录本身已经在 PATH 里。

这个三层结构的设计意图很明确:升级时新版本解压在 Cellar 里一个新的目录,只需要把 opt 那一层软链改指向新目录,全局命令立刻切到新版本,旧版本还完整地躺在原地。万一新版有问题,改回软链就能退回去。这也是为什么brew cleanup存在——它的主要工作之一就是删掉 Cellar 里不再被 opt 指向的旧版本目录,把磁盘空间收回来。我在一台老机器上跑过一次brew cleanup,一口气收回了十几个 G,基本全是历史版本的残留。

2.2 cask 的落点:Caskroom 加应用程序目录

cask 的安装位置完全是另一套逻辑。它先把下载来的 dmg、zip、pkg 解到$(brew --prefix)/Caskroom/包名/版本号/下面,然后根据 cask 定义里写的"产物类型"做对应的摆放动作。常见的有三类:

  • app 型:压缩包里就是一个.app,通常会被放进/Applications,如果当前用户对/Applications没有写权限,Homebrew 会退而求其次放到~/Applications
  • pkg 型:产物是.pkg安装器,会调用 macOS 自己的安装流程,可能同时写入系统组件、后台服务、驱动,这类安装过程中弹出管理员密码框是正常的。
  • binary 型:少数只在发布页提供单个可执行文件的工具,会被软链到$(brew --prefix)/bin下,这种情况下 cask 和 formula 在使用体验上几乎没有差别。

也就是说,cask 的"安装"本质上是把上游已经做好的东西放到系统约定的位置,它不负责编译,也不保证你的 App 是一个自包含的目录。有些 pkg 型 cask 装完之后,你在/Applications里删掉图标,后台进程和驱动可能还留在系统里,这就是后面要讲的--zap存在的原因。

2.3 一张对照表把差异钉死

维度formulacask
典型对象命令行工具、库、语言运行时图形应用、桌面客户端、驱动类程序
产物来源官方预编译包或源码构建上游发布的 dmg / zip / pkg
主要落点Cellar + opt + bin 软链Caskroom + /Applications 或 ~/Applications
版本共存多版本并列存放,软链切换一般只保留一个版本
升级方式brew upgrade 包名brew upgrade --cask 包名
卸载残留配置文件通常在用户主目录偏好设置、缓存、后台服务可能残留
是否需要密码通常不需要pkg 型安装器会要求管理员授权

这张表我在自己的笔记里贴了好几年,每次遇到"这个包该用哪种方式装"的犹豫,扫一眼就能得出结论。判断标准其实就一句话:你装完之后主要是在终端里敲它,还是在访达里双击它。

3. 命令写法已经变了:brew cask install 为什么敲不出来

3.1 一段值得知道的历史

cask 最初是一个独立的第三方项目,靠外部命令的形式挂进 Homebrew,所以那时候必须写成brew cask install 应用名。后来它被正式并入 Homebrew 主干,成为官方的一部分。随着整合深入,维护者发现"两套几乎同构的子命令"维护成本很高,于是从 Homebrew 2.6 开始把brew cask系列命令标记为弃用,统一改成在主命令上加--cask标志的写法;到 2.7 之后,brew cask install这种写法直接被移除,执行只会得到Unknown command

所以你在网上看到brew cask install时,不需要怀疑它的正确性——它在当年是对的,只是现在过期了。判断一篇 Homebrew 教程是否还值得参考,看它有没有用--cask这种写法就是一个很实用的信号。

3.2 常用命令的新旧对照

用途旧写法现在的写法
安装 GUI 应用brew cask install xxxbrew install --cask xxx
升级 GUI 应用brew cask upgradebrew upgrade --cask
列出已装 GUI 应用brew cask listbrew list --cask
查看可升级的 GUI 应用brew cask outdatedbrew outdated --cask
卸载 GUI 应用brew cask uninstall xxxbrew uninstall --cask xxx
查看应用信息brew cask info xxxbrew info --cask xxx
重装brew cask reinstall xxxbrew reinstall --cask xxx

这里有一个特别容易踩的坑:brew upgrade不带参数时,默认只升 formula,不会动 cask。很多人以为"我每天都跑 brew upgrade,怎么 Chrome 还是老版本",原因就在这里。要一起升级得显式写brew upgrade --cask,或者分开两条命令跑。我自己的习惯是先brew outdatedbrew outdated --cask各看一遍,确认要升什么,再分别执行,避免一次升级太多导致出问题后不好定位。

3.3 卸载这件事,两种包的处理深度完全不同

formula 的卸载比较干脆:删掉 Cellar 里的目录、删掉 opt 和 bin 里的软链,剩下的基本只有你主目录下的配置文件,比如~/.config/或者用户主目录下某个点开头的目录。要不要一起删,你自己判断。

cask 的卸载则分两个层次。brew uninstall --cask 应用名只做基础清理:删掉应用程序目录里的 App、删掉 Caskroom 里的记录。而加上--zap之后,它会按照 cask 定义里手写的一份路径清单,去删偏好设置、缓存、日志、容器目录等残留。问题是这份清单是 cask 的维护者人工写的,可能漏掉新版本新增的路径,也可能删得比你预期更狠。

我的做法是:卸载前先brew info --cask 应用名,看输出里有没有 zap 相关的条目、具体列了哪些路径,扫一眼确认里面没有你还需要的东西,再决定加不加--zap。对于存储配置或者登录状态的 App,我一般不加 zap,宁可手动去~/Library下面翻一遍。

4. 安装路径与权限:/opt/homebrew 和 /usr/local 的分岔口

4.1 两台机器上的 brew 前缀不一样

Intel 芯片的 Mac 上,Homebrew 默认装在/usr/local;Apple Silicon 机器上则是/opt/homebrew。这不是随意改的:/usr/local在 Apple Silicon 上的归属和权限模型跟新系统的预期不完全一致,另外还要考虑旧机器上通过兼容层运行的历史安装会互相干扰,所以新前缀是更干净的选择。

这个差别带来一个实际问题:如果你从 Intel 机器迁移到新机器,或者两台机器之间同步配置,PATH 里写死的/usr/local/bin在新机器上就找不到东西了。正确做法是在 shell 配置里用命令动态取路径,而不是写死:

# ~/.zshrc eval "$(/opt/homebrew/bin/brew shellenv)"

Intel 机器上把上面的路径换成/usr/local/bin/brew即可。用brew shellenv的好处是它会一次性把 PATH、MANPATH、INFOPATH 都设好,后续 Homebrew 改结构你也不用跟着改配置。

如果你不确定自己机器上是不是装了两份 Homebrew,跑一下which -a brew。输出两行是常见情况——旧机器迁移过来的残留加上新装的,两份同时存在会导致"我明明装了某个包,命令却找不到"这种诡异问题。处理办法是用brew --prefix确认当前生效的是哪一份,把 PATH 顺序理清楚,再用brew doctor检查有没有冲突项。

4.2 什么时候会弹密码框,什么时候不该加 sudo

这是新手最容易搞混的地方。formula 安装在$(brew --prefix)下面,那个目录属于当前用户,所以正常安装全程不需要密码。而 cask 有两种情况需要管理员授权:一种是往/Applications写文件时权限不够,另一种是 pkg 型 cask 调起系统安装器,安装器本身要求授权。

关键区别是:那个密码框是 macOS 安装器弹的,不是你给 brew 加 sudo 换来的。sudo brew install这种写法非常危险,它会让 Cellar 里的文件归属变成 root,之后所有正常安装、清理、升级都会撞上权限拒绝,而且报错信息通常很含糊,让人一头雾水。正确的处理方式是先用brew doctor看它怎么说,实在需要修归属时,用类似下面这样的命令把前缀目录的归属改回当前用户:

# 先看清楚当前前缀在哪 brew --prefix # 确认归属被改坏了之后再执行,把路径换成上面输出的结果 sudo chown -R "$(whoami)" /opt/homebrew

这属于最后手段,执行前最好确认一下是哪些文件归属不对,别一上来就整目录改。我踩过一次这个坑:早期照着某篇教程用 sudo 装了一个工具,之后每次brew cleanup都报一堆权限错误,花了半小时才定位到是归属问题。

4.3 装到 ~/Applications 的取舍

当 Homebrew 判断当前用户对/Applications没有写权限时,会把 App 放到~/Applications。这个目录对当前用户完全可控,不需要任何授权,卸载也干净。代价是只有当前用户能用这些应用,其他账户登录后看不到;另外少数脚本或工具会硬编码去/Applications里找应用,这种情况下就会找不到。

如果你是多用户共用一台机器,我更倾向于让应用装到全局的/Applications,安装时输入管理员密码即可。如果是个人机器并且在意权限干净,~/Applications其实挺省心。

5. 下载失败或很慢的时候,先分清是哪一路流量

5.1 formula 和 cask 的下载来源不是一回事

这一点被误解得最厉害。formula 的下载流量主要有几路:命令本身的 git 仓库、包信息的 API 接口、以及预编译包(bottle)的存放站点。这几路都可以通过环境变量指向国内高校或云厂商维护的镜像站点,配置写在~/.zshrc里:

# 示例配置,请以镜像站点当期公布的地址为准 export HOMEBREW_API_DOMAIN="https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/api" export HOMEBREW_BOTTLE_DOMAIN="https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles" export HOMEBREW_BREW_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git" export HOMEBREW_CORE_GIT_REMOTE="https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git"

写完记得source ~/.zshrc,然后brew update一次让配置生效。用brew config可以看到当前生效的各项地址,用来验证改没改成功。

但 cask 完全是另一条路。cask 定义里写的下载地址是上游厂商自己的服务器,比如某个浏览器、某个编辑器官方发布的下载链接,镜像站点一般不会覆盖这部分内容。这就是为什么很多人配了镜像之后发现:装命令行工具飞快,装 GUI 应用还是那个样子。这不是配置失效,而是这套加速手段对 cask 的下载环节无效。

5.2 三条最常见报错的排查链路

我在自己的记录里把 cask 相关的报错归了三类,遇到时按顺序走一遍基本能定位:

第一类:提示应用已存在。报错大意是目标位置上已经有同名的 App,Homebrew 不会去覆盖一个不是它装的程序。这在从官网下载过安装包、后来又想让 Homebrew 接管的情况下特别常见。处理方式是先手动把旧 App 拖到废纸篓,再执行安装;确认要覆盖时可以用--force,但我不建议一上来就加,先看清楚要覆盖的是什么。

第二类:校验值不匹配。表现为下载完成后提示 sha256 不符。常见原因是缓存的半成品文件损坏,或者上游悄悄换了安装包而 cask 定义还没跟上。处理顺序是:先brew update拿到最新的定义,再brew reinstall --cask 包名;如果反复失败,去 cask 的官方仓库看有没有对应的 issue 记录,通常已经有人在处理了。

第三类:下载中断或超时。这类报错里 curl 的错误码会直接打出来。可以先删掉缓存重试,缓存位置在~/Library/Caches/Homebrew/downloads。如果同一个大文件反复下到一半断掉,基本可以判断是这条链路在当前网络下不稳,换个时间段或者换个网络环境再试,比反复重试更有效。

5.3 一张从表象到根因的排查顺序表

现象先确认什么常见根因
Unknown command: cask是不是抄了旧教程旧子命令已移除,改用--cask
No available formula名字属于哪套索引装错了索引,该用--cask
命令装好了但敲不出来echo $PATHbrew --prefixPATH 没配或配的是另一个前缀
装完提示缺少命令行工具xcode-select -p命令行开发者工具没装好
应用装到了用户目录当前用户对/Applications的写权限权限不足触发了降级安装
反复校验失败brew update是否成功上游换了包,本地定义过期

这张表的价值不在答案,而在顺序。我见过太多人一遇到报错就去重装 Homebrew,其实大部分问题在前两行就能解决。

6. 几个只有自己装过几十次才会注意到的细节

6.1 formula 和 cask 的边界比想象中模糊

有些工具两边都有,比如命令行客户端是一个 formula,配套的桌面版是另一个 cask,名字还很像,差一个后缀。这种时候不要靠猜,brew info --cask 包名输出的 artifacts 部分会明确告诉你它装的是什么:是往应用程序目录放一个 App,还是往 bin 里放一个可执行文件。看清这一行,你就知道它装完之后该怎么用。

反过来,有些纯命令行工具只在发布页提供单个二进制文件、没有做 formula,于是被打包成了 cask。第一次遇到这种"用 --cask 装的却是命令行工具"的情况会觉得别扭,但只要看一眼 artifacts 就理解了——分类依据是分发方式,不是程序形态。

6.2 --greedy 与版本漂移

不少 GUI 应用自带更新机制,它们会在后台把自己升到新版本,而 Homebrew 记录的还是安装时的版本号。这种"版本漂移"会导致brew outdated --cask看不到需要升级的项,因为你本地记录的版本和远端最新版一致,但磁盘上跑的其实已经是更新的版本了。加上--greedy标志可以让 Homebrew 把这些自带更新器的应用也纳入升级判断:

brew outdated --cask --greedy # 看看有哪些被漏掉了 brew upgrade --cask --greedy # 连带升级

我的建议是先用--greedy看一眼,确认列表里的东西你确实想升再执行。因为有些应用的新版本会改配置格式,自动升级之后旧配置不兼容,反而添麻烦。

6.3 清理缓存时要分清对象

brew cleanup默认清的是 formula 的旧版本目录和一部分下载缓存,cask 的下载缓存放在用户缓存目录下,清理策略不太一样。加--prune=all会更激进。我在磁盘紧张的时候会先brew cleanup -n看一眼它准备删什么,确认没有还需要回退的旧版本再真正执行。这一步多花十秒钟,能避免事后想回退版本却发现旧目录已经被删了的尴尬。

7. 顺便回答一个高频疑问:装 Homebrew 本身要多久

经常有人问"在 Mac 上装 Homebrew 到底要等多久",这个问题的答案往往不在 Homebrew 身上,而在它前面那一步。

安装脚本执行时会先检查命令行开发者工具,如果没装,会触发系统的安装流程。这一步的耗时完全取决于系统和网络,可能几分钟,也可能十几分钟,而且它是在另一个窗口里进行的,终端上看不到进度,很多人以为卡死了就把终端关掉,结果装了一半。判断办法是另开一个终端窗口,执行xcode-select -p,能看到路径就说明工具链就绪了。

工具链就绪之后,脚本要做的事情是拉取 Homebrew 自身的仓库、初始化目录结构、跑首次更新。这一步才是可以用镜像提速的部分:把前面提到的几个 git 远程地址换成镜像站点的地址,再执行安装脚本,拉取环节通常能从十几分钟压到一两分钟。至于首次brew update拉取包信息的时间,取决于当前版本是走 API 还是完整克隆仓库,前者快得多,这也是新版本默认走 API 的原因之一。

装完之后我习惯跑三条命令做确认:brew --version看版本、brew --prefix看安装位置、brew config看当前生效的各项配置和工具链状态。brew config的输出信息量最大,前缀、系统版本、命令行工具版本、有没有走 API,都在里面。留着这份输出,之后遇到问题想找人帮忙时直接贴出来,对方能省掉一大堆追问。

最后说一个我自己的判断习惯:每次要在新机器上装东西之前,先花十秒钟问自己一句"这个东西装完我主要是敲它还是点它"。敲它就用brew install,点它就用brew install --cask,需要从应用商店装的才轮到mas这类工具。想清楚这一句,绝大多数关于brew installbrew cask install的困惑其实就不会发生了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询