zsh: Rewrite how $PATH are configured in zsh startup scripts
Changes:
$PATH (or $path in zsh) configurations for zsh shell and scripts are unified and all moved to ~/.zshenv, from previously being scattered at many different places without a clear sense about what-goes-where.
This could be a more principled solution to the very annoying problem where $PATH being messed up in nested shells and tmux sessions, etc. (see also 97370d32, d7c7cad6, 1fd48b324 for more context and history).
With the following changes, we are now able to have a consistent$PATH environment regardless of whether we are opening a new terminal, using tmux, using a sub-shell (either interactive or non-interactive) where $PATH is set as inherited from the parent shell, etc.
Problem and Solution: (1) Fix broken $PATH on macOS
The crux of the problem is that, on macOS, $PATH is messed up by having the "system, global" path entries from /etc/paths such as /usr/bin, /bin are always prepended. (see the ⚠️ mark below and "fighting with path_helper")
To prevent this from happening, we disable sourcing global RC files (/etc/zprofile, /etc/zshrc, etc.) and take care of them manually if needed. This also makes the behavior consistent with other Linux systems.
Problem and Solution: (2) Configure $path in the right place.
In short, user-scope $path are added and configured in ~/.zshenv if they should be visible by both interactive-shells and scripts.
Some $path entries that should be used only for interactive shells (but not for scripts) can lie in ~/.zshrc (not in ~/.zprofile), for the sake of performance. Note that ~/.zshenv is sourced by all scripts, so it should be quite fast.
Adding a path entry is done in such a manner that it's prepended to $path, buf only if it's not already included in the $path. If we always prepend, the path order set by other sources or plugins (e.g., anaconda or virtual environments) can be inconsistent in nested shells. This is not to shadow any $PATH entries inherited from the parent shell or process.
Background Knowledge: How does zsh startup work?
FYI, the execution order of zsh startup scripts is (see zsh manual):
antidote puts $fpath entries for the zsh plugins after the system site-functions path.
Since we want custom shell completions shipped with zsh plugins, we manually fix the order of $fpath after sourcing all the antidote plugins as a workaround.
zsh: Rewrite how $PATH are configured in zsh startup scripts
Changes:
$PATH
(or$path
in zsh) configurations for zsh shell and scripts are unified and all moved to~/.zshenv
, from previously being scattered at many different places without a clear sense about what-goes-where.This could be a more principled solution to the very annoying problem where $PATH being messed up in nested shells and tmux sessions, etc. (see also 97370d32, d7c7cad6, 1fd48b324 for more context and history).
With the following changes, we are now able to have a consistent
$PATH
environment regardless of whether we are opening a new terminal, using tmux, using a sub-shell (either interactive or non-interactive) where$PATH
is set as inherited from the parent shell, etc.Problem and Solution: (1) Fix broken
$PATH
on macOSThe crux of the problem is that, on macOS,
$PATH
is messed up by having the "system, global" path entries from/etc/paths
such as/usr/bin
,/bin
are always prepended. (see the ⚠️ mark below and "fighting with path_helper")To prevent this from happening, we disable sourcing global RC files (
/etc/zprofile
,/etc/zshrc
, etc.) and take care of them manually if needed. This also makes the behavior consistent with other Linux systems.Problem and Solution: (2) Configure
$path
in the right place.$path
are added and configured in~/.zshenv
if they should be visible by both interactive-shells and scripts.$path
entries that should be used only for interactive shells (but not for scripts) can lie in~/.zshrc
(not in~/.zprofile
), for the sake of performance. Note that~/.zshenv
is sourced by all scripts, so it should be quite fast.$path
, buf only if it's not already included in the$path
. If we always prepend, the path order set by other sources or plugins (e.g., anaconda or virtual environments) can be inconsistent in nested shells. This is not to shadow any$PATH
entries inherited from the parent shell or process.Background Knowledge: How does zsh startup work?
FYI, the execution order of zsh startup scripts is (see zsh manual):
Or more specifically, consider the following four cases:
-l
,-i
: interactive, login-i
: interactive, non-loginzsh
orexec zsh
on an existing interactive shell, or opening a terminal window inside a neovimzsh -c "ls"
). Also includes some GUI apps withSHELL=zsh
.-l
: non-interactive, login (this is not usual)echo ... | ssh server
,zsh -l -c "ls"
-l
,-i
-i
-l
Legend:
(or
/etc/zsh/*) are only if
GLOBAL_RCS` is set.path_helper
will mess up$PATH
, adding the system paths (e.g.,/usr/bin
,/bin
) frontmost to$PATH
. See "fighting with path_helper".--norc
,--rcfile
,--noprofile
, etc. are not considered.References:
zsh: Fix incorrect $fpath ordering
antidote puts
$fpath
entries for the zsh plugins after the system site-functions path.Since we want custom shell completions shipped with zsh plugins, we manually fix the order of $fpath after sourcing all the antidote plugins as a workaround.