How to add a directory to your PATH

Julia Evans

如何将目录添加到 PATH

原文由 Julia Evans 发布,订阅该博客

今天我和朋友聊了聊如何把一个目录添加到 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 了,而你大概本来就知道)

另外,bash 是 Linux 上的默认 shell,zsh 是 macOS 上的默认 shell(截至 2024 年)。这篇教程只会涉及 bash、zsh 和 fish。

第二步:找到 shell 的配置文件

  • 在 zsh 里,通常是 ~/.zshrc
  • 在 bash 里,可能是 ~/.bashrc,但情况比较复杂,详见下一节的说明
  • 在 fish 里,通常是 ~/.config/fish/config.fish(如果你想百分百确定,可以运行 echo $__fish_config_dir

关于 bash 配置文件的说明

Bash 有三个可能的配置文件:~/.bashrc~/.bash_profile~/.profile

如果你不确定自己的系统用的是哪一个,我建议这样测试:

  1. ~/.bashrc 里加上 echo hi there
  2. 重启终端
  3. 如果你看到了“hi there”,就说明正在使用 ~/.bashrc!太好了!
  4. 否则就把它删掉,再用 ~/.bash_profile 试同样的方法
  5. 如果前两个都不行,也可以试试 ~/.profile

(网上有很多复杂的流程图来解释 bash 是如何决定使用哪个配置文件的,但我觉得没必要去记这些,直接测试才是最快、最可靠的办法)

第三步:弄清楚要添加哪个目录

比如说,你想安装并运行一个叫 http-server 的程序,结果却不行,像这样:

$ npm install -g http-server
$ http-server
bash: http-server: command not found

怎么找到 http-server 所在的目录呢?说实话,这通常不太容易——答案往往是“这取决于 npm 是怎么配置的”。这里有几个思路:

  • 很多时候在初次设置新的安装工具(比如 cargonpmhomebrew 等)时,它会打印出如何更新 PATH 的说明。所以如果你当时留意了,就能直接拿到操作方法。
  • 有时安装程序会自动帮你更新 shell 的配置文件,把 PATH 改好
  • 有时直接搜一下“where does npm install things?”就能找到答案
  • 有些工具有子命令可以直接告诉你它们的安装位置,比如:
    • Node/npm:npm config get prefix(然后在后面加上 /bin/
    • Go:go env GOPATH(然后在后面加上 /bin/
    • asdf:asdf info | grep ASDF_DIR(然后在后面加上 /bin//shims/

第 3.1 步:确认目录是否正确

找到一个你觉得可能正确的目录后,一定要确认它真的对!比如,我发现自己机器上的 http-server~/.npm-global/bin 里。我可以通过直接运行该目录下的 http-server 来确认,像这样:

$ ~/.npm-global/bin/http-server
Starting up http-server, serving ./public

成功了!现在你已经知道要把哪个目录添加到 PATH,我们进入下一步!

第四步:编辑 shell 配置文件

现在我们已经有了需要的两条关键信息:

  1. 你想添加到 PATH 的是哪个目录(比如 ~/.npm-global/bin/
  2. 你的 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 配置后,如果不重启,是不会生效的!

有两种办法:

  1. 打开一个新的终端(或终端标签页),最好把旧的关掉,免得弄混
  2. 运行 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、图形界面、cron 定时任务或其他方式运行程序,就需要用别的方法把目录加到 PATH,具体细节取决于实际情况。

在 cron 定时任务中

一些可选方案:

  • 使用要运行程序的完整路径,比如 /home/bork/bin/my-program
  • 在 crontab 的第一行写上你想要的完整 PATH(类似 PATH=/bin:/usr/bin:/usr/local/bin:…)。你可以在 shell 里运行 echo "PATH=$PATH" 来获取当前使用的完整 PATH。

老实说,我已经很久没遇到在 IDE/图形界面中处理 PATH 的情况了,所以不太确定该怎么做,如果有人能指点,我会把相关说明补在这里。

问题 3:重复的 PATH 条目让调试更困难

如果你编辑了 PATH,然后通过运行 bash(或 zshfish)来启动新 shell,常常会出现重复的 PATH 条目,因为每次启动 shell 时,它都会往 PATH 里重复添加内容。

就我个人而言,我还没遇到过这种重复会导致什么故障的情况,但如果你想搞清楚 PATH 里到底有什么,重复项会让调试变得更麻烦。

有几种处理办法:

  1. 如果你在调试 PATH,就在全新的终端里进行,这样能得到“干净”的状态。应该可以避免重复。
  2. 在 shell 配置文件的末尾对 PATH 去重(比如在 zsh 里据说可以用 typeset -U path 来实现)
  3. 在添加目录时检查它是否已经在 PATH 中(比如在 fish 里我记得可以用 fish_add_path --path /some/directory 来做)

如何给 PATH 去重取决于具体的 shell,而且并不总是有内置的方法,所以你需要去查一下在自己用的 shell 里该怎么做。

问题 4:更新 PATH 后丢失历史记录

在 bash 或 zsh 里,很容易遇到这种情况:

  1. 运行一条命令(失败了)
  2. 更新你的 PATH
  3. 运行 bash 来重新加载配置
  4. 按几下上方向键想重新运行那条失败的命令(或者打开一个新终端)
  5. 结果那条失败的命令不在历史记录里!为什么呢?

这是因为在 bash 中,默认情况下历史记录要等到退出 shell 时才会保存。

一些解决方法:

  • 不要用运行 bash 来重新加载配置,改为运行 source ~/.bashrc(在 zsh 里是 source ~/.zshrc)。这样会在当前会话中重新加载配置。
  • 把 shell 配置为持续保存历史记录,而不是只在退出时才保存。(具体怎么做取决于你用的是 bash 还是 zsh,zsh 的历史记录选项有点复杂,我也不太确定最好的办法是什么)

关于 source 的说明

第一次安装 cargo(Rust 的安装工具)时,它会给出这样的 PATH 设置说明,里面完全没有提到具体的目录。

This is usually done by running one of the following (note the leading DOT):

. "$HOME/.cargo/env"        	# For sh/bash/zsh/ash/dash/pdksh
source "$HOME/.cargo/env.fish"  # For fish

它的思路是,你把那一行加到 shell 配置里,它们的脚本就会自动帮你设置好 PATH(以及其他一些东西)。

这很常见(比如 Homebrew 就建议你执行 brew shellenv),处理这个问题有两种方式:

  1. 直接按工具建议的做(比如把 . "$HOME/.cargo/env" 加到 shell 配置里)
  2. 弄清楚它们让你运行的脚本会往 PATH 里添加哪些目录,然后手动添加它们。我的做法是:
    • 在我的 shell 里运行 . "$HOME/.cargo/env"(如果用 fish,就运行对应的 fish 版本)
    • 运行 echo "$PATH" | tr ':' '\n' | grep cargo 来看看它添加了哪些目录
    • 看到它显示 /Users/bork/.cargo/bin,然后简写为 ~/.cargo/bin
    • 把目录 ~/.cargo/bin 添加到 PATH(按本文中的步骤操作)

我觉得按工具建议的做也没什么不对(那可能就是“最佳方式”!),但我个人通常会用第二种办法,因为我更想清楚地知道自己到底改了哪些配置。

关于 fish_add_path 的说明

fish 有一个很方便的函数叫 fish_add_path,可以像这样把目录添加到 PATH

fish_add_path /some/directory

这很酷(命令这么简单!),但我已经不再用它了,原因有几个:

  1. 有时候 fish_add_path 会用“通用变量”的方式为今后所有会话更新 PATH,有时候又只为当前会话更新,很难判断它到底会怎么做。理论上文档里有解释,但我没看懂。
  2. 如果你几周或几个月后因为弄错了,需要把某个目录从 PATH移除,会有点麻烦(不过这个 GitHub issue 的评论里有操作方法)。

就这些

希望这能帮到一些人。如果你把目录添加到 PATH 时还遇到过其他大坑,或者对本文有疑问,欢迎在 Mastodon 或 Bluesky 上告诉我!

本文章由 muse-spark-1.2-contributor 进行翻译

评论