Zsh-Startup-Dateien: .zprofile vs. .zshrc unter macOS und Linux
Automatische Übersetzung
Dieser Artikel wurde automatisch aus der englischen Originalversion übersetzt.
Wenn sich dein Terminal langsam anfühlt oder eine Umgebungsvariable in einem Terminal erscheint, in einem anderen aber nicht, liegt die Ursache oft im Startmodus von Zsh. Die beiden Dateien, zu denen die meisten greifen, sind ~/.zprofile und ~/.zshrc, doch keiner der beiden Dateinamen verrät, ob eine Zeile dorthin gehört. Dieser Artikel zeigt, welche Startup-Dateien welche Art von Zsh liest, was in welche Datei gehört und mit welchen Befehlen du herausfindest, in welcher Art von Shell du dich befindest.
Zsh wählt die Startup-Dateien anhand von zwei unabhängigen Eigenschaften: ob die Shell eine Login-Shell ist und ob sie interaktiv ist. Terminal-Anwendungen unter macOS und Linux können unterschiedliche Kombinationen wählen. Prüfe deshalb die laufende Shell, statt anzunehmen, dass das Betriebssystem es für dich entscheidet.
Eine interaktive Shell dient der Eingabe von Befehlen, meist in einem Terminal; -i kann diesen Modus erzwingen. Eine Login-Shell startet im Login-Modus, den ihr Startprogramm oder -l auswählt. Das bedeutet nicht, dass die Shell nur bei der Anmeldung am Computer startet (Zsh-Optionen zum Shell-Zustand).
Zsh wertet zwei unabhängige Flags aus
Eine Shell kann sein:
- Login und interaktiv, etwa ein neues macOS-Terminal-Fenster oder
zsh -l - Non-Login und interaktiv, etwa eine in einer anderen Shell eingetippte verschachtelte
zsh - Login und nicht interaktiv, etwa
zsh -lc 'command' - Non-Login und nicht interaktiv, etwa
zsh -c 'command'
Das Diagramm legt die beiden Flags auf zwei Achsen. Jede Zelle nennt die Programme, die diese Art von Shell meist starten, und markiert, welche der fünf Benutzerdateien sie liest.
Das Programm, das die Shell startet, legt den Modus fest. Das sind die Standardwerte gängiger Launcher:
| Gestartet durch | Standardmodus |
|---|---|
| macOS-Terminal, iTerm2 | Login, interaktiv |
| GNOME Terminal | Non-Login, interaktiv |
| VS-Code-Terminal unter macOS | Login, interaktiv (zsh -l) |
| VS-Code-Terminal unter Linux | Non-Login, interaktiv |
ssh host | Login, interaktiv |
ssh host 'command' | Non-Login, nicht interaktiv |
neues tmux-Fenster bei leerem default-command | Login, interaktiv |
in einer Shell eingetipptes zsh | Non-Login, interaktiv |
zsh script.zsh | Non-Login, nicht interaktiv |
Deine Einstellungen können jede Zeile ändern. Den tatsächlichen Modus der aktuellen Shell zeigt dir dieser Befehl:
print -r -- "interactive=$options[interactive] login=$options[login]"
Die tatsächliche Startreihenfolge
Mit den standardmäßigen Startoptionen liest Zsh zuerst globale Dateien und dann die zugehörige Benutzerdatei. Die Reihenfolge der Benutzerdateien ist:
$ZDOTDIR/.zshenvbei jedem Zsh-Aufruf$ZDOTDIR/.zprofilewenn die Shell eine Login-Shell ist$ZDOTDIR/.zshrcwenn die Shell interaktiv ist$ZDOTDIR/.zloginwenn die Shell eine Login-Shell ist$ZDOTDIR/.zlogoutwenn eine interaktive Login-Shell normal beendet wird
Ist ZDOTDIR nicht gesetzt, verwendet Zsh $HOME. Die systemweiten Dateipfade hängen von der Installation ab; üblich sind /etc/zshenv, /etc/zprofile, /etc/zshrc, /etc/zlogin und /etc/zlogout (Zsh-Handbuch).
Unter macOS führt /etc/zprofile path_helper aus, das die Systemverzeichnisse an den Anfang von PATH stellt. Setze eigene PATH-Einträge in ~/.zprofile, das danach läuft, und nicht in ~/.zshenv.
RCS ist eine standardmäßig aktive Zsh-Option, die Zsh nach /etc/zshenv weitere Startup-Dateien lesen lässt. zsh -f schaltet sie aus und überspringt gewöhnlich die übrigen Startup-Dateien. /etc/zshenv läuft weiterhin und kann RCS wieder einschalten. Verschwindet ein Problem dort, untersuche die übersprungene Startkonfiguration (Zsh-Handbuch).
Das nächste Diagramm zeigt die vollständige Reihenfolge für ein neues Terminal-Fenster unter macOS, globale Dateien eingeschlossen, und was mit einem PATH-Eintrag passiert, der in ~/.zshenv statt in ~/.zprofile ergänzt wird.
.zprofile läuft einmal pro Login-Shell-Prozess, nicht einmal pro Anmeldung am Rechner. Startet jeder neue Terminal-Tab eine Login-Shell, liest jeder Tab die Datei.
Was in welche Datei gehört
| Datei | Auswahlregel | Gute Kandidaten | Vermeiden |
|---|---|---|---|
.zshenv | Jede Zsh | ZDOTDIR und seltene Variablen, die jeder Zsh-Prozess braucht | Ausgaben, Aliase, Prompts, Netzwerkaufrufe, langsame Befehle |
.zprofile | Login-Shells | Pfad-Setup der Login-Session und exportierte Standardwerte | interaktive Hooks, Completions, Tastenbelegungen |
.zshrc | Interaktive Shells | Prompt, Completion, Aliase, Shell-Optionen, interaktive Tool-Hooks | Ausgaben oder Änderungen, die für Skripte gedacht sind |
.zlogin | Login-Shells, nach .zshrc | seltene Login-Aktionen, die nach .zshrc laufen müssen | Konfiguration, die vor .zshrc stehen muss |
.zlogout | Ende einer Login-Shell | kleine Aufräum- oder Terminal-Reset-Aktionen | wichtiger Zustand, der Abstürze oder exec überstehen muss |
.zshenv kurz und still halten
Jede nicht interaktive zsh -c liest .zshenv (Zsh-Handbuch). Ein echo, ein Aufruf des Paketmanagers oder ein aufwendiger Subprozess dort kann die Ausgabe von Befehlen verfälschen und Skripte verlangsamen. Die meisten brauchen in dieser Datei wenig oder nichts.
Die übliche Ausnahme ist ZDOTDIR. Setze es in ~/.zshenv, um die übrigen Startup-Dateien in einem anderen Verzeichnis wie ~/.config/zsh zu halten.
.zprofile für Setup nur beim Login nutzen
Eine in .zprofile exportierte Variable erben die Kindprozesse dieser Login-Shell. Sie wird nicht automatisch in unabhängige GUI-Anwendungen, Dienste oder Shells übernommen, deren Elternprozess die Datei nie gelesen hat.
Ein kleines Profil unter macOS könnte Homebrew laden, exportierte Standardwerte setzen und PATH ohne Duplikate aufbauen:
# ~/.zprofile
# The Homebrew installer recommends this line on Apple silicon.
eval "$(/opt/homebrew/bin/brew shellenv zsh)"
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 behält jeden Eintrag nur einmal. Ohne diese Option fügt jedes source der Datei und jede verschachtelte Login-Shell dasselbe Verzeichnis erneut hinzu, und echo $PATH zeigt es mehrfach.
.zshrc für interaktiven Zustand nutzen
Ein neuer zsh-Aufruf erbt exportierte Variablen. Die Aliase, Funktionen, Shell-Optionen, Tastenbelegungen und Hooks der Eltern-Shell erbt er nicht. Jede interaktive Shell muss sie neu definieren, und genau das tut .zshrc. Bei gewöhnlichen nicht interaktiven Aufrufen überspringt Zsh .zshrc.
Das Diagramm vergleicht eine verschachtelte zsh und eine zsh -c-Skript-Shell, die aus derselben Login-Shell gestartet werden. In diesem Beispiel definiert nur .zshrc den gezeigten Shell-Zustand.
Eine minimale .zshrc setzt Optionen, Completion, Aliase und Tastenbelegungen:
# ~/.zshrc
setopt auto_cd hist_ignore_all_dups share_history
autoload -Uz compinit
compinit
alias ll='ls -lah'
bindkey -e
Wohin die Hooks von Version-Managern gehören
„Leg Version-Manager in .zprofile, weil sie langsam sind“ ist ein unzuverlässiger Rat. Ein Version-Manager kann mehrere Arten von Code ausgeben:
- einen statischen Pfad oder eine Root-Variable
- einen Shim-Pfad für Befehle, den Kindprozesse brauchen
- Completion-Definitionen
- Shell-Funktionen, die die aktuelle Shell verändern
- Hooks beim Verzeichniswechsel für das automatische Umschalten der Version
Die ersten beiden werden exportiert und gehen an Kind-Shells. Der Rest ist Shell-Zustand und muss deshalb in jeder interaktiven Shell vorhanden sein, auch in einer verschachtelten zsh, die aus einem Editor startet. Das aktuelle fnm-Zsh-Setup legt diesen Hook daher in .zshrc:
eval "$(fnm env --use-on-cd --shell zsh)"
Das aktuelle pyenv-Zsh-Setup legt die vollständige interaktive Initialisierung ebenfalls in .zshrc:
export PYENV_ROOT="$HOME/.pyenv"
[[ -d $PYENV_ROOT/bin ]] && path=("$PYENV_ROOT/bin" $path)
eval "$(pyenv init - zsh)"
Pyenv dokumentiert einen engeren Modus pyenv init --path für das Shim-Pfad-Setup, während das vollständige pyenv init auch Completion und Shell-Funktionen installiert. Lies die aktuelle Dokumentation und die erzeugte Ausgabe eines Tools, bevor du es aufteilst.
Ist der Start langsam, miss ihn, bevor du Code in einen Modus verschiebst, in dem er nicht mehr funktioniert. Der Abschnitt zur Diagnose zeigt, wie.
Das ganze Profil nicht aus .zshrc einbinden
Ein verbreiteter plattformübergreifender Behelf ist:
# Avoid this blanket coupling.
source ~/.zprofile
Damit werden Aktionen, die nur beim Login laufen sollen, zu Aktionen für jede interaktive Shell. Das kann ssh-agent erneut starten, erneut nach Keychain-Zugriff fragen, Ausgaben erzeugen oder PATH-Einträge doppelt hinzufügen.
Brauchen sowohl Login-Shells als auch interaktive Non-Login-Shells einen kleinen Satz statischer Exporte, lagere ein idempotentes Fragment aus und binde es gezielt ein:
# ~/.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.
Eine Login-Shell liest environment.zsh über .zprofile. Eine interaktive Non-Login-Shell liest sie über .zshrc. Login-Aktionen wie ssh-agent bleiben in .zprofile, deshalb trennt dieses Muster gemeinsame Daten von den Nebenwirkungen des Logins. Eine andere Möglichkeit ist, dein Terminal so einzustellen, dass es eine Login-Shell startet, sodass jeder Tab .zprofile liest.
Die Shell diagnostizieren, die langsam ist oder Zustand vermissen lässt
Geh vom Symptom aus: eine langsame Shell oder eine fehlende Einstellung.
Reproduziere jeden Modus direkt:
zsh -lic 'print "login interactive"'
zsh -ic 'print "non-login interactive"'
zsh -lc 'print "login non-interactive"'
zsh -c 'print "non-login non-interactive"'
Bei einer langsamen Shell zeigt time die Gesamtzeit, zprof die Funktion, die die Zeit braucht, und der -x-Trace die Datei und Zeile, die gerade läuft.
Miss den Modus, den dein Terminal startet:
time zsh -lic exit
time zsh -ic exit
Lade den Profiler am Anfang von .zshrc und gib seinen Bericht am Ende aus. Die nächste neue Shell gibt eine nach Zeit sortierte Tabelle der Funktionen aus:
# first line of ~/.zshrc
zmodload zsh/zprof
# last line of ~/.zshrc
zprof
Verfolge die Ausführung nach Datei und Zeile, wenn die Quelle unklar ist:
PS4='%N:%i> ' zsh -xlic exit
Führe den Trace in einem sauberen Testaccount aus oder prüfe ihn, bevor du ihn teilst: Startbefehle können Tokens und andere sensible Werte ausgeben.
Bei einer fehlenden ausführbaren Datei prüfe sowohl den Modus als auch den Pfad:
print -r -- "interactive=$options[interactive] login=$options[login]"
print -l -- $path
whence -va python node uv
Um eine Änderung zu übernehmen, öffne einen neuen Tab oder führe exec zsh -l (Login) oder exec zsh (Non-Login) aus. source ~/.zshrc führt die Datei in der aktuellen Shell erneut aus und behält alles, was die alte Version gesetzt hat.
Fazit
Die nützliche Unterscheidung lautet nicht „Umgebung gegen Aliase“. Sie lautet: Zustand nur für den Login gegen interaktiven Zustand, plus die kleine Menge, die jeden Zsh-Prozess betreffen muss.
Verhält sich eine Shell falsch, führe den print-Befehl aus dem ersten Abschnitt aus und prüfe dann, ob die Einstellung in einer Datei steht, die dieser Modus liest.