Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
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:
Lass uns mit dem Grundlegendsten beginnen: Wie Bash mit Fehlern umgeht.
In Bash zeigt jeder Befehl durch seinen Exit-Code an, ob er erfolgreich war oder nicht. Das ist wie ein Ampelsystem:
#!/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
#!/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>&2
) ausgegeben werdenBeim Debugging helfen dir verschiedene Werkzeuge, den Ablauf deines Skripts zu verfolgen und Fehler zu finden. Die wichtigsten Werkzeuge sind set -x
und set -e
.
#!/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.
#!/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"
#!/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 VariablenEin gutes Logging-System hilft dir, nachzuvollziehen, was dein Skript macht und was schiefgegangen ist. Lass uns ein einfaches, aber effektives Logging-System aufbauen.
#!/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"
#!/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
}
#!/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:
Lass uns lernen, wie du systematisch Fehler in deinen Skripten finden und beheben kannst. Hier sind die wichtigsten Strategien mit praktischen Beispielen:
#!/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"
}
#!/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...
}
#!/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"
}
#!/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
Erstelle ein Skript für die Datensicherung von Verzeichnissen, das folgende Anforderungen erfüllt:
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"
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!