- 1 Section
- 10 Lessons
- unbegrenzt
- Bash / Shell-Scripting10
- 1.1Was ist eine Shell? bash, sh, zsh im Vergleich
- 1.2Grundlegende Befehle: cd, ls, cp, mv, rm, grep
- 1.3Variablen, Eingabe und Ausgabe
- 1.4Kontrollstrukturen: if, case, for, while
- 1.5Funktionen und Argumente
- 1.6Fehlerbehandlung: exit codes, trap
- 1.7Textverarbeitung: sed, awk, cut, sort
- 1.8Dateisystem-Automation: find, xargs, cron
- 1.9Netzwerk-Scripting: curl, ssh, rsync
- 1.10Praxisprojekt: Systemstatus- und Backup-Script
Fehlerbehandlung: exit codes, trap
Selbst das perfekt geschriebene Bash-Skript hat irgendwann mit Fehlern zu tun: eine Datei fehlt, ein Server ist nicht erreichbar, die Festplatte ist voll. Anders als Python oder Java hat Bash keine Exceptions im klassischen Sinne. Stattdessen arbeitet alles über Exit-Codes: jeder Befehl liefert nach seiner Ausführung eine Zahl zurück. 0 bedeutet „alles gut", alles andere ist ein Fehler.
Was nach Steinzeit klingt, hat sich seit Jahrzehnten bewährt: dieses einfache System lässt sich beliebig kombinieren und ermöglicht robuste Skripte. Dazu kommen set-Optionen die Bash strenger machen, und trap für Cleanup-Aktionen vor dem Beenden. Mit diesen drei Werkzeugen werden deine Skripte produktionsreif.
1) Exit-Codes – die Sprache des Erfolgs
Jeder ausgeführte Befehl in Bash hinterlässt einen Zahlenwert: den Exit-Code. Über die Spezialvariable $? kannst du ihn lesen. Aber Vorsicht: $? gilt nur direkt nach dem Befehl – ein einziger weiterer Befehl überschreibt ihn.
2) Exit-Codes in Bedingungen
Was die Bedingung if BEFEHL in L4 macht: sie prüft den Exit-Code. 0 = wahr, alles andere = falsch. Genau umgekehrt zu Python wo 0 falsy ist! In Unix bedeutet 0 Erfolg:
3) exit und eigene Codes
Mit exit N beendest du dein Skript und gibst Code N zurück. Sehr wichtig in Skripten die von anderen Tools (z.B. CI/CD) aufgerufen werden – die schauen genau auf den Exit-Code um zu erkennen ob alles geklappt hat:
Dokumentations-Tipp: schreibe in einen Kommentar oben im Skript was welcher Exit-Code bedeutet. Andere (und dein zukünftiges Ich) werden es dir danken. Beispiel: # EXIT CODES: 0=ok, 2=args, 3=quelle fehlt, 4=tar-fehler.
4) Die set-Optionen: Bash strenger machen
Standardmäßig ist Bash sehr tolerant: ein fehlgeschlagener Befehl wird ignoriert, das Skript läuft einfach weiter. Das ist in der Praxis oft gefährlich – stell dir vor das Backup-Verzeichnis kann nicht erstellt werden, aber die Daten werden trotzdem hineinkopiert (also ins Nichts). Mit set machst du Bash strenger:
set +x.set -euo pipefail. Macht Bash so strikt wie eine „echte" Programmiersprache. Mit set +e kannst du temporär entschärfen wenn du einen Fehler erwartest und behandeln willst.5) set -e in der Praxis
Vergleich was passiert mit und ohne strikten Modus:
Das ist ein dramatisches aber nicht erfundenes Beispiel – „shell script vernichtete halbe Server-Festplatte" ist eine bekannte Folklore-Geschichte. set -euo pipefail ist eine billige Versicherung.
6) Wann set -e nicht ausreicht: explizite Prüfung
set -e ist nicht magisch – es greift nicht in allen Fällen. Probleme:
- In Pipes: nur der letzte Befehl der Pipe wird geprüft. Lösung:
set -o pipefaildazu. - In Bedingungen:
if befehl;– hier wird der Fehler erwartet, nicht abgebrochen. - Mit
||:befehl || alternative– auch hier wird der Fehler „eingefangen". - Subshells:
(...)haben eigenes Verhalten.
Für kritische Stellen lieber explizit prüfen:
7) trap: Cleanup vor dem Beenden
Was wenn dein Skript eine temporäre Datei anlegt – und mitten in der Ausführung mit Strg+C abgebrochen wird? Die temp-Datei bleibt liegen. Hier kommt trap ins Spiel: damit registrierst du einen „Aufräum-Befehl" der bei bestimmten Ereignissen ausgeführt wird.
trap 'BEFEHL' SIGNAL. Mehrere Signale: trap 'cleanup' INT TERM EXIT. EXIT ist ein „Pseudo-Signal" das immer beim normalen Beenden ausgelöst wird – darum ist es das meistgenutzte trap-Ziel. INT ist Strg+C. TERM ist kill PID. Trap entfernen mit trap - SIGNAL.8) Klassisches trap-Pattern: Temp-Datei aufräumen
Das wahrscheinlich häufigste trap-Pattern in echten Skripten:
Das ist „defensive Programmierung" in Bash. mktemp -d erstellt ein eindeutig benanntes Temp-Verzeichnis (wichtig: zwei parallel laufende Skripte stören sich nicht). Egal wie das Skript endet – cleanup wird ausgeführt. Vergleichbar mit finally in Python try/finally oder mit defer in Go.
9) Die wichtigsten Unix-Signale
Signale sind kurze „Nachrichten" die ein Prozess vom System bekommen kann. Über trap reagiert dein Skript darauf:
kill PID. Höflich, abfangbar.kill -9 stirbt sofort.kill -l. Wenn ein Server-Admin „kill -9" gegen dein Skript wirft, hast du keine Chance mehr aufzuräumen – darum sind Locks und Markierungs-Dateien auf der Festplatte zusätzlich sinnvoll. Mehr zu Prozessen und Signalen in K29 Linux-Server-Admin.10) Debug-Modus mit set -x
Wenn dein Skript sich falsch verhält und du nicht weißt warum: set -x aktiviert einen Debug-Modus der jeden Befehl vor der Ausführung zeigt:
Ausgabe sieht etwa so aus:
Jede Zeile mit + ist ein „Bash zeigt was es gerade tut". Sehr nützlich um zu sehen wie Variablen tatsächlich expandiert werden – oft anders als gedacht! Alternativ ohne ganzes Skript modifizieren: bash -x mein-skript.sh beim Aufruf.
11) ShellCheck – der Bash-Linter
Bevor du ein Bash-Skript live schaltest: jag es durch ShellCheck. Das ist ein statischer Analyzer der typische Fehler erkennt:
- Fehlende Quotes um Variablen
- Verwechslung von
=und==in[ ] - Nicht-gesetzte Variablen verwendet
- Veraltete Konstrukte (z.B. Backticks statt
$()) - Logikfehler in if-Bedingungen
Installation: apt install shellcheck (Linux) oder brew install shellcheck (Mac). Aufruf: shellcheck mein-skript.sh. Oder direkt im Browser unter shellcheck.net. Die meisten Editoren (VS Code) haben Plugins die ShellCheck im Hintergrund laufen lassen und Probleme markieren während du tippst. Jedes ernsthafte Bash-Projekt sollte ShellCheck verwenden – findet Bugs bevor sie in Produktion gehen.
12) Defensives Skript-Template
Eine Vorlage die du für jedes neue Bash-Skript verwenden kannst:
Zusammenfassung
Exit-Code = Zahl nach jedem Befehl. 0 = Erfolg, alles andere = Fehler. Über $? abrufen (direkt danach!). Eigene Codes mit exit N. set -e bricht bei jedem Fehler ab. set -u wirft Fehler bei nicht-gesetzten Variablen. set -o pipefail macht Pipes streng. Goldene Kombination: set -euo pipefail. set -x für Debug-Modus. trap 'cleanup' EXIT registriert Aufräum-Handler – läuft bei normalem Ende UND Abbruch. Wichtige Signale: SIGINT (Strg+C), SIGTERM (kill), SIGKILL (kill -9, nicht abfangbar). Defensive Skripte: set -euo pipefail + trap cleanup EXIT + frühe Argument-Validierung + shellcheck drüber laufen lassen.
