Ein Bash-Skript, das „meistens funktioniert“, ist nur ein Cronjob davon entfernt, dir nachts um 03:17 Uhr den Kaffee kalt werden zu lassen. Wir haben alle schon diese 400-Zeilen-Monster gesehen: keine Fehlerbehandlung, Variablen aus dem Nebel, `echo` als Logging-Konzept. Läuft. Bis es läuft wie ein brennender Serverwagen.
Der erste Reflex bei neuen Bash-Skripten sollte sein:
```bash
!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
```
Das ist quasi der Sicherheitsgurt.
`set -e` beendet das Skript bei Fehlern. Kein fröhliches Weiterstolpern nach einem kaputten `cp`.
`set -u` behandelt nicht gesetzte Variablen als Fehler. Sehr praktisch, wenn aus `$BACKUP_DIR` plötzlich nichts wird und `rm -rf "$BACKUP_DIR/"*` auf kreative Ideen kommt.
`set -o pipefail` sorgt dafür, dass Pipelines fehlschlagen, wenn ein Kommando darin fehlschlägt. Ohne das sieht Bash nur den Exitcode des letzten Kommandos:
```bash
grep "wichtig" /nicht/da | sort
```
`sort` ist glücklich. Dein Skript auch. Die Realität nicht.
Wichtig: `set -e` ist kein magischer Schutzengel. In `if`, `while`, Subshells und komplexen Konstrukten gibt es Ecken, an denen es sich anders verhält als erwartet. Deshalb: bewusst einsetzen, nicht blind anbeten.
Für Fehlerbehandlung bauen wir uns gern eine kleine Trap:
```bash
!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
log() {
printf '%s [%s] %s\n' "$(date -Is)" "$1" "$2"
}
die() {
log "ERROR" "$1"
exit 1
}
trap 'die "Fehler in Zeile $LINENO beim Kommando: $BASH_COMMAND"' ERR
```
Damit bekommst du im Fehlerfall wenigstens einen brauchbaren Hinweis. Nicht nur „Script failed“. Danke für nichts, Bash.
Logging sollte nicht aus zufälligen `echo`s bestehen. Mach dir eine Funktion:
```bash
log "INFO" "Backup startet"
log "WARN" "Zielverzeichnis existiert schon"
log "ERROR" "rsync ist explodiert"
```
Wenn das Skript per Cron läuft, willst du Logs idealerweise in eine Datei schreiben:
```bash
LOGFILE="/var/log/my-backup.log"
exec > >(tee -a "$LOGFILE") 2>&1
```
Damit landen stdout und stderr im Logfile und bleiben trotzdem auf der Konsole sichtbar. Praktisch beim Testen. Weniger praktisch, wenn du Passwörter loggst. Also: keine Secrets ins Log kippen. Klingt banal, passiert aber schneller als ein `chmod 777`.
Variablen immer quoten:
```bash
cp "$src" "$dst"
```
Nicht:
```bash
cp $src $dst
```
Leerzeichen in Dateinamen sind zwar ein kulturelles Verbrechen, aber sie existieren. Genau wie Tabs, Zeilenumbrüche und User mit „kreativen“ Uploads.
Wartbarkeit beginnt bei Struktur. Pack Logik in Funktionen:
```bash
check_requirements() {
command -v rsync >/dev/null || die "rsync fehlt"
}
run_backup() {
rsync -a --delete "$SOURCE/" "$TARGET/"
}
```
Dann liest sich dein Skript wie ein Ablaufplan:
```bash
main() {
check_requirements
run_backup
log "INFO" "Backup fertig"
}
main "$@"
```
Und bitte: `shellcheck` benutzen. Immer.
```bash
shellcheck backup.sh
```
Das Tool findet Dinge, die du um 18:30 Uhr übersiehst und um 03:17 Uhr bereust. CI-Pipeline? Noch besser. Bash ist kein Freifahrtschein für Wildwest-Code.
Unsere Faustregel: Ein Admin-Skript sollte so geschrieben sein, dass du es sechs Monate später mit müdem Blick verstehst, ohne archäologische Ausgrabungen zu starten. Klare Namen, saubere Fehler, kontrolliertes Logging, keine stillen Annahmen.
Bash bleibt Bash: mächtig, überall verfügbar, leicht scharfkantig. Mit `set -euo pipefail`, sinnvollen Traps und etwas Disziplin wird daraus aber kein Minenfeld, sondern ein Werkzeug. Und das ist deutlich angenehmer als ein Cronjob mit Überraschungsei-Funktion.