Willkommen zum sechsten Artikel unserer technischen Wiki-Serie über Bash-Programmierung!
In den vorherigen Artikeln hast du gelernt, wie du Bash-Skripte erstellst, mit Variablen arbeitest und Kontrollstrukturen wie if-else und Schleifen verwendest. Heute lernen wir etwas sehr Wichtiges: Wie du Fehler in deinen Skripten findest und behandelst.
Warum ist das wichtig?
Stell dir vor, du hast ein Skript geschrieben, aber es funktioniert nicht wie erwartet. Oder noch schlimmer: Es läuft auf deinem Computer perfekt, aber wenn es jemand anders benutzt, treten Fehler auf. Solche Situationen sind normal in der Programmierung, und heute lernst du, wie du damit umgehst.
In diesem Artikel lernst du:
- Wie du häufige Fehler erkennst und vermeidest
- Wie du dein Skript Schritt für Schritt debuggst
- Wie du Fehlermeldungen richtig verwendest
- Wie du dein Skript robuster machst
Lass uns mit dem Grundlegendsten beginnen: Wie Bash mit Fehlern umgeht.
Grundlegende Fehlerbehandlung
Exit-Codes
In Bash zeigt jeder Befehl durch seinen Exit-Code an, ob er erfolgreich war oder nicht. Das ist wie ein Ampelsystem:
- Exit-Code 0 bedeutet „Erfolg“ (grüne Ampel)
- Jeder andere Exit-Code (1-255) bedeutet „Fehler“ (rote Ampel)
Exit-Codes überprüfen
#!/bin/bash
# Einen Befehl ausführen und Exit-Code prüfen
ls /existierender_ordner
if [ $? -eq 0 ]; then
echo "Der Befehl war erfolgreich"
else
echo "Der Befehl ist fehlgeschlagen"
fi
# Direkt im if-Statement prüfen
if ls /nicht_existierender_ordner; then
echo "Ordner gefunden"
else
echo "Ordner nicht gefunden"
fi
Eigene Exit-Codes verwenden
#!/bin/bash
pruefe_alter() {
local alter=$1
# Prüfe, ob eine Zahl eingegeben wurde
if ! [[ $alter =~ ^[0-9]+$ ]]; then
echo "Fehler: Bitte eine Zahl eingeben" >&2
return 1
fi
# Prüfe, ob das Alter realistisch ist
if [ $alter -lt 0 ] || [ $alter -gt 120 ]; then
echo "Fehler: Alter muss zwischen 0 und 120 liegen" >&2
return 2
fi
echo "Das eingegebene Alter ist gültig"
return 0
}
# Funktion testen
pruefe_alter "25" # Sollte erfolgreich sein
pruefe_alter "abc" # Sollte Fehler 1 zurückgeben
pruefe_alter "150" # Sollte Fehler 2 zurückgeben
Wichtig für Anfänger:
$?
enthält immer den Exit-Code des letzten Befehls- Fehlermeldungen sollten auf stderr (
>&2
) ausgegeben werden - Verwende unterschiedliche Exit-Codes für verschiedene Fehlerarten
- Dokumentiere die Bedeutung deiner Exit-Codes
Debugging-Techniken
Beim Debugging helfen dir verschiedene Werkzeuge, den Ablauf deines Skripts zu verfolgen und Fehler zu finden. Die wichtigsten Werkzeuge sind set -x
und set -e
.
Debug-Modus mit set -x
#!/bin/bash
# Debug-Modus einschalten
set -x
echo "Starte das Skript"
name="Max"
echo "Hallo $name"
# Debug-Modus ausschalten
set +x
echo "Debug-Modus ist jetzt aus"
Wenn du dieses Skript ausführst, siehst du jede Zeile, bevor sie ausgeführt wird. Das hilft dir zu verstehen, was dein Skript genau macht.
Fehler abfangen mit set -e
#!/bin/bash
# Skript beenden bei Fehlern
set -e
echo "Dieser Befehl funktioniert"
ls /existierender_ordner
echo "Dieser Befehl wird einen Fehler erzeugen"
ls /nicht_existierender_ordner
echo "Diese Zeile wird nie erreicht, weil das Skript vorher abbricht"
Debugging-Ausgaben einfügen
#!/bin/bash
# Debug-Funktion erstellen
debug() {
local nachricht="$1"
# Nur ausgeben wenn DEBUG=1 gesetzt ist
if [ "${DEBUG:-0}" -eq 1 ]; then
echo "[DEBUG] $nachricht" >&2
fi
}
# Beispielverwendung
DEBUG=1 # Debug-Ausgaben aktivieren
debug "Skript gestartet"
debug "Überprüfe Verzeichnis"
if [ -d "/tmp" ]; then
debug "Verzeichnis /tmp existiert"
else
debug "Verzeichnis /tmp fehlt"
fi
Wichtige Debug-Optionen:
set -x
: Zeigt jeden Befehl vor der Ausführungset -e
: Beendet das Skript bei Fehlernset -v
: Zeigt jede Zeile beim Einlesenset -u
: Fehler bei undefinierten Variablen
Logging implementieren
Ein gutes Logging-System hilft dir, nachzuvollziehen, was dein Skript macht und was schiefgegangen ist. Lass uns ein einfaches, aber effektives Logging-System aufbauen.
Grundlegendes Logging
#!/bin/bash
# Logging-Funktion erstellen
log() {
local level="$1"
local nachricht="$2"
local zeitstempel=$(date "+%Y-%m-%d %H:%M:%S")
echo "[$zeitstempel] [$level] $nachricht" >> "skript.log"
}
# Verschiedene Log-Level
log_info() {
log "INFO" "$1"
echo "INFO: $1"
}
log_warning() {
log "WARNUNG" "$1"
echo "WARNUNG: $1" >&2
}
log_error() {
log "FEHLER" "$1"
echo "FEHLER: $1" >&2
}
# Beispielverwendung
log_info "Skript gestartet"
log_warning "Datei könnte veraltet sein"
log_error "Zugriff verweigert"
Erweitertes Logging mit Funktionen
#!/bin/bash
# Konfiguration
LOG_DATEI="anwendung.log"
LOG_LEVEL="INFO" # Mögliche Werte: DEBUG, INFO, WARNING, ERROR
# Hilfsfunktion für Log-Level-Vergleich
ist_log_level_aktiv() {
local level="$1"
case "$LOG_LEVEL" in
"DEBUG") return 0 ;;
"INFO") [[ "$level" != "DEBUG" ]] && return 0 ;;
"WARNING") [[ "$level" =~ ^(WARNING|ERROR)$ ]] && return 0 ;;
"ERROR") [[ "$level" == "ERROR" ]] && return 0 ;;
esac
return 1
}
# Erweiterte Logging-Funktion
log() {
local level="$1"
local nachricht="$2"
# Prüfe, ob dieser Log-Level aktiv ist
if ist_log_level_aktiv "$level"; then
local zeitstempel=$(date "+%Y-%m-%d %H:%M:%S")
local prozess_id="$$"
echo "[$zeitstempel] [$level] (PID:$prozess_id) $nachricht" >> "$LOG_DATEI"
# Fehler und Warnungen auch auf stderr ausgeben
if [[ "$level" =~ ^(ERROR|WARNING)$ ]]; then
echo "[$level] $nachricht" >&2
fi
fi
}
# Beispielverwendung mit Fehlerbehandlung
datei_verarbeiten() {
local dateiname="$1"
log "INFO" "Starte Verarbeitung von: $dateiname"
if [ ! -f "$dateiname" ]; then
log "ERROR" "Datei nicht gefunden: $dateiname"
return 1
fi
if [ ! -r "$dateiname" ]; then
log "ERROR" "Keine Leserechte für: $dateiname"
return 2
fi
log "DEBUG" "Lese Dateiinhalt..."
# Hier würde die eigentliche Verarbeitung stattfinden
log "INFO" "Verarbeitung abgeschlossen"
return 0
}
Log-Rotation implementieren
#!/bin/bash
# Log-Rotation-Funktion
rotate_logs() {
local log_datei="$1"
local max_groesse=$((1024 * 1024)) # 1MB
if [ -f "$log_datei" ]; then
# Prüfe Dateigröße
local groesse=$(stat -f%z "$log_datei" 2>/dev/null || stat -c%s "$log_datei")
if [ $groesse -gt $max_groesse ]; then
log "INFO" "Starte Log-Rotation"
# Alte Log-Datei umbenennen
mv "$log_datei" "${log_datei}.$(date +%Y%m%d-%H%M%S)"
# Neue Log-Datei erstellen
touch "$log_datei"
log "INFO" "Log-Rotation abgeschlossen"
fi
fi
}
Wichtige Aspekte beim Logging:
- Verwende verschiedene Log-Level für unterschiedliche Wichtigkeitsstufen
- Füge Zeitstempel zu den Log-Einträgen hinzu
- Implementiere Log-Rotation, um die Dateigröße zu begrenzen
- Speichere wichtige Informationen wie Prozess-ID und Kontext
Praktische Debugging-Strategien
Lass uns lernen, wie du systematisch Fehler in deinen Skripten finden und beheben kannst. Hier sind die wichtigsten Strategien mit praktischen Beispielen:
Schrittweise Fehlersuche
#!/bin/bash
# Debug-Modus für einen bestimmten Bereich aktivieren
debug_bereich() {
echo "=== Debug-Bereich Start ==="
set -x # Debug-Modus an
# Hier kommt der Code, den wir debuggen wollen
local name="Max"
echo "Verarbeite: $name"
# Weitere Befehle...
set +x # Debug-Modus aus
echo "=== Debug-Bereich Ende ==="
}
# Beispiel für schrittweise Fehlersuche
verarbeite_daten() {
local datei="$1"
echo "Schritt 1: Prüfe Datei"
[ -f "$datei" ] || { echo "Fehler: Datei nicht gefunden"; return 1; }
echo "Schritt 2: Prüfe Leserechte"
[ -r "$datei" ] || { echo "Fehler: Keine Leserechte"; return 2; }
echo "Schritt 3: Verarbeite Inhalt"
while read -r zeile; do
echo "Verarbeite: $zeile"
done < "$datei"
}
Variablen überprüfen
#!/bin/bash
# Aktiviere Fehler bei undefinierten Variablen
set -u
debug_variablen() {
local var1="Wert1"
local var2
echo "var1=${var1:-nicht_gesetzt}"
echo "var2=${var2:-nicht_gesetzt}"
# Prüfe ob Variable gesetzt ist
if [ -z "${var2+x}" ]; then
echo "var2 ist nicht gesetzt"
fi
}
# Beispiel für Variablenprüfung in Funktionen
verarbeite_eingabe() {
local eingabe="$1"
# Prüfe ob Parameter übergeben wurde
if [ -z "$eingabe" ]; then
echo "Fehler: Keine Eingabe erhalten" >&2
return 1
fi
# Zeige Variableninhalt für Debugging
echo "DEBUG: eingabe=$eingabe" >&2
# Verarbeitung...
}
Trap für Fehlerbehandlung
#!/bin/bash
# Fehlerbehandlung einrichten
fehler_aufgetreten() {
local zeile=$1
local befehl=$2
echo "Fehler in Zeile $zeile: $befehl fehlgeschlagen" >&2
# Aufräumarbeiten hier...
}
# Trap für Fehler einrichten
trap 'fehler_aufgetreten ${LINENO} "$BASH_COMMAND"' ERR
# Trap für sauberes Beenden
cleanup() {
echo "Räume auf..."
# Temporäre Dateien löschen, Verbindungen schließen, etc.
}
trap cleanup EXIT
# Beispielcode der Fehler erzeugen könnte
nicht_existierende_datei() {
cat /nicht/vorhanden.txt
}
teste_fehler() {
echo "Teste Fehlerbehandlung..."
nicht_existierende_datei
echo "Diese Zeile wird nicht erreicht"
}
Häufige Fehlerquellen und deren Vermeidung
#!/bin/bash
# 1. Leerzeichen bei Variablenzuweisungen
# Falsch
name = "Max" # Erzeugt einen Fehler
# Richtig
name="Max"
# 2. Anführungszeichen bei Variablen
dateiname="Meine Datei.txt"
# Falsch
rm $dateiname # Wird als zwei separate Argumente interpretiert
# Richtig
rm "$dateiname"
# 3. Pfade mit Leerzeichen
# Falsch
cd /pfad mit/leerzeichen
# Richtig
cd "/pfad mit/leerzeichen"
# 4. Prüfung auf leere Variablen
# Unsicher
if [ $variable = "wert" ]; then
# Sicher
if [ "$variable" = "wert" ]; then
# 5. Numerische Vergleiche
# Falsch
if [ $zahl = 5 ]; then # String-Vergleich
# Richtig
if [ $zahl -eq 5 ]; then # Numerischer Vergleich
Übung
Erstelle ein Skript für die Datensicherung von Verzeichnissen, das folgende Anforderungen erfüllt:
- Das Skript soll:
- Ein Verzeichnis sichern
- Fehler erkennen und protokollieren
- Eine ausführliche Log-Datei erstellen
- Bei Problemen sauber aufräumen
- Implementiere:
- Fehlerbehandlung für alle kritischen Operationen
- Ausführliches Logging
- Debugging-Möglichkeiten
- Sauberes Aufräumen bei Abbruch
- Beachte:
- Überprüfe alle Eingaben
- Protokolliere jeden wichtigen Schritt
- Gib sinnvolle Fehlermeldungen aus
Hier ist eine mögliche Lösung:
#!/bin/bash
# Konfiguration
BACKUP_DIR="/tmp/backups"
LOG_FILE="/var/log/backup.log"
DEBUG=0
# Logging-Funktion
log() {
local level="$1"
local message="$2"
local timestamp=$(date "+%Y-%m-%d %H:%M:%S")
echo "[$timestamp] [$level] $message" >> "$LOG_FILE"
# Bei Debug-Modus oder Fehlern auch auf stderr ausgeben
if [ "$DEBUG" = "1" ] || [ "$level" = "ERROR" ]; then
echo "[$level] $message" >&2
fi
}
# Aufräumfunktion
cleanup() {
local exit_code=$?
log "INFO" "Aufräumen nach Beendigung (Exit-Code: $exit_code)"
# Temporäre Dateien löschen, falls vorhanden
rm -f "/tmp/backup_temp_$$" 2>/dev/null
exit $exit_code
}
# Fehlerbehandlung
error_handler() {
local line_number=$1
local command=$2
log "ERROR" "Fehler in Zeile $line_number: $command fehlgeschlagen"
exit 1
}
# Traps einrichten
trap cleanup EXIT
trap 'error_handler ${LINENO} "$BASH_COMMAND"' ERR
# Hauptfunktion
main() {
local source_dir="$1"
# Eingabevalidierung
if [ -z "$source_dir" ]; then
log "ERROR" "Kein Quellverzeichnis angegeben"
return 1
fi
if [ ! -d "$source_dir" ]; then
log "ERROR" "Quellverzeichnis existiert nicht: $source_dir"
return 1
fi
# Backup-Verzeichnis erstellen
log "INFO" "Erstelle Backup-Verzeichnis"
mkdir -p "$BACKUP_DIR"
# Backup erstellen
local backup_file="$BACKUP_DIR/backup_$(date +%Y%m%d_%H%M%S).tar.gz"
log "INFO" "Erstelle Backup: $backup_file"
if tar -czf "$backup_file" "$source_dir" 2>/dev/null; then
log "INFO" "Backup erfolgreich erstellt"
else
log "ERROR" "Backup fehlgeschlagen"
return 1
fi
}
# Skript starten
log "INFO" "Backup-Skript gestartet"
main "$1"
Fazit
In diesem sechsten Teil unseres Bash-Grundkurses hast du die wichtigen Konzepte der Fehlerbehandlung und des Debuggings kennengelernt. Du weißt nun, wie du systematisch Fehler in deinen Skripten findest und diese behebst. Das Logging hilft dir dabei, den Überblick über die Abläufe in deinen Skripten zu behalten, während die verschiedenen Debugging-Techniken dir ermöglichen, Probleme schnell zu lokalisieren und zu beheben.
Die praktischen Übungen haben dir gezeigt, wie wichtig eine gute Fehlerbehandlung für robuste Skripte ist. Besonders das Backup-Beispiel demonstriert, wie du Fehlerbehandlung, Logging und Debugging in einem realen Szenario kombinierst.
Im siebten und letzten Teil unserer Serie werden wir uns mit fortgeschrittenen Bash-Techniken beschäftigen. Du wirst lernen, wie du Arrays verwendest, mit regulären Ausdrücken arbeitest und komplexe Kommandozeilenargumente verarbeitest.
Bis dahin, happy scripting!