安装好工具却提示 command not found,环境变量写在配置文件里却不起作用,同一个配置终端能用 AI 工具却不行,这些不是 bug,是 macOS zsh 的四象限分类机制在起作用。

本文系统梳理.zshenv 与 .zshrc 等五大配置文件的加载逻辑,对比zsh 和 bash,介绍 source、rehash、launchctl setenv 等实操技巧,最后给出面向 AI 工具时代的配置习惯建议。

朋友们有没有碰到过以下这些类似的错误案例,被折腾得欲仙欲死。
案例1:command not found
我明明刚刚在Mac终端里安装好homebrew,想看看它的版本“brew -v”,结果却报错”command not found”。

然后DeepSeek一下,我执行下面几个命令后,再执行“brew - v”能正常显示版本号了,再用brew install 命令去安装其他工具也就能正常运行了,但是这几个命令起啥作用就不知道了:
echo>> /Users/xxx/.zprofileecho 'eval "$(/opt/homebrew/bin/brew shellenv zsh)"' >> /Users/xxx/.zprofileeval "$(/opt/homebrew/bin/brew shellenv zsh)"案例2:WorkBuddy读取不到环境变量
还有比如在WorkBuddy中安装Lovart对应Skill后,按照Skill的提示,去Mac终端里执行了export命令,设置了这两个环境变量,但是WorkBuddy后续任务执行时,就是没有读取到这个配置:

在WorkBuddy中验证下,发现读取不到这个环境变量,然后它推荐我要把export这个命令写到~/.zshrc文件里去:

我照着干了,也重启了WorkBuddy,但还是读取不到,最后workbuddy自己想了个办法,它每次执行lovart命令时,都在命令中显式指定export。
案例3:Codex读取环境变量
还有当你使用Codex Desktop时,想不用OpenAI,切换成第三方的LLM,需要把第三方LLM的 API Key 设置到环境变量里,你参考上面的做法,写到了.zshrc配置文件里,Codex Desktop 就可以了。

你开始怀疑工具有 bug,但其实这是 zsh 的设计,不是 bug。
搞懂了这个,你才真正理解 AI 工具是怎么执行脚本跑命令的。
先说结论:两把刀切出来四个象限
macOS 的 zsh 用两个维度划分 Shell 运行状态:
维度一:交互式 vs 非交互式 - 交互式:有提示符 %,等你输命令,就是你打开的那个终端窗口 - 非交互式:执行完就退出,不等人(脚本、自动化工具)
维度二:登录 vs 非登录 - 登录 shell:需要验证身份才能进入,比如 SSH 远程连接、开机登录 - 非登录 shell:在已登录会话里启动的子 shell
两个维度交叉,切出四个象限:
| 交互式 | zsh,启动子 shell | |
| 非交互式 | ssh 目标host xxxscript.sh | zsh -c "命令" |
比如你打开一个终端窗口:交互式 × 登录 shell ,加载最完整的配置链
WorkBuddy :非交互式 × 非登录 shell ,只加载 .zshenv
Codex Desktop :交互式 × 非登录 shell ,可加载 .zshrc
.zshenv 是什么?为什么关键?
zsh 有 5 个配置文件,按加载顺序是:
.zshenv → .zprofile → .zshrc → .zlogin → .zlogout但不是每次都全加载:
.zshenv | 所有 shell 都加载 | |
.zprofile | ||
.zshrc | ||
.zlogin | ||
.zlogout |
.zshenv 是唯一一个所有场景都会加载的文件。
WorkBuddy 启动的是非交互式 + 非登录 shell,整条加载链只剩 .zshenv。
这就是为什么同一个命令(比如npm install xxx),终端里能执行,WorkBuddy却提示command not found。
所以本文最前面,按照DeepSeek给的命令执行后,把 Homebrew PATH 写在 .zprofile 里了,终端里用brew install 安装node26系列、python3.14系列都很正常,安装后也能正常运行。
但在WorkBuddy 里执行很多skill过程中,要执行npm 安装组件或执行python3 脚本时,总提示对应命令找不到,最后它自动降级为用workbuddy内置的node和python版本。
为什么要这么设计?不是故意坑你
这套分级机制背后有清晰的逻辑:
env(.zshenv):底层基础设施 任何 zsh 实例都不能没有 PATH,否则什么命令都找不到。所以它放最前、全加载,不分场景。
profile(.zprofile):一次性初始化 登录时做一次就够了。如果每次开新终端都重新跑一遍,浪费资源还可能出错。
rc(.zshrc):人机交互专属 别名 alias ll='ls -la'、彩色提示符、Tab 补全,这些是给人用的,脚本用不到。加载了反而拖慢启动速度,还可能污染脚本的标准输出。
说白了:给人用的配置和给机器用的配置,本来就该分开。
WorkBuddy 和 Codex Desktop 本质上就是”机器”,它们启动 zsh 只想要一个干净、快速的执行环境,不需要你的彩色提示符。
对比 bash,你就明白 zsh 为什么更现代
bash 只有 3 个配置文件:
~/.bash_profile | |
~/.bashrc | |
~/.bash_logout |
bash 没有全场景兜底的文件。
非交互式 × 非登录 shell(最”裸”的场景),bash 默认什么都不加载。如果你想覆盖这个场景,只能用一个叫 $BASH_ENV 的环境变量来指向配置文件,但这个变量本身还需要先被加载才能生效,有点鸡生蛋蛋生鸡的问题。
.zshenv | ||
$BASH_ENV | ||
.zshenv |
macOS 从 Catalina (10.15) 开始默认切换到 zsh,这是原因之一:现代系统里各种自动化工具、IDE 任务、AI 工具越来越多,需要一个设计更清晰的 shell 配置体系。
实操:把 API Key 放对地方
如果你要给 Codex Desktop、WorkBuddy 这类工具配置环境变量,正确姿势是:
写入 .zshenv,不要写到 .zprofile 或 .zshrc
echo 'export BAILIAN_API_KEY="你的API密钥"' >> ~/.zshenv验证是否生效(模拟非交互式 zsh 环境):
zsh -c 'echo $BAILIAN_API_KEY'zsh -c 就是开一个新的非交互式 zsh,跑完就退出,这正好模拟了 WorkBuddy 的执行环境。如果这个命令能打印出你的 Key,就说明配置成功了,WorkBuddy 和 Codex Desktop 都能读到。
如果直接在终端里 echo $BAILIAN_API_KEY 能读到,但 zsh -c 读不到,说明你的变量写错地方了。
延伸一:改完配置,当前终端窗口怎么刷新?
当你把环境变量写进了 .zshenv 或安装了新工具,但当前这个终端窗口不会自动重读,它只在启动时加载一次配置。如果你因为各种原因,不想重启当前终端窗口,那如何才能刷新生效呢?
方式一:source ~/.zshenv(重读配置文件)
source ~/.zshenv原理:把文件里的每一行命令,在当前这个正在运行的 shell 进程里逐条执行一遍。不开新进程,不替换当前进程,变量直接生效在当前窗口里。
适用场景:刚往配置文件里加了新的环境变量或别名,想立即在当前终端生效。
它和前面提到的 zsh xxx.sh 的做法意义完全相反:zsh是“子进程独立执行”,source是“自己亲自下场执行”。
“从亲自下场执行”这个角度看,它和本文开头的提到的 eval 命令有点类似:
eval "$(/opt/homebrew/bin/brew shellenv zsh)"source 是读取脚本文件并在当前窗口执行,而 eval 则是读取字符串并在当前窗口执行。
方式二:rehash(刷新命令路径缓存)
rehash原理:zsh 为了加速,会在内存里缓存一张”命令 → 路径”的哈希表(比如 brew 在哪)。你用 brew install 或 npm install -g 装了新工具,这张表不会自动更新,所以 zsh 找不到新命令。rehash 清掉旧缓存,重建索引。
适用场景:安装了新的命令行工具(pip install、npm install -g、brew install),当前终端提示 command not found。
或者安装了新版本以后,命令还是指向老版本的路径,当前终端窗口不用重启,rehash刷新后,就指向新的版本了:

source vs rehash,傻傻分不清?
记住这一句就够了:
改完配置用
source,装完软件用rehash。
source | rehash | |
|---|---|---|
$API_KEY 为空) | command not found) | |
延伸二:图形化应用的环境变量光靠 .zshenv 不够
.zshenv 能解决 WorkBuddy、Codex Desktop 这类内部会启动 zsh 子进程的 AI 工具问题,但 Mac 上还有另一类应用,它们是纯图形化进程,根本不走 zsh,比如 Chrome、Finder、或者某些不依赖 shell 的桌面 App。
这类应用怎么读环境变量?它们依赖的是 macOS 的系统级启动服务 launchd。
Mac 进程的家谱
launchd (PID 1,万进程之祖)├── 图形界面会话 (WindowServer)│ ├── Finder、Chrome、WorkBuddy、Codex Desktop 等 GUI 应用│ └── Terminal.app(这也是 GUI 应用)│ └── zsh(终端里的 shell,Terminal 的子进程)│ └── 再加载 .zshenv、.zshrc 等└── 系统级守护进程关键点: - GUI 应用是 launchd 的直接子进程,它们不经过 Terminal,也不读 .zshenv - 终端里的 zsh 是 Terminal.app 的子进程,是 launchd 的”孙子”,通过继承链拿到 launchd 的变量,再叠加 .zshenv 的配置
launchctl setenv — 直接写进 launchd
launchctl setenv HTTP_PROXY http://127.0.0.1:7897launchctl setenv HTTPS_PROXY http://127.0.0.1:7897这条命令把变量写入 launchd 的环境变量表,之后所有由 launchd 启动的进程(包括 GUI 应用)都能继承到,重启后依然有效。
两种方式对比
launchctl setenv | .zshenvexport | |
|---|---|---|
.zshenv 覆盖) | ||
那为什么 Codex Desktop 直接写 .zshrc 也管用?
Codex Desktop 虽然是 GUI 应用,但它内部会启动一个 交互式 zsh(相当于 zsh -i,带 PTY 伪终端)来执行命令。所以:
.zshenv:所有 zsh 都加载 → 一定生效 .zshrc:只有交互式 zsh 加载 → Codex Desktop( zsh -i)生效,WorkBuddy(zsh -c)不生效launchctl setenv:GUI 应用层面生效,进入 zsh 子进程后可能被 .zshenv覆盖
.zshenv | |||
.zshrc | |||
launchctl setenv | |||
最完整的覆盖方案:对于需要全局生效的变量(比如代理、SDK 路径),两边都写:
# 写入 launchd,覆盖所有 GUI 应用launchctl setenv HTTP_PROXY http://127.0.0.1:7897# 写入 .zshenv,覆盖所有 zsh(含 WorkBuddy)echo'export HTTP_PROXY=http://127.0.0.1:7897'>> ~/.zshenv结语
说实话,好多年没碰 Shell 命令和脚本了。最近因为要频繁使用各种 AI 工具,新换了电脑,又需要重新安装 Python、Node 等运行环境,需要加载各种 Skill,需要设置各种环境变量,时常出错,有时候真的很让人崩溃。
正是在这个过程里,踩过一个个”明明装了却找不到”的坑之后,才决定把这套机制系统性梳理出来。
希望大家看完这篇文章,能对Mac里这个Shell体系有个大致了解,这样当以后遇到问题时,去咨询大模型,能大致看懂它给的解决方法是什么意思,看到博主们文章里给的命令,也能明白其作用,而不是无脑执行,发现不起作用,再折腾半天。
把环境变量写在 .zshenv 里,是面向 AI 工具时代最对的配置习惯。
一个终端命令在 AI 工具里跑不通的问题,背后是 zsh 精心设计的四象限分级机制。但你的配置放对了地方,很多问题其实自己就消失了。
不是 WorkBuddy 的 bug,不是 Codex 的限制,是你之前配置文件没放对地方,仅此而已。
下次碰到 AI 工具说”command not found”,先想想:这个命令在哪个配置文件里?

大神推荐:本文排版用到了花叔(@huashu)开源的 md/html 技能(https://github.com/alchaincyf/huashu-md-html)。花叔是真的把 AI 用进日常工作流里的博主。GitHub 高星项目一堆,huashu-design 审美在线、nuva 蒸馏框架能打,踩过的坑都是实战经验浓缩出来的。推荐大家关注👇
—End—
如果觉得不错 随手点个赞
、在看
、转发三连吧
关注+星标⭐ 可第一时间收到更多精彩思考和总结
您的支持是我继续写下去的动力
注:原创不易,合作请在公众号后台留言,未经许可,不得随意修改及盗用原文。
夜雨聆风