Skip to content

path_helper 引起的 $PATH 环境变量顺序错乱问题 #1

@dz85

Description

@dz85

问题标题

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 参数的作用,是只生成 $PATHexport 语句。

而执行 path_helper 命令的时候,它会按照以下次序,依次添加路径:

  1. /etc/paths 文件中的路径
  2. /etc/paths.d 目录下所有文件中的路径
  3. 当前 $PATH 变量

其中,重复路径不再添加。

现在我们来推测一下:当系统加载 zsh 环境的时候,$PATH 环境变量到底发生了什么?

由于 OS X El Capitan 系统中默认不存在 /etc/zshenv 文件,所以 zsh 加载的第一个文件是 .zshenv。加载 .zshenv 后,rvmnvm.sh 等环境配置脚本被执行,此时 $PATH 是理想的状态;

当系统执行 /etc/zprofile 文件的时候,文件中的 path_helper 指令对 $PATH 变量中所有的路径重新做了一个排序,系统默认的 /bin 路径自动排到了最前面,元凶终于找到了:)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions