ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Homebrew formula 与 cask 区别:安装、路径、升级与卸载

Homebrew formula 与 cask 区别:安装、路径、升级与卸载 新 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 --jsonv2 包名或者直接去它的官方仓库页面上看更省事。2. 装完之后文件到底去哪了Cellar 与 /Applications 的分岔2.1 formula 的落点Cellar、opt 和 bin 的三层软链理解 formula 的目录结构能解释很多为什么升级没出问题为什么删了还能找回来的现象。以$(brew --prefix)作为前缀Intel 机器通常是/usr/localApple 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 outdated和brew 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/localApple 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_DOMAINhttps://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/api export HOMEBREW_BOTTLE_DOMAINhttps://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles export HOMEBREW_BREW_GIT_REMOTEhttps://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git export HOMEBREW_CORE_GIT_REMOTEhttps://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git写完记得source ~/.zshrc然后brew update一次让配置生效。用brew config可以看到当前生效的各项地址用来验证改没改成功。但 cask 完全是另一条路。cask 定义里写的下载地址是上游厂商自己的服务器比如某个浏览器、某个编辑器官方发布的下载链接镜像站点一般不会覆盖这部分内容。这就是为什么很多人配了镜像之后发现装命令行工具飞快装 GUI 应用还是那个样子。这不是配置失效而是这套加速手段对 cask 的下载环节无效。5.2 三条最常见报错的排查链路我在自己的记录里把 cask 相关的报错归了三类遇到时按顺序走一遍基本能定位第一类提示应用已存在。报错大意是目标位置上已经有同名的 AppHomebrew 不会去覆盖一个不是它装的程序。这在从官网下载过安装包、后来又想让 Homebrew 接管的情况下特别常见。处理方式是先手动把旧 App 拖到废纸篓再执行安装确认要覆盖时可以用--force但我不建议一上来就加先看清楚要覆盖的是什么。第二类校验值不匹配。表现为下载完成后提示 sha256 不符。常见原因是缓存的半成品文件损坏或者上游悄悄换了安装包而 cask 定义还没跟上。处理顺序是先brew update拿到最新的定义再brew reinstall --cask 包名如果反复失败去 cask 的官方仓库看有没有对应的 issue 记录通常已经有人在处理了。第三类下载中断或超时。这类报错里 curl 的错误码会直接打出来。可以先删掉缓存重试缓存位置在~/Library/Caches/Homebrew/downloads。如果同一个大文件反复下到一半断掉基本可以判断是这条链路在当前网络下不稳换个时间段或者换个网络环境再试比反复重试更有效。5.3 一张从表象到根因的排查顺序表现象先确认什么常见根因Unknown command: cask是不是抄了旧教程旧子命令已移除改用--caskNo available formula名字属于哪套索引装错了索引该用--cask命令装好了但敲不出来echo $PATH和brew --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 的下载缓存放在用户缓存目录下清理策略不太一样。加--pruneall会更激进。我在磁盘紧张的时候会先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 install和brew cask install的困惑其实就不会发生了。
返回列表