The behaviour of the PATH
variable in ZSH on macOS can be confusing. If you follow ZSH guidance, you should set your PATH
variable in ~/.zshenv
.
“
.zshenv
is sourced on all invocations of the shell, unless the-f
option is set. It should contain commands to set the command search path, plus other important environment variables..zshenv
should not contain commands that produce output or assume the shell is attached to a tty.”
To illustrate the confusing behaviour, I need an example. I’m going to show how prepending a component to PATH
does not result in the expected behaviour. Before you jump in and tell me that this isn’t something I should be doing, I hear you. There are reasons why this is a bad idea and, in general, you should be appending PATH
components instead. I’m doing it here for illustrative purposes only.
My .zshenv
:
export PATH=/Users/bg/bin:$PATH
My PATH
variable:
/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/Library/Apple/usr/bin:/Users/bg/bin
I expected the /Users/bg/bin
path component to appear as the first component in the PATH
variable. system default. Instead it is being appended to the end of the existing PATH
. Why?
The answer lies in the order ZSH reads its configuration files. The full sequence depends on the type of shell you are opening. But we are lucky. We can explain this unexpected behaviour early on in the sequence.
/etc/zshenv
- this doesn’t exist on my machine~/.zshenv
- this is where we set our path/etc/zprofile
- this runs a utility calledpath_helper
~/.zprofile
- this doesn’t exist on my machine- …
The path_helper
utility sets up the default path components. Because it is called in /etc/zprofile it runs after we have made our PATH
modifications. We are prepending our custom path to an empty PATH
variable. The default PATH
components are set-up after our modifications. This explains why our custom path component appears at the end of the PATH
variable.
If we were to ignore the ZSH guidance and set up our path in ~/.zshrc
we don’t see this behaviour. This is because ~/.zshrc
exists later in the chain. We get the behaviour we expect.
My .zshrc
:
export PATH=/Users/bg/bin:$PATH
My PATH
variable:
/Users/bg/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/Library/Apple/usr/bin
In most cases it won’t matter where you set your PATH
variable. But it is reassuring to know that there is an explanation behind the behaviour I was seeing.