Mac OS X 中 zsh 下 PATH 环境变量的正确设置

/

在 Mac OS X 中使用 zsh,环境变量 PATH 一不小心就会变得很紊乱,表现为自己设置的路径总是被放到系统路径之后,部分路径还会有重复。这是我们不太了解 zsh 启动时加载文件的顺序和 Mac OS X 的 shell 类型导致的。

zsh 启动过程中会依次读取以下文件:

  1. /etc/zshenv
  2. $ZDOTDIR/.zshenv($ZDOTDIR 未设置时默认为 $HOME)
  3. 如果是 login shell,读取 /etc/zprofile, $ZDOTDIR/.zprofile
  4. 如果是 interactive shell,读取 /etc/zshrc, $ZDOTDIR/.zshrc
  5. 如果是 login shell,读取 /etc/zlogin, $ZDOTDIR/.zlogin

login shell 是用户登陆时,输入用户名和密码后启动的 shell,non-login shell 是登录以后所打开的 shell。interactive shell 在终端上执行,shell 等待你的输入,并且立即执行你提交的命令,跟用户存在交互;non-interactive shell 以 shell script(非交互)方式执行。

interactive, login shell 比较常见,我们 ssh 到远程主机的字符终端时,就是典型的这类 shell。non-interactive, login shell 很少见,一些 X 设置会让你登陆,目的仅仅是加载它的相应设置文件。interactive, non-login shell 通常发生在通过已存在的 session 启动一个新的 shell,比如 linux 下的 screen、tmux,linux 下桌面环境启动的 shell 也是这类 shell,比如 gnome shell。non-interactive, non-login shell 很常见,我们执行 shell script 就是这类 shell。

+--------------+-------------+-------------+-----------------+-----------------+
|              |    login    |  non-login  |      login      |    non-login    |
|              | interactive | interactive | non-interactive | non-interactive |
+--------------+-------------+-------------+-----------------+-----------------+
|/etc/zshenv   |     A       |      A      |        A        |        A        |
+--------------+-------------+-------------+-----------------+-----------------+
|~/.zshenv     |     B       |      B      |        B        |        B        |
+--------------+-------------+-------------+-----------------+-----------------+
|/etc/zprofile |     C       |             |        C        |                 |
+--------------+-------------+-------------+-----------------+-----------------+
|~/.zprofile   |     D       |             |        D        |                 |
+--------------+-------------+-------------+-----------------+-----------------+
|/etc/zshrc    |     E       |      C      |                 |                 |
+--------------+-------------+-------------+-----------------+-----------------+
|~/.zshrc      |     F       |      D      |                 |                 |
+--------------+-------------+-------------+-----------------+-----------------+
|/etc/zlogin   |     G       |             |        E        |                 |
+--------------+-------------+-------------+-----------------+-----------------+
|~/.zlogin     |     H       |             |        F        |                 |
+--------------+-------------+-------------+-----------------+-----------------+
|              |             |             |                 |                 |
+--------------+-------------+-------------+-----------------+-----------------+
|              |             |             |                 |                 |
+--------------+-------------+-------------+-----------------+-----------------+
|~/.zlogout    |     I       |             |        G        |                 |
+--------------+-------------+-------------+-----------------+-----------------+
|/etc/zlogout  |     J       |             |        H        |                 |
+--------------+-------------+-------------+-----------------+-----------------+

那么问题来了,在 Mac OS X 中打开 iTerm.app 或者 Terminal.app 启动的 shell 是什么类型呢?通常来说,应该是 interactive, non-login shell,但实际上却是 interactive, login shell,至于为什么这样就不深究了。下面的测试代码可以证明:

[[ -o login ]] && echo 'yes' || echo 'no'
[[ -o interactive ]] && echo 'yes' || echo 'no'

所以,打开 iTerm.app 或者 Terminal.app 启动的 shell 会读取上述1-5中存在的所有文件,如果其中多个文件均对 PATH 环境变量作过设置,那么最终呈现的 PATH 环境变量就会比较复杂,部分路径重复也就不足为奇了。查看 /etc/zshenv,会发现调用的是/usr/libexec/path_helper,而它加载的正是系统路径,并且将系统路径放在最前。如果接下来用户在 $ZDOTDIR 中的文件中加载了自己设置的路径并置于最前,再接下来再加载的 /etc/zprofile、/etc/zshrc 可能还会调用/usr/libexec/path_helper,又造成了系统路径重新被放到最前面,形成了奇葩的 PATH 环境变量系统路径、自设路径、系统路径交错的现象。

了解了这么多,解决方法也很简单,那就是上述1-5中仅让必要的文件涉及 PATH 环境变量。比如在 /etc/zshenv 中通过调用/usr/libexec/path_helper设置系统路径,$ZDOTDIR/.zshenv 中将自设路径放在最前,其余文件均不涉及 PATH 环境变量设置。

Comments