Homebrew 把 OpenVPN 二进制装在 sbin,不在你的 PATH 里
该 formula 把 OpenVPN 守护进程装在 sbin 下,而 macOS 默认 PATH 并不包含这个目录。先确认前缀,再在一个文件里修好解析。
安装结束了,brew 说 openvpn 就在那里,shell 却回答 command not found。二进制确实存在,只是待在一个 shell 从未被告知过的目录里。Homebrew 会把守护进程类的二进制链接到前缀的 sbin 目录,而 macOS 默认的 PATH 通常包含 bin 目录,不包含 sbin。没有任何东西损坏,重装也不会改变这一点。
在动手修改任何 shell 文件之前,四项检查可以确认这一机制。
formula 实际安装了什么
openvpn 的 formula 构建的是命令行守护进程,所以它的可执行文件落在 sbin 而不是 bin 下。随后 Homebrew 把这个二进制链接到前缀的 sbin 目录,这就是为什么 "$(brew --prefix)/sbin/openvpn" --version 能工作,而裸调用不行。带版本号的 Cellar 目录里同样有一份副本,把那个路径写进启动文件,正是机器在升级后仍被钉在旧版本构建上的原因。
前缀的符号链接才是稳定的路径。Cellar 路径是实现细节,里面带着版本号。
去问 Homebrew,而不是文件系统
从包管理器开始,因为只有它知道这份 formula 属于哪个安装:command -v brew、brew --prefix、brew list --versions openvpn、brew --prefix openvpn。如果最后一条打印出路径,说明 formula 在,问题纯粹出在解析上。如果什么都不打印,那就是 shell 在错误的位置找一个从未安装过的包,这是另一个问题,需要另一种修复。
一台机器上有两个 Homebrew 安装,正是这种情况最要紧的场合:应答的那个 brew 未必是安装的那个。
为什么 shell 看不到 sbin
登录 shell 的 PATH 由系统默认值加上启动文件追加的内容构成。Homebrew 的 bin 通常会被加上,sbin 通常不会,于是除了守护进程之外每个命令都能用。command -v openvpn 什么都不返回,而带前缀的路径返回了版本号,这两条命令就是全部诊断。用 ls -l "$(brew --prefix)/sbin/openvpn" 确认目录里确实有那个链接。
链接缺失和 PATH 条目缺失是两种不同的故障,只有一个能靠修改 shell 文件修好。
路径只修一次,只修在一个地方
要在当前 shell 里快速测试,eval "$(brew shellenv)" 会设置安装变量并把两个目录都放到前面。要长期有效,就把 shellenv 那一行写进你所用的登录模式下 shell 唯一读取的启动文件,而不是把 export 散落到 bash 和 zsh 文件里。
然后开一个新终端重跑版本查询,因为被缓存的 shell 还在报告旧的 PATH。
把发现和服务分开
找到一个二进制并不等于跑起一个 VPN 服务。自动启动 openvpn 会带来配置文件、凭据、权限和生命周期方面的要求,而包发现从未涉及这些。Tunnelblick 还自带一套引擎,所以你刚打印的版本可能和应用实际使用的版本毫无关系。
记录是哪个可执行文件产出了版本输出。下一次两个版本对不上时,这一行能省下一小时。