
envocabulary v1.0.5
追踪每个 Shell 环境变量到其确切的文件和行号来源。审计 macOS、Linux 和 FreeBSD 上 zsh 和 bash 的 Shell 配置,查找失效条目、重复项和孤立文件。
envocabulary
对于当前 shell 中的每个变量,找出设置它的文件和行号,或者是由哪个子系统(direnv、launchd、terminal、SSH、system)注入的。此外还提供了一些静态文件命令,用于应对你的 shell 配置散落在 N 个文件和备份中、你已经理不清头绪的情况。
我构建这个工具是因为每隔几个月我都要花上同样的一小时去追踪为什么某个 JAVA_HOME 或 PATH 指向了我意料之外的地方。which 了解命令,direnv status 了解 direnv,launchctl getenv 了解 launchd。它们都不会告诉你 ~/.zshrc:42 才是真正的写入者。
支持 macOS、Linux 和 FreeBSD 上的 zsh 和 bash。下面的示例输出使用 ~/ 以保持简洁;该工具在所有地方都打印绝对路径,report 除外。
“啊哈”时刻
grep -r JAVA_HOME ~ 会显示所有提到该变量的文件。但它不会告诉你在你当前所在的 shell 中哪个赋值胜出:
$ envocabulary explain --chain JAVA_HOME
JAVA_HOME
origin shell-file
primary ~/helpers.sh:3
chain ~/.zshrc → ~/helpers.sh
writers
~/.zshenv:8
~/helpers.sh:3 (winner)
value [hidden, use --values]
通过 eval "$(brew shellenv)"、eval "$(pyenv init -)" 之类方式设置的变量会指向 eval 那一行。生成的代码没有自己的行号;eval 才是你能在编辑器中打开的东西。
安装
curl -fsSL https://raw.githubusercontent.com/sreckoskocilic/envocabulary/main/install.sh | sh
该脚本会检测操作系统和架构,下载发布归档,验证其 sha256 校验和,并在安装了 cosign 时验证 cosign 签名。如果 /usr/local/bin 可写则安装到那里,否则安装到 ~/.local/bin,并在该目录不在你的 $PATH 中时发出警告。在 macOS 上它还会清除 Gatekeeper 隔离标志。
选项:sh -s -- --version v1.0.4 用于固定版本,--bin-dir DIR 用于选择目标目录。
或者使用 go install github.com/sreckoskocilic/envocabulary/cmd/envocabulary@latest。请注意,go install 构建的版本在 --version 中会报告 dev;只有发布二进制文件才带有版本、提交和构建日期。
预构建的二进制文件和 Linux 软件包(.deb / .rpm / .apk / .pkg.tar.zst)在发布页面上。
命令
实时环境(读取你正在运行的 shell):
scan(默认) — 打印当前环境中的所有变量,按来源分组explain NAME— 对单个变量进行完整归属分析path [VARNAME...]— 对以冒号分隔的路径变量进行逐项归属分析;--check查找失效条目
静态文件:
inventory— 列出$HOME中的 shell 配置文件(规范文件以及.zshrc.bak之类的备份变体),并按类型统计定义数量catalog— 按 shell 读取它们的顺序拼接打印这些文件dedup— 针对 export、assign、alias、function 的重复报告,包括文件内和跨文件dangling— 列出目标已不存在的配置条目;发现任何条目时以 1 退出lost— 列出仅存在于孤立/备份文件中的定义report— 综合审计:可安全删除、需审查(值不同的重复项,以及所有重复函数)、悬空、孤立文件;--html会在当前目录写入一个带时间戳的.html文件clean [--full] FILE— 列出将被剥离的注释行;使用--full时打印清理后的内容
其他:
-V、--version— 打印版本、提交和构建日期
envocabulary <cmd> -h 查看标志。
退出码:成功时为 0,运行时错误或 dangling / path --check 发现问题时为 1,用法错误时为 2。
查找失效引用
dangling 列出不再指向任何东西的配置条目,即 JAVA_HOME=/opt/jdk-i-uninstalled 和 source ~/dotfiles/work-old.zsh 这类残留:
$ envocabulary dangling
## ~/.zshrc
~/.zshrc:14 source → ~/dotfiles/work-old.zsh (source target missing)
~/.zshrc:42 export JAVA_HOME → /opt/jdk-11 (path does not exist)
追踪 PATH 条目
path 显示 PATH(或 MANPATH、FPATH 等)中每个条目是在哪里引入的:
$ envocabulary path PATH
## PATH
/opt/homebrew/bin ~/.zprofile:6
/opt/homebrew/sbin ~/.zprofile:6
/usr/local/bin /etc/zprofile:11
/usr/bin inherited
/bin inherited
~/.cargo/bin ~/.zshrc:22
未匹配到任何 shell 启动赋值的条目显示为 inherited。这始终包括 /usr/bin、/bin、/usr/sbin 和 /sbin:它们是追踪器起始的种子,而不是某个文件添加的。
--check 会过滤出目录已不存在的条目,并针对你的 dotfiles 和 /etc/paths.d 重新解析来源,从而指向需要编辑的行:
$ envocabulary path --check PATH
## PATH
/opt/homebrew/Cellar/go/1.25.1/libexec/bin ~/.zshrc:17 (does not exist)
/opt/pkg/env/active/bin /etc/paths.d/10-pmk-global:1 (does not exist)
/Applications/VMware /etc/paths.d/com.vmware.fusion.public:1 (does not exist)
发现失效条目时以 1 退出,这使其可用于脚本中。
限制
- 每行一个赋值:
export EDITOR=vim VISUAL=vim只记录EDITOR。 - 静态命令只查看
$HOME中的规范 dotfiles(.zshenv、.zprofile、.zshrc、.zlogin、.zlogout、.bashrc、.bash_profile、.profile)及其.bak/.old风格的变体。它们不会跟随$ZDOTDIR、~/.config/zsh、/etc或你source的文件。 dangling会跳过类 PATH 的值以及任何包含展开的内容(export GOPATH=$HOME/go);它无法静态解析这些内容。path的归属分析基于 xtrace 差异。第一个包含某条目的赋值会认领它,即使它是通过$PATH展开被携带过来而非显式添加的。- 在 bash 下,通过
eval运行的代码会报告eval那一行外加一个偏移量;bash 不像 zsh 那样为 eval 主体提供标记。 - 不支持的 shell:fish、nu、csh/tcsh、PowerShell。
设计上只读
envocabulary 永远不会 unset、rm 或编辑你的 shell 配置。一个应急工具不应该成为让紧急情况变得更糟的东西。如果你想清理,复制 file:line 指针并自己进行编辑。clean 输出到 stdout;重定向由你来做。