Understanding the Difference Between .zprofile
and .zshrc
When configuring your shell, especially in Terminal.app, it’s important to understand the difference between .zprofile
and .zshrc
. Both are used in Zsh, but they run at different times and for different purposes.
What’s the Difference?
File | Runs During | Purpose |
---|---|---|
.zprofile |
Login shell | Set up environment variables like PATH |
.zshrc |
Interactive shell | Customize shell: prompt, aliases, tools |
.zprofile
Example
Used for things that must be set before any command runs — such as making a specific Python version available globally.
# ~/.zprofile
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init --path)"
This ensures the correct Python version is available when the shell starts.
.zshrc
Example
Used for interactive features — like customizing your prompt or defining aliases.
# ~/.zshrc
eval "$(pyenv init -)"
alias ll="ls -alF"
This makes your terminal easier and more powerful for interactive use.
🎬 Real Scenarios (Terminal.app)
Scenario 1: Both Run
Action: Open Terminal.app
Result:
.zprofile
runs (setsPATH
).zshrc
runs (sets aliases, prompt)
Scenario 2: Only .zshrc
Runs
Action: Run zsh -i
from an open Terminal
Result:
.zprofile
does not run.zshrc
runs
Scenario 3: Only .zprofile
Runs
Action: Run zsh -l -c 'echo $PATH'
Result:
.zprofile
runs.zshrc
does not run
Recommendation
Use the right file for the right job:
- Put environment setup in
.zprofile
- Put interactive customization in
.zshrc
This keeps your shell clean, efficient, and predictable.
🧨 Problem: Putting export PYENV_ROOT="$HOME/.pyenv"
in .zshrc
Instead of .zprofile
Understanding where to place your PYENV_ROOT
export is critical for consistent and reliable shell behavior, especially with pyenv
.
🔧 What’s Technically Wrong?
.zshrc
only runs for interactive shells- But
export PYENV_ROOT=...
is needed before the shell starts usingpyenv
, even in non-interactive or login shells
📉 What Could Break?
1. Login-only shells won’t know about PYENV_ROOT
- If a login shell runs a command (e.g., from a cron job, SSH, or GUI launcher), and
PYENV_ROOT
is only in.zshrc
, that session won’t know wherepyenv
is. - Result:
pyenv
fails silently — or defaults to broken paths.
2. VS Code / JetBrains Terminal / Scripts Might Break
- Many GUI apps launch subprocesses using login shells.
- If
PYENV_ROOT
is only in.zshrc
, these subprocesses won’t see it. - This causes failures when:
- Selecting Python interpreters in IDEs
- Running pre-configured build scripts
3. Performance & Redundancy
.zshrc
runs every time a new terminal is opened.- Re-exporting the same environment variable repeatedly is inefficient and messy.
✅ Best Practice
- Put
PYENV_ROOT
andPATH
exports in.zprofile
(run once per login shell) - Use
.zshrc
only for interactive shell customizations, like:
eval "$(pyenv init -)"
alias ll="ls -alF"
This ensures pyenv
behaves reliably across interactive terminals, login sessions, and automated environments.