Bash-Startdateien: Ladereihenfolge von profile und bashrc
AI generated
$
/etc
Linux · Bash · Shell-Konfiguration
Bash-Startdateien: die Ladereihenfolge verstehen
von /etc/profile bis zur eigenen .bashrc

Wer schon einmal eine Umgebungsvariable in einer SSH-Sitzung gesetzt hat, die im Cron-Job plötzlich fehlte, kennt das Problem: Bash lädt je nach Kontext völlig unterschiedliche Startdateien. Die Ladereihenfolge von profile, bash_profile und bashrc entscheidet, welche Konfiguration überhaupt wirksam wird, und genau dieses Verständnis fehlt vielen Administratoren.

14 Min. Lesezeit Login-Shell · Non-Login-Shell · profile · bashrc Bash 4.x · 5.x · Debian · Ubuntu · RHEL

1. Warum die Ladereihenfolge so oft verwirrt

Kaum ein Thema unter Linux sorgt so zuverlässig für Verwirrung wie die Ladereihenfolge der Bash-Startdateien. Ein Administrator setzt eine Umgebungsvariable in der .bashrc, öffnet ein neues Terminal, und die Variable ist da. Derselbe Cron-Job, der dieselbe Variable braucht, scheitert am nächsten Morgen mit einer kryptischen Fehlermeldung. Der Grund ist simpel und gleichzeitig selten dokumentiert: Bash entscheidet anhand des Aufrufkontexts, welche Datei sie überhaupt liest, und Cron ruft die Shell auf eine Art auf, die .bashrc komplett ignoriert.

Die Ladereihenfolge ist keine willkürliche Designentscheidung, sondern folgt einer klaren Logik, die sich an zwei Fragen orientiert: Ist die Shell eine Login-Shell, und ist sie interaktiv. Aus der Kombination dieser beiden Eigenschaften ergibt sich exakt, welche Dateien in welcher Reihenfolge eingelesen werden. Wer diese Logik einmal verinnerlicht hat, findet Konfigurationsfehler in Sekunden, statt stundenlang zu raten, warum eine Variable "manchmal" da ist und manchmal nicht.

2. Login-Shell und Non-Login-Shell: der zentrale Unterschied

Eine Login-Shell entsteht, wenn man sich frisch am System anmeldet: über eine physische Konsole, per ssh benutzer@server ohne Kommando, oder mit bash --login. Eine Non-Login-Shell entsteht dagegen, wenn man aus einer bereits laufenden Sitzung heraus ein neues Terminal öffnet, etwa in einem grafischen Desktop, oder wenn man bash ohne weitere Optionen aufruft. Diese Unterscheidung ist der wichtigste Hebel für die gesamte Ladereihenfolge, denn Login-Shells und Non-Login-Shells lesen komplett unterschiedliche Dateisätze.

Eine zweite, unabhängige Achse ist die Frage, ob eine Shell interaktiv ist, also mit einem Prompt auf Eingaben wartet, oder nicht interaktiv läuft, wie es bei Skripten, Cron-Jobs und systemd-Diensten der Fall ist. Zusammen ergeben Login-Status und Interaktivität vier mögliche Kombinationen, und für jede dieser vier Kombinationen liest Bash einen anderen Satz an Startdateien. Genau hier liegt die Ursache, warum ein Skript, das per Cron läuft, andere Umgebungsvariablen sieht als dasselbe Skript, wenn man es manuell im Terminal startet.


# Determine whether the current shell is a login shell
shopt -q login_shell && echo "login shell" || echo "not a login shell"

# Determine whether the current shell is interactive
case $- in
  *i*) echo "interactive" ;;
  *)   echo "not interactive" ;;
esac

# Combine both checks in a small diagnostic script
cat <<'EOF' > /tmp/shell-context.sh
#!/usr/bin/env bash
if shopt -q login_shell; then
  login="login"
else
  login="non-login"
fi
case $- in
  *i*) interactive="interactive" ;;
  *)   interactive="non-interactive" ;;
esac
echo "Context: $login, $interactive shell (PID $$)"
EOF
chmod +x /tmp/shell-context.sh

3. /etc/profile und /etc/profile.d: die systemweite Ebene

Bei jeder Login-Shell liest Bash zuerst die systemweite Datei /etc/profile, bevor überhaupt eine benutzerspezifische Datei zum Zug kommt. Diese Datei ist der richtige Ort für Einstellungen, die für alle Benutzer eines Servers gelten sollen, etwa einen systemweiten PATH-Zusatz oder eine globale Umask. Auf den meisten modernen Distributionen bindet /etc/profile zusätzlich alle Dateien aus dem Verzeichnis /etc/profile.d/*.sh ein, was die Wartung erheblich vereinfacht, weil jedes Paket seine eigene Konfigurationsdatei ablegen kann, ohne die zentrale Datei zu verändern.

Diese modulare Struktur erklärt, warum etwa nach der Installation von rvm oder einer PHP-Version plötzlich neue Umgebungsvariablen in jeder Login-Shell verfügbar sind: Das Installationsskript legt einfach eine neue Datei in /etc/profile.d/ ab. Wichtig ist, dass /etc/profile nur bei Login-Shells gelesen wird. Öffnet man in einem laufenden Desktop nur ein neues Terminalfenster, wird diese Datei in aller Regel nicht erneut ausgeführt, denn dieses neue Fenster ist typischerweise keine Login-Shell.

4. Die Login-Kette: bash_profile, bash_login und profile

Nach /etc/profile sucht Bash in einer Login-Shell nach genau einer der folgenden drei Dateien im Home-Verzeichnis des Benutzers, und zwar in dieser festen Reihenfolge: zuerst ~/.bash_profile, dann ~/.bash_login, und erst wenn keine der beiden existiert, ~/.profile. Sobald eine dieser Dateien gefunden wird, liest Bash nur diese eine und ignoriert die restlichen. Das führt in der Praxis oft zu einem Fallstrick: Legt man eine ~/.bash_profile an, wird eine bereits vorhandene ~/.profile von Bash komplett übergangen, was Einstellungen aus dieser Datei unwirksam macht, ohne dass eine Fehlermeldung erscheint.

Die gängige Praxis auf den meisten Systemen ist deshalb, gar keine eigene ~/.bash_profile anzulegen, sondern in ~/.profile zu arbeiten, weil diese Datei zusätzlich von POSIX-kompatiblen Shells wie dash gelesen wird. Wer Bash-spezifische Konfiguration in der Login-Kette benötigt, legt stattdessen eine minimale ~/.bash_profile an, die lediglich ~/.bashrc nachlädt. Damit greifen Login-Shell und Non-Login-Shell auf dieselbe zentrale Konfigurationsdatei zu, und die Wartung bleibt an einer einzigen Stelle gebündelt.


# ~/.bash_profile — minimal pattern: delegate to .bashrc
# Ensures a login shell picks up the same interactive settings
if [ -f "$HOME/.bashrc" ]; then
  source "$HOME/.bashrc"
fi

# Login-only additions still belong here, not in .bashrc
export PATH="$HOME/.local/bin:$PATH"
umask 022

5. .bashrc: die interaktive Non-Login-Shell

Die Datei ~/.bashrc ist ausschließlich für interaktive Non-Login-Shells zuständig, also für neue Terminalfenster, Tabs in einem Terminal-Multiplexer oder Sitzungen, die aus einer bereits laufenden Shell heraus mit bash gestartet werden. Hier gehören Aliase, die Prompt-Konfiguration (PS1), die History-Einstellungen und Shell-Optionen wie shopt hin, weil diese Dinge nur in interaktiven Sitzungen überhaupt einen Effekt haben. Umgebungsvariablen, die auch in nicht interaktiven Kontexten verfügbar sein müssen, sollten hingegen nicht ausschließlich in .bashrc gesetzt werden.

Ein verbreitetes Missverständnis ist die Annahme, .bashrc würde bei jeder Bash-Sitzung ausnahmslos gelesen. Tatsächlich wird sie bei einer reinen Login-Shell ohne den oben gezeigten Nachlade-Trick gar nicht automatisch geladen, was erklärt, warum Aliase, die man in .bashrc definiert hat, nach einem frischen SSH-Login manchmal fehlen, aber nach dem Öffnen eines zusätzlichen Terminals plötzlich vorhanden sind.

6. Non-interaktive Shells und BASH_ENV

Für nicht interaktive Shells, also solche, die ein Skript ausführen, ohne auf einen Prompt zu warten, liest Bash standardmäßig gar keine der bisher genannten Dateien. Stattdessen prüft Bash die Umgebungsvariable BASH_ENV, und falls diese gesetzt ist, wird die dort referenzierte Datei geladen, bevor das eigentliche Skript ausgeführt wird. Diese Variable ist der einzige offizielle Hebel, um nicht interaktiven Shells eine gemeinsame Konfiguration mitzugeben, wird in der Praxis aber selten genutzt, weil viele Administratoren ihre Variablen stattdessen explizit im Skript selbst setzen.

Wichtig für die Fehlersuche: BASH_ENV wird nur von Bash selbst ausgewertet, nicht von sh oder anderen POSIX-Shells, und sie greift nur bei nicht interaktiven Shells, niemals bei interaktiven Login- oder Non-Login-Shells. Wer also erwartet, dass eine über Cron gestartete Variable auch in der interaktiven Shell des Benutzers auftaucht, verwechselt zwei völlig getrennte Ladepfade.


# Set BASH_ENV so non-interactive scripts pick up shared config
export BASH_ENV="/etc/bash_env_common.sh"

# /etc/bash_env_common.sh — loaded before every non-interactive script
export PHP_INI_SCAN_DIR="/etc/php/8.4/custom.d"
export MAGENTO_MODE="production"

# Verify the effect from a non-interactive invocation
bash -c 'echo "MAGENTO_MODE inside non-interactive shell: $MAGENTO_MODE"'

7. SSH, Cron und systemd in der Praxis

Eine SSH-Sitzung mit ssh benutzer@server ohne angehängtes Kommando erzeugt eine interaktive Login-Shell, die also /etc/profile, danach die Login-Kette und je nach Nachlade-Trick auch .bashrc durchläuft. Führt man dagegen ssh benutzer@server 'befehl' mit einem direkt angehängten Kommando aus, entsteht eine nicht interaktive Non-Login-Shell, die standardmäßig gar keine der genannten Dateien liest, sondern nur BASH_ENV auswertet. Das erklärt, warum ferngesteuerte Deployment-Skripte oft mit einem leeren PATH zu kämpfen haben, obwohl im interaktiven SSH alles funktioniert.

Cron-Jobs laufen grundsätzlich als nicht interaktive, nicht anmeldende Shells mit einer sehr minimalen Umgebung, meist nur SHELL, PATH und HOME. systemd-Dienste sind noch strikter: Sie erben überhaupt keine Shell-Startdateien, sondern beziehen ihre Umgebung ausschließlich aus Environment= und EnvironmentFile= in der Unit-Datei. Wer also PHP-Umgebungsvariablen für einen Magento-Cron-Job oder einen systemd-Timer benötigt, muss diese explizit im Job selbst oder in der Unit-Datei setzen, statt sich auf .bashrc zu verlassen.

8. Typische Fehler: verlorene PATH-Einträge und doppelte Ausgaben

Der häufigste Fehler in der Praxis ist, eine benötigte Umgebungsvariable ausschließlich in .bashrc zu definieren und dann davon auszugehen, dass sie auch in einem Cron-Job oder einem SSH-Einzeilenbefehl verfügbar ist. Die zweithäufigste Fehlerquelle sind Ausgaben mit echo in .bashrc oder .bash_profile, etwa für ein Willkommensbanner: In nicht interaktiven Kontexten führt so etwas dazu, dass ein Skript, das die Ausgabe eines SSH-Kommandos parst, plötzlich unerwartete Textzeilen vor dem eigentlichen Ergebnis erhält, weil die Startdatei mitgelesen wurde.

Ein dritter klassischer Fehler betrifft doppeltes Anhängen an PATH. Lädt ~/.bash_profile zusätzlich ~/.bashrc nach, und beide Dateien hängen denselben Pfad an PATH an, wächst die Variable bei jedem neuen Terminal um einen weiteren doppelten Eintrag. Die robuste Lösung ist eine Prüfung, ob der Pfad bereits enthalten ist, bevor er erneut angehängt wird, statt blind zu konkatenieren.


# Idempotent PATH extension — avoids duplicate entries on repeated sourcing
add_to_path() {
  local new_path="$1"
  case ":$PATH:" in
    *":$new_path:"*) ;;                 # already present, do nothing
    *) PATH="$new_path:$PATH" ;;        # not present, prepend once
  esac
}

add_to_path "$HOME/.local/bin"
add_to_path "/usr/local/php8.4/bin"
export PATH

9. Ladereihenfolge debuggen und Vergleichstabelle

Um die tatsächlich geladene Reihenfolge nachzuvollziehen, hilft der Aufruf bash -x -l -c 'exit', der jede eingelesene Datei mit dem Trace-Modus sichtbar macht. Alternativ setzt man in jeder verdächtigen Startdatei eine eigene echo-Zeile mit dem Dateinamen an den Anfang, startet eine neue Sitzung im jeweiligen Kontext und liest die Reihenfolge der Ausgaben ab. Für SSH-Kommandos eignet sich ssh benutzer@server 'echo $0; echo $-', um zu prüfen, ob die entstandene Shell interaktiv war oder nicht.

Kontext Login-Shell Interaktiv Gelesene Dateien
SSH ohne Kommando Ja Ja /etc/profile, bash_profile/bash_login/profile
SSH mit Kommando Nein Nein nur BASH_ENV, falls gesetzt
Neues Terminalfenster Nein Ja ~/.bashrc
Cron-Job Nein Nein nur BASH_ENV, minimale Umgebung
systemd-Service Nein Nein keine, nur Unit-Datei-Environment

Diese Tabelle macht deutlich, warum eine zentrale, von .bash_profile nachgeladene .bashrc zusammen mit einer sauber gepflegten BASH_ENV-Datei die robusteste Kombination ist. Statt Variablen an mehreren Stellen redundant zu pflegen, definiert man sie einmal in einer gemeinsamen Datei und bindet diese über die jeweils passenden Mechanismen ein.

Mironsoft

Linux-Serveradministration und Shell-Umgebungen für PHP-Hosting

Umgebungsvariablen, die dort ankommen, wo sie gebraucht werden?

Wir prüfen eure Shell-Konfiguration über SSH, Cron und systemd hinweg, beheben inkonsistente Ladereihenfolgen und sorgen dafür, dass Deployment-Skripte und Cron-Jobs zuverlässig dieselbe Umgebung sehen.

Konfigurations-Audit

Analyse aller Startdateien und ihrer tatsächlichen Ladereihenfolge im Betrieb

Bereinigung

Konsolidierung redundanter PATH-Einträge und widersprüchlicher Variablen

Deployment-Absicherung

Konsistente Umgebung für Cron, systemd-Timer und CI/CD-Pipelines

10. Zusammenfassung

Die Ladereihenfolge der Bash-Startdateien folgt zwei Achsen: Login-Status und Interaktivität. Login-Shells lesen zuerst /etc/profile, danach genau eine der Dateien ~/.bash_profile, ~/.bash_login oder ~/.profile. Interaktive Non-Login-Shells lesen ausschließlich ~/.bashrc. Nicht interaktive Shells lesen standardmäßig gar nichts außer der über BASH_ENV referenzierten Datei. Cron-Jobs und systemd-Dienste folgen eigenen, noch strikteren Regeln und erhalten ihre Umgebung nicht aus den klassischen Startdateien.

Wer eine robuste, wartbare Konfiguration will, lässt ~/.bash_profile die ~/.bashrc nachladen, pflegt gemeinsame Umgebungsvariablen zentral über BASH_ENV oder /etc/profile.d/, und verzichtet auf Ausgaben in Startdateien, die auch von nicht interaktiven Kontexten erreicht werden könnten. Diese wenigen Regeln beseitigen den Großteil der Verwirrung rund um verschwundene Umgebungsvariablen im Serveralltag.

Bash-Ladereihenfolge: Das Wichtigste auf einen Blick

Login-Shell

/etc/profile, dann genau eine von bash_profile, bash_login oder profile. Nur bei frischem Login oder bash --login.

Interaktive Non-Login-Shell

Ausschließlich ~/.bashrc. Betrifft neue Terminalfenster und verschachtelte interaktive Shells.

Nicht interaktive Shell

Standardmäßig keine Datei, nur BASH_ENV falls gesetzt. Betrifft Skript-Ausführungen und einzeilige SSH-Kommandos.

Cron & systemd

Eigene, minimale Umgebung. Cron nutzt keine Startdateien, systemd-Units nur Environment= und EnvironmentFile=.

11. FAQ: Bash-Startdateien und Ladereihenfolge

1Unterschied bash_profile und bashrc?
bash_profile nur bei Login-Shells, bashrc nur bei interaktiven Non-Login-Shells. Beide laufen normalerweise nicht gemeinsam.
2Warum wird .profile ignoriert?
Bash liest nur die erste existierende Datei aus bash_profile, bash_login, profile. Existiert bash_profile, wird profile komplett übersprungen.
3Andere Variablen im Cron-Job?
Cron startet nicht interaktive, nicht anmeldende Shells mit minimaler Umgebung. Weder profile noch bashrc werden gelesen.
4Was macht BASH_ENV?
Verweist auf eine Datei, die Bash bei nicht interaktiven Shells vor dem Skript lädt. Nur von Bash ausgewertet, nur bei nicht interaktiven Aufrufen.
5Warum kein echo in Startdateien?
In nicht interaktiven Kontexten landet die Ausgabe vor dem eigentlichen Ergebnis und kann Skripte stören, die die Ausgabe maschinell verarbeiten.
6Liest systemd Bash-Startdateien?
Nein. systemd-Services erben keine Bash-Startdateien, die Umgebung kommt nur aus Environment= und EnvironmentFile= in der Unit-Datei.
7Wie geladene Dateien finden?
bash -x -l -c 'exit' zeigt jede Datei im Trace-Modus. Alternativ eigene echo-Zeile mit Dateinamen pro Startdatei einbauen.
8Empfohlener Aufbau?
Minimale bash_profile, die bashrc nachlädt, plus zentrale bashrc. Gemeinsame Variablen über BASH_ENV oder /etc/profile.d/.
9Warum verdoppelt sich PATH?
Lädt bash_profile zusätzlich bashrc nach und beide hängen denselben Pfad an, verdoppelt sich der Eintrag. Eine Prüfung vor dem Anhängen verhindert das.
10Gilt das auch für zsh oder dash?
Nein, jede Shell hat eigene Startdateien. zsh nutzt zshenv, zprofile, zshrc, zlogin. Die beschriebene Reihenfolge gilt spezifisch für Bash.