Zsh Startup Files: .zprofile versus .zshrc op macOS en Linux

Automatische vertaling Dit artikel is automatisch vertaald vanuit de oorspronkelijke Engelse versie.

Als je terminal traag aanvoelt of een environment variable wel in de ene terminal maar niet in de andere verschijnt, ligt de oorzaak vaak bij de startup mode van Zsh. De twee bestanden waar de meeste mensen naar grijpen zijn ~/.zprofile en ~/.zshrc, maar geen van beide bestandsnamen zegt op zichzelf waar een regel thuishoort.

Zsh kiest startup files op basis van twee onafhankelijke eigenschappen: of de shell een login shell is en of deze interactive is. Terminalapplicaties op macOS en Linux kunnen verschillende combinaties kiezen. Inspecteer daarom de actieve shell in plaats van ervan uit te gaan dat het besturingssysteem dit voor je bepaalt.

TL;DR. Plaats initialisatie voor de login session in .zprofile en interactive gedrag in .zshrc. Houd .zshenv stil en minimaal wanneer RCS is ingesteld. Verplaats interactive version-manager hooks niet naar .zprofile en source niet het volledige profile vanuit .zshrc; inspecteer de door de tool gegenereerde code en volg de actuele Zsh-instructies.

Zsh evalueert twee onafhankelijke flags

Zsh startup files geselecteerd door login- en interactive modesZsh startup files geselecteerd door login- en interactive modes

Een shell kan zijn:

  • login en interactive, wanneer Apple Terminal is ingesteld om de Default login shell van het account te openen
  • non-login en interactive, zoals bij een geneste zsh of bij veel Linux-terminalconfiguraties
  • login en non-interactive, zoals bij zsh -lc 'command'
  • non-login en non-interactive, zoals bij zsh -c 'command'

Terminalapplicaties, IDE’s, remote sessions, multiplexers en expliciete shell flags kunnen verschillende modes kiezen. Het besturingssysteem alleen bepaalt het resultaat dus niet.

Vraag het aan de actieve shell in plaats van te gokken:

print -r -- "interactive=$options[interactive] login=$options[login]"

De daadwerkelijke startup order

Zsh leest eerst globale files en daarna het bijbehorende user file. Met de standaard RCS options is de volgorde van de user files:

  1. $ZDOTDIR/.zshenv bij elke Zsh-invocation
  2. $ZDOTDIR/.zprofile als de shell een login shell is
  3. $ZDOTDIR/.zshrc als de shell interactive is
  4. $ZDOTDIR/.zlogin als de shell een login shell is
  5. $ZDOTDIR/.zlogout wanneer een login shell normaal afsluit

Als ZDOTDIR niet is ingesteld, gebruikt Zsh $HOME. De locaties van system-wide files zijn installatieafhankelijk; de gebruikelijke defaults zijn /etc/zshenv, /etc/zprofile, /etc/zshrc, /etc/zlogin en /etc/zlogout (Zsh manual).

zsh -f start met RCS unset en slaat daarom normaal gesproken de bovenstaande $ZDOTDIR user files over; /etc/zshenv wordt altijd gelezen en kan die option wijzigen.

Zsh startup sequence voor vier shell modesZsh startup sequence voor vier shell modes

.zprofile wordt eenmaal per login-shell process uitgevoerd, niet eenmaal per login op de computer. Als elk nieuw terminaltabblad een login shell start, leest elk tabblad het bestand.

Wat hoort in elk bestand?

FileSelectieregelGoede kandidatenVermijden
.zshenvElke Zsh met RCS ingesteldZeldzame variables die elk Zsh-process nodig heeftoutput, aliases, prompts, network calls, trage commands
.zprofileLogin shellspath setup voor de login session en geëxporteerde defaultsinteractive hooks, completions, key bindings
.zshrcInteractive shellsprompt, completion, aliases, shell options, interactive tool hooksoutput of mutations die voor scripts bedoeld zijn
.zloginLogin shells, na .zshrczeldzame post-interactive login actionsconfiguration die vóór .zshrc moet worden uitgevoerd
.zlogoutAfsluiten van een login shellkleine cleanup- of terminal-resetactiesbelangrijke state die crashes of exec moet overleven

Houd .zshenv saai wanneer RCS is ingesteld

Wanneer RCS is ingesteld, leest elke non-interactive zsh -c .zshenv (Zsh manual). Een echo, package-manager call of dure subprocess daar kan command output beschadigen en scripts vertragen. De meeste gebruikers hebben weinig tot niets in dit bestand nodig.

Een geëxporteerde variable in .zprofile wordt geërfd door children van die login shell. De variable wordt niet automatisch geïnjecteerd in niet-gerelateerde GUI-applicaties, services of shells waarvan de parent het bestand nooit heeft gelezen.

Gebruik .zprofile voor login-only setup

Een klein profile kan geëxporteerde defaults en een idempotent path instellen:

# ~/.zprofile
export EDITOR=nvim
export VISUAL=nvim

# Zsh ties the path array to the PATH scalar.
typeset -U path PATH
path=("$HOME/.local/bin" $path)
export PATH

typeset -U verwijdert dubbele array entries. Dat maakt herhaald sourcen veiliger, al moeten startup files nog steeds onnodig werk vermijden.

Gebruik .zshrc voor interactive state

# ~/.zshrc
setopt auto_cd hist_ignore_all_dups share_history

autoload -Uz compinit
compinit

alias ll='ls -lah'
bindkey -e

Aliases, completion widgets, key maps, prompts en directory-change hooks horen bij de interactive shell die ze gebruikt.

Version managers overschrijden de eenvoudige bestandsgrens

“Plaats version managers in .zprofile omdat ze traag zijn” is onbetrouwbaar advies. Een version manager kan verschillende soorten code genereren:

  • een statisch path of root variable
  • een command-shim path dat child processes nodig hebben
  • completion definitions
  • shell functions die de huidige shell wijzigen
  • directory-change hooks voor automatisch wisselen van versions

De interactive onderdelen moeten in elke interactive shell aanwezig zijn, inclusief een geneste zsh die vanuit een editor wordt gestart. De actuele fnm Zsh setup plaatst deze hook daarom in .zshrc:

eval "$(fnm env --use-on-cd --shell zsh)"

De actuele pyenv Zsh setup plaatst volledige interactive initialisatie eveneens in .zshrc:

export PYENV_ROOT="$HOME/.pyenv"
[[ -d $PYENV_ROOT/bin ]] && path=("$PYENV_ROOT/bin" $path)
eval "$(pyenv init - zsh)"

Pyenv documenteert een beperktere pyenv init --path-mode voor shim-path setup, terwijl volledige pyenv init ook completion en shell functions installeert. Inspecteer de actuele documentatie en gegenereerde output van een tool voordat je deze opsplitst.

Als startup traag is, meet dit dan eerst voordat je code verplaatst naar een mode waarin deze niet meer werkt.

Source niet het volledige profile vanuit .zshrc

Een veelgebruikte cross-platform workaround is:

# Avoid this blanket coupling.
source ~/.zprofile

Daarmee worden login-only actions per interactive shell uitgevoerd. Ook kunnen agent startup, keychain access, output of path mutation worden herhaald.

Als zowel login als non-login interactive shells een kleine set statische exports nodig hebben, haal dan een idempotent fragment uit de configuratie en source dat bewust:

# ~/.config/zsh/environment.zsh
typeset -U path PATH
path=("$HOME/.local/bin" $path)
export PATH EDITOR=nvim VISUAL=nvim
# ~/.zprofile
source "$HOME/.config/zsh/environment.zsh"
# ~/.zshrc
if [[ ! -o login ]]; then
  source "$HOME/.config/zsh/environment.zsh"
fi

# Interactive-only configuration follows.

Dit pattern houdt gedeelde data gescheiden van login side effects. Een andere geldige keuze is om de terminal- of session manager consistent te configureren in plaats van beide modes te ondersteunen.

Diagnoseer de shell die traag is of state mist

Reproduceer elke mode direct:

zsh -lic 'print "login interactive"'
zsh -ic  'print "non-login interactive"'
zsh -lc  'print "login non-interactive"'
zsh -c   'print "non-login non-interactive"'

Meet de mode die gebruikers daadwerkelijk starten:

time zsh -lic exit
time zsh -ic exit

Trace file- en line-execution wanneer niet duidelijk is welke source wordt uitgevoerd:

PS4='%N:%i> ' zsh -xlic exit

Voer de trace uit in een schoon testaccount of inspecteer deze voordat je hem deelt: startup commands kunnen tokens en andere gevoelige waarden expanderen.

Bij een ontbrekende executable inspecteer je zowel de mode als het path:

print -r -- "interactive=$options[interactive] login=$options[login]"
print -l -- $path
whence -va python node uv

Conclusie

Het relevante onderscheid is niet “environment versus aliases”. Het gaat om login-only state versus interactive state, plus de kleine hoeveelheid die elk Zsh-process moet beïnvloeden wanneer RCS is ingesteld.

Controleer de shell flags, houd startup files stil en plaats elke gegenereerde hook waar alle vereiste features beschikbaar zijn. Zo blijft de configuratie portable tussen terminalapplicaties, zonder te doen alsof macOS en Linux elk één vaste launch mode hebben.

Referenties