-
Notifications
You must be signed in to change notification settings - Fork 0
Description
问题标题
path_helper 引起的 $PATH 环境变量顺序错乱问题
问题描述
所有的环境安装完成后,可能出现某些命令无法执行。这个时候检查系统变量 $PATH,发现通过脚本自动添加的 $PATH 路径
(例如 $HOME/.nvm/versions/node/v4.2.1/bin 等)并未置于 $PATH 之首,造成 $HOME/.rvm/scripts/rvm 等环境脚本加载失败
解决方案
将解决方案置前,以便快速阅读;问题分析见下文。
原则上,将在 $PATH 中添加前置路径的脚本,从 .zshenv 移到 .zprofile 和 .zshrc 中加载,即可。
其余的,具体情况具体分析。
问题解析
通过实验,得到 zsh 的配置文件的加载规律:
@startuml
start
#pink:A1*;
note:/etc/zshenv
#pink:A2;
note:$HOME/.zshenv
if (login ?) then (yes)
#cyan:B1;
note left
==/etc/zprofile==
~# System-wide profile for interactive zsh(1) login shells.
~# Setup user specific overrides for this in ~/.zprofile. See zshbuiltins(1)
~# and zshoptions(1) for more details.
if [ -x /usr/libexec/<color:red>path_helper</color> ]; then
eval `/usr/libexec/<color:red>path_helper</color> -s`
fi
end note
#cyan:B2;
note:$HOME/.zprofile
if (interactive ?) then (yes)
#lime:C1;
note:/etc/zshrc
#lime:C2;
note:$HOME/.zshrc
else (no)
endif
#lightblue:D1*;
note:/etc/zlogin
#lightblue:D2;
note:$HOME/.zlogin
:logout;
#lightblue:E2;
note:$HOME/.zlogout
#lightblue:E1*;
note:/etc/zlogout
else (no)
if (interactive ?) then (yes)
#lime:C1;
#lime:C2;
else (no)
endif
endif
stop
@enduml#
# A: /etc/zshenv B: ~/.zshenv C: /etc/zprofile D: ~/.zprofile
# E: /etc/zshrc F: ~/.zshrc G: /etc/zlogin H: ~/.zlogin
# I: ~/.zlogout J: /etc/zlogout
#+-------------------+-------------------------------------------+
#| | login |
#| +------------------------------+------------+
#| | yes | no |
#+-------------+-----+------------------------------+------------+
#| | yes | A->B->C->D->E->F->G->H->I->J | A->B->E->F |
#| interactive |-----+------------------------------+------------+
#| | no | A->B->C->D-> G->H->I->J | A->B |
#+-------------+-----+------------------------------+------------+
#
从加载顺序中可以看出来,.zshenv 文件是能保证被第一个加载的。所以,大部分的 zsh 配置指令都应该写在这个文件中。
OS X El Capitan 系统中,有两个 zsh 的默认配置文件,其中内容如下:
/etc/zprofile:
# system-wide environment settings for zsh(1)
if [ -x /usr/libexec/path_helper ]; then
eval `/usr/libexec/path_helper -s`
fi/etc/zshrc:
# Correctly display UTF-8 with combining characters.
if [ "$TERM_PROGRAM" = "Apple_Terminal" ]; then
setopt combiningchars
fi我们发现,/etc/zprofile 引用了一个可执行文件
/usr/libexec/path_helper,那这个文件的作用是什么呢?
原来,苹果使用一套新的机制希望来替换传统的直接修改环境变量的方式:path_helper。
path_helper 命令只是用来输出一个 shell 语句,例如:
export $PATH=<...>
export $MANPATH=<...>而本身并不执行任何修改。因此,可使用 eval 命令执行修改。-s 参数的作用,是只生成 $PATH 的 export 语句。
而执行 path_helper 命令的时候,它会按照以下次序,依次添加路径:
/etc/paths文件中的路径/etc/paths.d目录下所有文件中的路径- 当前
$PATH变量
其中,重复路径不再添加。
现在我们来推测一下:当系统加载 zsh 环境的时候,$PATH 环境变量到底发生了什么?
由于 OS X El Capitan 系统中默认不存在 /etc/zshenv 文件,所以 zsh 加载的第一个文件是 .zshenv。加载 .zshenv 后,rvm、nvm.sh 等环境配置脚本被执行,此时 $PATH 是理想的状态;
当系统执行 /etc/zprofile 文件的时候,文件中的 path_helper 指令对 $PATH 变量中所有的路径重新做了一个排序,系统默认的 /bin 路径自动排到了最前面,元凶终于找到了:)