今天我和朋友聊到如何把目录添加到 PATH 里。对我来说,这似乎是件“显而易见”的事,毕竟我用终端已经很久了。但当我搜索具体操作步骤时,却找不到一个能完整解释所有环节的教程——很多教程只说“把它加到 ~/.bashrc 里”,可如果你用的不是 bash 呢?如果你的 bash 配置实际上在另一个文件里呢?而且,你又怎么知道该添加哪个目录呢?
所以我打算试着写一份更完整的指南,并提一下这些年我遇到过的几个坑。
以下是目录:
第一步:你用的什么 shell?
如果你不确定自己用的什么 shell,可以用这个方法查一下。运行以下命令:
ps -p $$ -o pid,comm=
- 如果你用的是 bash,它会输出
97295 bash - 如果你用的是 zsh,它会输出
97295 zsh - 如果你用的是 fish,它会输出类似“In fish, please use $fish_pid”的错误(
$$在 fish 中不是有效语法,但无论如何,错误信息会告诉你用的是 fish,这你大概早就知道了)
另外,Linux 默认是 bash,Mac OS(截至 2024 年)默认是 zsh。本指南只涵盖 bash、zsh 和 fish。
第二步:找到你的 shell 配置文件
- 在 zsh 中,通常是
~/.zshrc - 在 bash 中,可能是
~/.bashrc,但情况比较复杂,详见下一节的说明 - 在 fish 中,通常是
~/.config/fish/config.fish(如果想 100% 确定,可以运行echo $__fish_config_dir)
关于 bash 配置文件的说明
Bash 有三个可能的配置文件:~/.bashrc、~/.bash_profile 和 ~/.profile。
如果你不确定系统用的是哪个,我建议这样测试:
- 在
~/.bashrc中添加echo hi there - 重启你的终端
- 如果看到“hi there”,说明
~/.bashrc正在被使用!太棒了! - 否则,删除它,然后用
~/.bash_profile重复同样的操作 - 如果前两个都不行,还可以试试
~/.profile
(网上有很多详细的流程图解释 Bash 如何决定使用哪个配置文件,但我觉得没必要死记硬背,直接测试才是最快确认的方法)
第三步:确定要添加的目录
假设你正在尝试安装并运行一个叫 http-server 的程序,但运行失败,就像这样:
$ npm install -g http-server
$ http-server
bash: http-server: command not found
如何找到 http-server 所在的目录?老实说,这通常并不容易——答案往往是“取决于 npm 的配置”。这里有几个思路:
- 通常设置新安装工具(如
cargo、npm、homebrew等)时,首次配置会打印一些关于如何更新 PATH 的说明。所以如果你留意了,当时就能拿到指引。 - 有时安装工具会自动更新你的 shell 配置文件来帮你更新
PATH。 - 有时直接搜索“npm 把东西装在哪里?”就能找到答案。
- 有些工具提供了子命令来告诉你它们配置的安装位置,比如:
- Node/npm:
npm config get prefix(然后加上/bin/) - Go:
go env GOPATH(然后加上/bin/) - asdf:
asdf info | grep ASDF_DIR(然后加上/bin/和/shims/)
- Node/npm:
第三步.1:再次确认目录是否正确
找到你认为正确的目录后,务必验证一下!例如,我发现我的机器上 http-server 位于 ~/.npm-global/bin。我可以尝试直接运行该目录下的 http-server 程序来确认:
$ ~/.npm-global/bin/http-server
Starting up http-server, serving ./public
成功了!现在你知道需要添加到 PATH 的目录了,进入下一步!
第四步:编辑 shell 配置文件
现在我们有了两个关键信息:
- 你要添加到 PATH 的目录(比如
~/.npm-global/bin/) - 你的 shell 配置文件位置(比如
~/.bashrc、~/.zshrc或~/.config/fish/config.fish)
接下来根据你的 shell 添加内容:
Bash 操作说明:
打开 shell 配置文件,添加一行类似这样的内容:
export PATH=$PATH:~/.npm-global/bin/
(显然,将 ~/.npm-global/bin 替换为你实际要添加的目录)
Zsh 操作说明:
你可以像 Bash 一样操作,但 Zsh 也支持一些更高级的语法,如果你喜欢的话:
path=(
$path
~/.npm-global/bin
)
Fish 操作说明:
在 Fish 中,语法不同:
set PATH $PATH ~/.npm-global/bin
(在 Fish 中你也可以使用 fish_add_path,关于这点下文有说明)
第五步:重启 shell
现在,非常重要的一步:更新 shell 配置文件后,如果不重启 shell,更改不会生效!
有两种方法可以做到:
- 打开一个新的终端(或终端标签页),最好关闭旧的以免混淆
- 运行
bash启动一个新的 shell(如果你使用 zsh,则运行zsh;如果使用 fish,则运行fish)
我发现这两种方法通常都能正常工作。
然后你就应该搞定了!尝试运行你之前想运行的程序,希望现在能正常工作。
如果不行,这里有几个你可能遇到的问题:
问题 1:运行了错误的程序
如果运行的是错误的版本,你可能需要将该目录添加到 PATH 的开头而不是末尾。
例如,在我的系统上安装了两个版本的 python3,可以通过运行 which -a 查看:
$ which -a python3
/usr/bin/python3
/opt/homebrew/bin/python3
你的 shell 将使用第一个列出的版本。
如果你想使用 Homebrew 版本,需要将该目录(/opt/homebrew/bin)添加到 PATH 的开头,方法是在 shell 的配置文件中写入以下内容(使用 /opt/homebrew/bin/:$PATH 而不是通常的 $PATH:/opt/homebrew/bin/):
export PATH=/opt/homebrew/bin/:$PATH
或者在 fish 中:
set PATH ~/.cargo/bin $PATH
问题 2:程序不是从你的 shell 中运行的
所有这些说明仅在从你的 shell 中运行程序时才有效。如果你从 IDE、GUI、cron 作业或其他方式运行程序,则需要以不同的方式将目录添加到 PATH,具体细节可能取决于具体情况。
在 cron 作业中
一些选项:
- 使用你要运行的程序的完整路径,例如
/home/bork/bin/my-program - 将你想要的完整 PATH 作为 crontab 的第一行(类似
PATH=/bin:/usr/bin:/usr/local/bin:…)。你可以通过在 shell 中运行echo "PATH=$PATH"来获取你当前使用的完整 PATH。
老实说,我不确定如何在 IDE/GUI 中处理这个问题,因为我已经很久没有遇到这种情况了,如果有人给我指路,我会在这里添加说明。
问题 3:重复的 PATH 条目使调试更困难
如果你编辑了路径并通过运行 bash(或 zsh、fish)启动一个新的 shell,通常会得到重复的 PATH 条目,因为每次启动 shell 时,shell 都会不断向 PATH 添加新内容。
就我个人而言,我不认为这种重复会导致任何问题,但如果你试图理解 PATH 的内容,重复项可能会使调试变得更困难。
一些处理方法:
- 如果你在调试
PATH,请打开一个新终端来操作,这样能获得一个“干净”的状态,避免重复。 - 在你的 shell 配置文件的末尾对
PATH进行去重(例如在 zsh 中,你可以用typeset -U path实现)。 - 在添加目录时,检查该目录是否已存在于
PATH中(例如在 fish 中,我认为可以用fish_add_path --path /some/directory实现)。
如何对 PATH 去重取决于你使用的 shell,并非所有 shell 都内置了去重功能,因此你需要查阅如何在你使用的 shell 中实现。
问题 4:更新 PATH 后丢失历史记录
在 bash 或 zsh 中,很容易遇到以下情况:
- 运行一条命令(它失败了)
- 更新你的
PATH - 运行
bash重新加载配置 - 按几次上箭头重新运行失败的命令(或打开一个新终端)
- 失败的命令不在历史记录中!为什么?
这是因为在 bash 中,默认情况下,历史记录直到你退出 shell 时才会保存。
一些解决方法:
- 不要运行
bash重新加载配置,而是运行source ~/.bashrc(在 zsh 中运行source ~/.zshrc)。这会在当前会话中重新加载配置。 - 配置你的 shell 持续保存历史记录,而不是仅在 shell 退出时保存。(具体方法取决于你使用的是 bash 还是 zsh,zsh 的历史记录选项有点复杂,我不太确定最佳方式是什么)
关于 source 的说明
当你首次安装 cargo(Rust 的安装器)时,它会提供以下设置 PATH 的说明,这些说明完全没有提及特定目录。
通常通过运行以下命令之一来完成(注意开头的点号):
. "$HOME/.cargo/env" # 适用于 sh/bash/zsh/ash/dash/pdksh
source "$HOME/.cargo/env.fish" # 适用于 fish
其思路是,你将这一行添加到 shell 配置中,然后它们的脚本会自动为你设置 PATH(以及可能的其他内容)。
这种做法相当常见(例如 Homebrew 建议你 eval brew shellenv),有两种处理方式:
- 直接按照工具的建议操作(例如将
. "$HOME/.cargo/env"添加到你的 shell 配置中) - 弄清楚它们让你运行的脚本会向你的 PATH 添加哪些目录,然后手动添加这些目录。以下是我的做法:
- 在我的 shell 中运行
. "$HOME/.cargo/env"(如果使用 fish,则运行 fish 版本) - 运行
echo "$PATH" | tr ':' '\n' | grep cargo来找出它添加了哪些目录 - 看到它显示
/Users/bork/.cargo/bin,将其缩短为~/.cargo/bin - 按照本文的说明将目录
~/.cargo/bin添加到 PATH 中
- 在我的 shell 中运行
我不认为按照工具建议的方式操作有什么问题(那可能是“最佳方法”!),但个人通常采用第二种方式,因为我更喜欢清楚知道自己正在修改哪个配置。
关于 fish_add_path 的说明
Fish 有一个名为 fish_add_path 的便捷函数,可以这样运行来将目录添加到 PATH:
fish_add_path /some/directory
这个命令很酷(如此简洁!),但我已经停止使用它,原因有二:
fish_add_path有时会更新未来所有会话的PATH(通过“通用变量”),有时又只更新当前会话的PATH,我很难判断它会采用哪种方式。理论上文档有解释,但我理解不了。- 如果几周或几个月后因操作失误需要移除
PATH中的目录,操作起来相当困难(不过 这个 GitHub issue 的评论中有相关说明)。
总结
希望这对某些人有帮助。如果你在向 PATH 添加目录时遇到过其他重大陷阱,或对本文有疑问,欢迎(在 Mastodon 或 Bluesky 上)告诉我!