Automatische vertaling
Dit artikel is automatisch vertaald vanuit de oorspronkelijke Engelse versie.
Zsh-opstartbestanden: ~/.zprofile vs ~/.zshrc op macOS en Linux
Als je terminal traag aanvoelt, of je omgevingsvariabelen niet laden waar je ze verwacht, dan loop je waarschijnlijk tegen de opstartvolgorde van Zsh aan.
De scheiding tussen ~/.zprofile en ~/.zshrc is een van de meest voorkomende bronnen van verwarring wanneer je naar Zsh overstapt, vooral op macOS, waar de standaarden zich anders gedragen dan op Linux.
TL;DR
~/.zprofile is voor omgevingsinstellingen. Het draait één keer per login, wat op macOS betekent: één keer per terminaltab. Zet daar je PATH, EDITOR en versiebeheerders zoals fnm of pyenv.
~/.zshrc is voor interactieve configuratie. Het draait elke keer dat je een nieuwe shell start. Zet daar aliassen, prompt-thema's en toetsenbindingen.
De opstartflow van de shell
Om te weten waar je dingen moet neerzetten, moet je weten wanneer bestanden laden. Zsh heeft een specifieke hiërarchie.
Login- vs. interactieve shells
Een login-shell is de eerste shell die je krijgt na authenticatie. Op macOS is elke nieuwe terminaltab of elk nieuw terminalvenster standaard een login-shell. Op Linux start het openen van een terminal meestal juist een niet-login interactieve shell, en daar komt het grootste deel van de cross-platform verwarring vandaan.
Een interactieve shell is alles waarin je commando's kunt typen.
Dit is wat er daadwerkelijk gebeurt wanneer je een terminal opent op macOS:
De laadvolgorde
~/.zshenv(optioneel). Draait voor elke shell, inclusief niet-interactieve scripts. Zet hier geen output of zware logica in — dat kan scripts breken die je configuratie sourcen. Gebruik het alleen voor omgevingsvariabelen die echt overal moeten bestaan, wat voor de meeste mensen zeldzaam is.~/.zprofile. Draait alleen voor login-shells. Je setupfase.~/.zshrc. Draait voor interactieve shells. Je aanpassingsfase.~/.zlogin(optioneel). Draait helemaal aan het einde van het opstarten van een login-shell.
Wat hoort waar?
~/.zprofile: de omgevingslaag
Hier zet je de dingen op die elk ander proces erft — paden, variabelen, language version managers.
Wat hier thuishoort:
PATH-wijzigingen.- Omgevingsvariabelen:
EDITOR,LANG,GOPATH,JAVA_HOME. - Tool-initialisatie die de omgeving aanpast:
pyenv,rbenv,fnm,cargo.
Deze hoeven maar één keer berekend te worden. Als je ze in .zshrc zet, worden ze telkens opnieuw berekend wanneer je een subshell opent of een script draait, wat zowel tijd verspilt als dubbele entries in je PATH kan achterlaten.
# ~/.zprofile
# 1. Set up your PATH
# Local bin first so your tools override system ones
export PATH="$HOME/.local/bin:/opt/homebrew/bin:$PATH"
# 2. Global variables
export EDITOR="nvim"
export VISUAL="nvim"
export LANG="en_US.UTF-8"
# 3. Version managers (the heavy ones)
# Doing this here keeps shell startup fast
eval "$(fnm env --use-on-cd)"
eval "$(pyenv init -)"
~/.zshrc: de interactieve laag
Hier pas je de shell aan waarin je daadwerkelijk typt.
Wat hier thuishoort:
- Aliassen:
alias g='git'. - Je prompt: Starship, Powerlevel10k, Pure.
- Completions:
compinit. - Toetsenbindingen:
bindkey. - Shell-opties:
setopt autocd,setopt histignorealldups.
Deze zijn alleen relevant wanneer er een mens achter het toetsenbord zit. Een script dat op de achtergrond draait heeft je prompt of je git-aliassen niet nodig.
# ~/.zshrc
# 1. Prompt
autoload -Uz promptinit && promptinit
prompt pure
# 2. Aliases
alias ll='ls -lah'
alias g='git'
alias gs='git status'
# 3. Shell options
setopt autocd # cd by typing the directory name
setopt histignorealldups # skip duplicate history entries
setopt share_history # share history across tabs
# 4. Completions
autoload -Uz compinit && compinit
Waarom niet alles in één bestand zetten?
Je vraagt je misschien af: als .zprofile eerst draait, waarom zou je dan niet gewoon je aliassen daar neerzetten en .zshrc helemaal overslaan?
Het komt neer op overerving versus herdefinitie.
Omgevingsvariabelen worden geërfd
Wanneer je export EDITOR="vim" in een parent shell, erft elk child process dat: subshells, scripts, programma's. Je zet het één keer en het propageert door de boom naar beneden, daarom is .zprofile de juiste plek voor export.
Aliassen en functies niet
Aliassen zoals alias g='git' en shellfuncties zijn lokaal voor de huidige shell. Ze worden niet doorgegeven aan child shells.
Als je een alias definieert in .zprofile, bestaat die in je top-level login-shell. Op het moment dat je zsh typt om een subshell te starten, of een script draait, is die alias weg. Om overal aliassen te hebben, moet je ze in elke nieuwe interactieve shell opnieuw definiëren. Precies daarvoor is .zshrc.
Scripts hebben geen menselijke features nodig
Wanneer je een shellscript uitvoert (./deploy.sh), start het een nieuwe niet-interactieve shell. Het heeft je prompt niet nodig, het heeft je git-aliassen niet nodig, en het wil zeker niet wachten tot oh-my-zsh klaar is met laden. Door interactieve configuratie in .zshrc te houden, draaien scripts snel en schoon, zonder dat je persoonlijke aanpassingen erin lekken.
Veelvoorkomende valkuilen
nvm of pyenv in .zshrc zetten
Je opent een nieuwe terminaltab en het duurt twee of drie seconden voordat je iets kunt typen. Versiebeheerders hebben meestal zware initialisatie, en .zshrc draait elke keer opnieuw. Verplaats ze naar ~/.zprofile en de vertraging verdwijnt.
Een groeiende PATH
Je $PATH eindigt met dezelfde directories die vijf keer in de lijst staan. De oorzaak is bijna altijd export PATH="$HOME/bin:$PATH" in .zshrc: elke reload (source ~/.zshrc) of subshell voegt het pad opnieuw toe. Verplaats PATH-definities naar ~/.zprofile.
Herladen na wijzigingen
Wijzigingen in ~/.zprofile zijn niet van toepassing op je huidige shell, omdat .zprofile alleen bij login wordt gelezen. Je kunt de tab sluiten en een nieuwe openen (de makkelijkste optie) of source ~/.zprofile handmatig uitvoeren.
Voor .zshrc gewoon:
source ~/.zshrc
Een configuratie die werkt op macOS en Linux
Als je tussen machines wisselt (macOS op het werk, Linux thuis, of andersom), kom je een complicatie tegen. Linux-terminals starten vaak als niet-login shells, wat betekent dat ze ~/.zprofile helemaal overslaan.
De gebruikelijke workaround is om .zprofile te sourcen vanuit .zshrc wanneer het nog niet geladen is:
# ~/.zshrc
# On Linux/non-login shells, make sure the environment is set
if [[ -o interactive && ! -o login ]]; then
[[ -f ~/.zprofile ]] && source ~/.zprofile
fi
# ... rest of your interactive config
Samenvatting
| Bestand | Doel | Voorbeelden |
|---|---|---|
~/.zshenv |
Kritieke omgevingsvariabelen | ZDOTDIR (alleen voor gevorderde gebruikers) |
~/.zprofile |
Omgevingssetup | PATH, EDITOR, eval "$(pyenv init -)" |
~/.zshrc |
Interactieve configuratie | alias, prompt, bindkey, compinit |
Twee regels dekken het grootste deel hiervan. Variabelen worden geërfd door de procesboom naar beneden, dus export hoort in .zprofile. Aliassen en functies worden niet geërfd, dus die horen in .zshrc en moeten voor elke nieuwe interactieve shell opnieuw worden gedefinieerd. Als nieuwe tabs traag aanvoelen, is de boosdoener bijna altijd een zware pyenv init of nvm.sh in .zshrc in plaats van .zprofile.