Sicherheit, Robustheit, Logging und Tests
Shell-Skripte überstehen Code-Reviews selten ohne Beanstandungen — weil es keine standardisierten Checklisten gibt. Diese Bash-Checkliste deckt alle kritischen Dimensionen ab: Sicherheit, Fehlerbehandlung, Logging, Testbarkeit mit BATS und statische Analyse mit ShellCheck — systematisch und praxisnah.
Inhaltsverzeichnis
- 1. Warum eine Bash-Checkliste notwendig ist
- 2. Checkliste: Skript-Fundament und Header
- 3. Checkliste: Sicherheit und Injection-Prävention
- 4. Checkliste: Robustheit und Fehlerbehandlung
- 5. Checkliste: Logging und Observability
- 6. ShellCheck: Statische Analyse in CI integrieren
- 7. BATS: Automatisierte Tests für Shell-Skripte
- 8. Bash-Checkliste in der CI-Pipeline durchsetzen
- 9. Checklistenpunkte im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum eine Bash-Checkliste notwendig ist
Shell-Skripte werden in den meisten Projekten anders behandelt als Anwendungscode. Es gibt kein Code-Review, kein automatisiertes Testing, keine einheitlichen Standards. Das Ergebnis: In Produktionsumgebungen laufen Skripte, die bei bestimmten Eingaben still fehlschlagen, Dateien ohne Berechtigungsprüfung schreiben, sensible Daten in Logs ausgeben oder bei Unterbrechung inkonsistente Zustände hinterlassen. Eine systematische Bash-Checkliste schließt diese Lücke.
Die Bash-Checkliste in diesem Artikel ist in fünf Dimensionen gegliedert: Skript-Fundament, Sicherheit, Robustheit, Logging und Tests. Jede Dimension enthält konkrete, überprüfbare Punkte — keine vagen Empfehlungen. Die Punkte sind nach Priorität geordnet, sodass auch ein schnelles Review unter Zeitdruck die kritischsten Fragen zuerst beantwortet. Die Checkliste ist als Basis für eigene Code-Review-Templates gedacht und kann direkt als GitHub-Pull-Request-Checkliste oder als Pre-Commit-Hook-Integration eingesetzt werden.
Die Ergänzung durch automatisierte Werkzeuge ist entscheidend: ShellCheck als statischer Analyzer findet die meisten Quoting- und Syntaxfehler maschinell, BATS (Bash Automated Testing System) ermöglicht unit-artige Tests für Shell-Funktionen. Beide Tools lassen sich trivial in CI-Pipelines integrieren. Die manuelle Bash-Checkliste bleibt trotzdem notwendig, weil sie Aspekte abdeckt, die statische Analyse nicht erkennt — wie Logging-Vollständigkeit, Cleanup-Logik und Sicherheitsanforderungen für sensible Daten.
2. Checkliste: Skript-Fundament und Header
Das Fundament jedes produktionstauglichen Shell-Skripts beginnt mit einem vollständigen Header. Der #!/usr/bin/env bash-Shebang verwendet den Systemsuche-Mechanismus für Bash, was auf Systemen mit mehreren Bash-Versionen (macOS vs. Linux) portabler ist als ein absoluter Pfad. Die Kombination set -euo pipefail ist der wichtigste Einzelpunkt der Bash-Checkliste: -e bricht bei Fehlern ab, -u behandelt ungesetzte Variablen als Fehler, -o pipefail propagiert Fehler in Pipes. Ohne diese drei Optionen fehlt die grundlegende Fehlerbehandlung.
Zur Bash-Checkliste für den Header gehört außerdem: IFS=$'\n\t' entfernt das Leerzeichen aus dem Field Separator und verhindert Word-Splitting bei Variablenexpansion. SCRIPT_DIR sollte immer mit cd "$(dirname "${BASH_SOURCE[0]}")" && pwd gesetzt werden, sodass relative Pfade im Skript unabhängig vom aktuellen Arbeitsverzeichnis funktionieren. Alle Konfigurationsvariablen am Anfang bündeln — entweder aus der Umgebung (${VAR:?Fehler}) oder mit Standardwerten (${VAR:-default}). Das macht Abhängigkeiten explizit und erleichtert das Review.
#!/usr/bin/env bash
# deploy.sh — Production deployment script
# Checklist: header section
#
# Required environment variables:
# DEPLOY_ENV — target environment (dev|staging|prod)
# DEPLOY_USER — SSH user for remote host
# DEPLOY_HOST — remote host FQDN or IP
#
# Optional environment variables:
# LOG_LEVEL — log verbosity (DEBUG|INFO|WARN|ERROR), default: INFO
# DRY_RUN — set to 1 to skip destructive operations, default: 0
set -euo pipefail
IFS=$'\n\t'
# ---- Resolve script directory (portable) ----
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"
readonly TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
# ---- Mandatory variables — abort with message if missing ----
DEPLOY_ENV="${DEPLOY_ENV:?Variable DEPLOY_ENV ist nicht gesetzt (dev|staging|prod)}"
DEPLOY_USER="${DEPLOY_USER:?Variable DEPLOY_USER ist nicht gesetzt}"
DEPLOY_HOST="${DEPLOY_HOST:?Variable DEPLOY_HOST ist nicht gesetzt}"
# ---- Optional variables with safe defaults ----
LOG_LEVEL="${LOG_LEVEL:-INFO}"
DRY_RUN="${DRY_RUN:-0}"
LOG_DIR="${LOG_DIR:-/var/log/deploy}"
# ---- Validate DEPLOY_ENV ----
case "$DEPLOY_ENV" in
dev|staging|prod) ;;
*) echo "[ERROR] DEPLOY_ENV muss dev, staging oder prod sein" >&2; exit 1 ;;
esac
readonly LOG_FILE="${LOG_DIR}/${SCRIPT_NAME%.sh}-${TIMESTAMP}.log"
3. Checkliste: Sicherheit und Injection-Prävention
Der kritischste Bereich der Bash-Checkliste ist Sicherheit. Shell-Skripte sind anfällig für Command-Injection, wenn externe Daten — Benutzereingaben, API-Antworten, Dateinamen aus unbekannten Verzeichnissen — ohne Quoting oder Validierung in Befehle eingesetzt werden. Die Grundregel: Jede externe Eingabe als potenziell feindlich behandeln. Variablen, die externe Daten enthalten, immer in doppelten Anführungszeichen verwenden: "$variable" statt $variable. Niemals Eingaben direkt in eval, bash -c oder ähnliche Konstrukte einsetzen.
Ein weiterer Punkt der Bash-Checkliste für Sicherheit: Temporäre Dateien immer mit mktemp erzeugen, nie mit festen Pfaden wie /tmp/skript.tmp. Feste Pfade sind anfällig für Symlink-Angriffe in Mehrbenutzersystemen. Mit trap 'rm -f "$tmpfile"' EXIT werden temporäre Dateien auch bei Fehlerabbruch aufgeräumt. Secrets — Passwörter, API-Keys, Tokens — dürfen nie als Kommandozeilenargumente übergeben werden, weil sie dann in der Prozessliste (ps aux) sichtbar sind. Stattdessen Umgebungsvariablen oder temporäre Dateien mit eingeschränkten Berechtigungen verwenden.
4. Checkliste: Robustheit und Fehlerbehandlung
Die Robustheitsdimension der Bash-Checkliste umfasst alle Maßnahmen, die ein Skript gegen unerwartete Zustände absichern. Neben set -euo pipefail gehört dazu ein vollständiges trap-Setup: trap cleanup EXIT für die Cleanup-Funktion, trap 'echo Unterbrochen; exit 130' INT TERM für Signal-Handling. Die Cleanup-Funktion muss idempotent sein — sie kann mehrfach aufgerufen werden (direkt und über EXIT-Trap) und darf in diesem Fall keinen Fehler erzeugen.
Für externe Abhängigkeiten gehört eine Vorprüfung in die Bash-Checkliste: Zu Beginn des Skripts alle benötigten Tools mit command -v tool >/dev/null 2>&1 || { echo Tool fehlt; exit 1; } prüfen. Das gibt eine verständliche Fehlermeldung, statt dass das Skript mitten in der Ausführung mit einem kryptischen "command not found" scheitert. Für kritische Operationen wie Datenbankmigrationen oder Dateilöschungen empfiehlt die Bash-Checkliste explizite Rollback-Logik: Den Ausgangszustand vor der Operation sichern und im Fehlerfall wiederherstellen.
#!/usr/bin/env bash
set -euo pipefail
# ---- Checklist: dependency verification ----
check_dependencies() {
local -a missing=()
local deps=("curl" "jq" "rsync" "ssh" "mktemp")
for dep in "${deps[@]}"; do
command -v "$dep" &>/dev/null || missing+=("$dep")
done
if (( ${#missing[@]} > 0 )); then
echo "[ERROR] Fehlende Abhängigkeiten: ${missing[*]}" >&2
echo "[ERROR] Installieren mit: apt-get install ${missing[*]}" >&2
exit 1
fi
}
# ---- Checklist: safe temp files ----
readonly TMP_DIR="$(mktemp -d)"
readonly TMP_CONFIG="${TMP_DIR}/config.json"
cleanup() {
local exit_code=$?
# Idempotent — safe to call multiple times
rm -rf "${TMP_DIR:-}" 2>/dev/null || true
[[ $exit_code -ne 0 ]] && echo "[ERROR] Abgebrochen mit Code $exit_code" >&2
return 0
}
trap cleanup EXIT
trap 'echo "[ABORT] Signal erhalten, breche ab" >&2; exit 130' INT TERM
# ---- Checklist: validate inputs before use ----
validate_env() {
local env="$1"
[[ "$env" =~ ^(dev|staging|prod)$ ]] || {
echo "[ERROR] Ungültige Umgebung: $env" >&2; exit 1
}
}
# ---- Checklist: safe file operations ----
safe_write() {
local target="$1" content="$2"
local backup="${target}.bak.$(date +%s)"
# Create backup before overwriting
[[ -f "$target" ]] && cp "$target" "$backup"
printf '%s\n' "$content" > "$target"
}
check_dependencies
validate_env "${DEPLOY_ENV:-}"
5. Checkliste: Logging und Observability
Der Logging-Bereich der Bash-Checkliste stellt sicher, dass Skripte im Produktionsbetrieb beobachtbar sind. Jedes Produktionsskript braucht strukturiertes Logging mit Zeitstempeln, Log-Levels und Kontext. Die Mindestanforderung: Eine Logging-Funktion, die Zeitstempel im ISO-8601-Format, das Level und die Nachricht ausgibt. Der komplette stdout/stderr-Stream sollte mit exec > >(tee -a "$LOG_FILE") 2>&1 in eine Logdatei umgeleitet werden — ohne dabei die Pipe-Kompatibilität zu verlieren.
Zur Bash-Checkliste für Logging gehört auch: Sensible Daten dürfen nie geloggt werden. Passwörter, Tokens und private Schlüssel müssen vor der Ausgabe maskiert werden. Nützlich ist eine Funktion redact(), die bekannte geheime Umgebungsvariablen durch [REDACTED] ersetzt, bevor das Skript env | sort zu Debugging-Zwecken ausgibt. Log-Rotation für Skripte, die häufig laufen: Logdateien älter als N Tage automatisch archivieren oder löschen, damit Festplatten nicht volllaufen.
6. ShellCheck: Statische Analyse in CI integrieren
ShellCheck ist das wichtigste automatisierte Werkzeug der Bash-Checkliste. Es findet statisch Quoting-Fehler (SC2086), unsichere Array-Expansion (SC2068), verlorene Exit-Codes bei lokaler Variablendeklaration (SC2155), unnötige Subshells und viele weitere häufige Fehler. Der Aufruf shellcheck -S warning skript.sh gibt alle Warnungen und Fehler mit Erklärung und Korrekturvorschlag aus. Mit shellcheck --format=gcc lässt sich die Ausgabe in IDE-Formate integrieren.
Die Integration in CI ist trivial: In GitHub Actions reicht eine fertige Action (uses: ludeeus/action-shellcheck@master), in anderen CI-Systemen ein einfaches shellcheck -S warning $(find . -name "*.sh"). Für die Bash-Checkliste gilt: ShellCheck-Warnungen werden nicht ignoriert, sondern behoben oder gezielt mit # shellcheck disable=SC2034 unterdrückt — mit Kommentar, warum die Unterdrückung gerechtfertigt ist. Das macht bewusste Ausnahmen sichtbar und verhindert, dass ShellCheck-Direktiven zur allgemeinen Fehler-Unterdrückung missbraucht werden.
#!/usr/bin/env bash
# test/run-checks.sh — CI quality gate for all shell scripts
set -euo pipefail
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
# ---- ShellCheck all scripts ----
run_shellcheck() {
echo "=== ShellCheck ==="
local -a scripts=()
while IFS= read -r -d '' f; do
scripts+=("$f")
done < <(find "$PROJECT_ROOT" -name "*.sh" -not -path "*/vendor/*" -print0)
if (( ${#scripts[@]} == 0 )); then
echo "Keine .sh-Dateien gefunden"
return 0
fi
local failed=0
for script in "${scripts[@]}"; do
if shellcheck -S warning "$script"; then
printf ' [OK] %s\n' "${script#$PROJECT_ROOT/}"
else
printf ' [FAIL] %s\n' "${script#$PROJECT_ROOT/}"
(( failed++ ))
fi
done
if (( failed > 0 )); then
echo "[ERROR] ShellCheck: $failed Dateien mit Warnungen" >&2
return 1
fi
echo "ShellCheck: Alle Skripte bestanden"
}
# ---- Bash syntax check ----
run_syntax_check() {
echo "=== Syntax-Check ==="
local failed=0
while IFS= read -r -d '' script; do
bash -n "$script" && printf ' [OK] %s\n' "${script#$PROJECT_ROOT/}" \
|| { printf ' [FAIL] %s\n' "${script#$PROJECT_ROOT/}"; (( failed++ )); }
done < <(find "$PROJECT_ROOT" -name "*.sh" -not -path "*/vendor/*" -print0)
(( failed == 0 ))
}
run_syntax_check
run_shellcheck
7. BATS: Automatisierte Tests für Shell-Skripte
BATS (Bash Automated Testing System) bringt unit-artige Tests in die Shell. Das macht es möglich, einzelne Bash-Funktionen isoliert zu testen — und ist ein eigenständiger Bereich der Bash-Checkliste. Ein BATS-Test definiert mit @test "Beschreibung" { ... } einen Testfall, der eine Funktion oder ein Kommando ausführt und das Ergebnis mit [ "$output" = "erwarteter Wert" ] und [ "$status" -eq 0 ] prüft. Das spezielle Variable $output enthält die kombinierte stdout/stderr-Ausgabe des letzten run-Befehls.
Für die Bash-Checkliste beim Testen gilt: Funktionen, die in der Bibliothek (lib/) leben, sind die primären Testziele. Skripte, die externe Befehle aufrufen, werden mit Stub-Funktionen (curl() { echo '{"status":"ok"}'; }) von echten Netzwerkaufrufen entkoppelt. BATS bietet Setup- und Teardown-Hooks (setup(), teardown()), die temporäre Verzeichnisse anlegen und aufräumen. Die Bash-Checkliste fordert für jede öffentliche Bibliotheksfunktion mindestens einen Erfolgsfall, einen Fehlerfall und einen Edge-Case-Test.
8. Bash-Checkliste in der CI-Pipeline durchsetzen
Die automatisierte Durchsetzung der Bash-Checkliste in CI-Pipelines ist der entscheidende Schritt vom guten Vorsatz zur gelebten Praxis. Jeder Pull Request, der Shell-Skripte verändert, muss automatisch ShellCheck, BATS und den Syntax-Check durchlaufen. In GitHub Actions genügt ein Workflow mit drei Jobs: shellcheck, bats und ein optionaler security-scan mit detect-secrets oder trufflehog für Secrets in Skripten.
Als Pre-Commit-Hook lässt sich die Bash-Checkliste mit dem pre-commit-Framework direkt ins lokale Git-Repository integrieren. Relevante Hooks: shellcheck aus dem offiziellen Hook-Repository, check-executables-have-shebangs und detect-private-key. Das stellt sicher, dass ShellCheck-Warnungen schon lokal vor dem Commit sichtbar sind — und nicht erst in der CI-Pipeline nach dem Push. Die Bash-Checkliste als Vorlage für manuelle Reviews und automatisierte Gates ergibt zusammen ein vollständiges Qualitätssystem für Shell-Skripte.
#!/usr/bin/env bats
# test/lib_utils.bats — BATS tests for lib/utils.sh functions
load '../lib/utils.sh'
# ---- Setup and teardown ----
setup() {
export TMP_TEST_DIR
TMP_TEST_DIR="$(mktemp -d)"
}
teardown() {
rm -rf "${TMP_TEST_DIR:-/nonexistent}"
}
# ---- Tests for validate_env() ----
@test "validate_env accepts valid environments" {
run validate_env "prod"
[ "$status" -eq 0 ]
run validate_env "staging"
[ "$status" -eq 0 ]
run validate_env "dev"
[ "$status" -eq 0 ]
}
@test "validate_env rejects invalid environment" {
run validate_env "production"
[ "$status" -eq 1 ]
[[ "$output" == *"Ungültige Umgebung"* ]]
}
# ---- Tests for safe_write() ----
@test "safe_write creates backup of existing file" {
local target="${TMP_TEST_DIR}/config.txt"
printf 'old content\n' > "$target"
safe_write "$target" "new content"
# Original content preserved in backup
local backup
backup="$(ls "${target}.bak."* 2>/dev/null | head -1)"
[ -n "$backup" ]
grep -q "old content" "$backup"
}
@test "safe_write creates new file when target does not exist" {
local target="${TMP_TEST_DIR}/new-file.txt"
safe_write "$target" "initial content"
[ -f "$target" ]
grep -q "initial content" "$target"
}
# ---- Tests for check_dependencies() ----
@test "check_dependencies passes when all tools present" {
# Stub missing tools if needed
run check_dependencies
[ "$status" -eq 0 ]
}
9. Checklistenpunkte im Vergleich
Die folgende Tabelle zeigt, welche Punkte der Bash-Checkliste durch ShellCheck automatisch erkannt werden und welche manuelle Review-Arbeit erfordern. Diese Unterscheidung hilft dabei, den Review-Aufwand richtig einzuschätzen und automatisierbare Prüfungen nicht manuell zu wiederholen.
| Checklistenpunkt | ShellCheck | BATS | Manuelles Review |
|---|---|---|---|
| Quoting-Fehler | Automatisch (SC2086) | — | Nicht nötig |
| set -euo pipefail | Teilweise (SC2039) | — | Header-Check |
| Cleanup-Logik | — | Testbar | Immer prüfen |
| Secrets in Skript | — | — | Immer prüfen |
| Logging-Vollständigkeit | — | Teilweise testbar | Review empfohlen |
ShellCheck deckt die syntaktischen und quoting-bezogenen Probleme zuverlässig ab. BATS validiert das Verhalten von Funktionen unter verschiedenen Bedingungen. Das manuelle Review konzentriert sich auf Sicherheitsaspekte, die kein Werkzeug erkennt: Sind Secrets sicher gehandhabt? Ist die Cleanup-Logik vollständig? Wird bei einer Unterbrechung ein konsistenter Zustand hinterlassen? Diese drei Ebenen der Bash-Checkliste zusammen decken das Spektrum von offensichtlichen Syntaxfehlern bis zu subtilen Sicherheitsproblemen ab.
Mironsoft
Shell-Automatisierung, DevOps-Tooling und Deployment-Infrastruktur
Bash-Qualität systematisch sicherstellen?
Wir führen strukturierte Bash-Checklisten-Reviews durch, integrieren ShellCheck und BATS in eure CI-Pipeline und helfen dabei, bestehende Shell-Skripte gegen die Checkliste zu prüfen und zu verbessern.
Checklisten-Review
Systematische Prüfung bestehender Shell-Skripte gegen alle fünf Checklisten-Dimensionen
CI-Integration
ShellCheck und BATS automatisch in GitHub Actions, GitLab CI oder Jenkins integrieren
Test-Aufbau
BATS-Testsuiten für bestehende Shell-Bibliotheken aufbauen und dokumentieren
10. Zusammenfassung
Eine systematische Bash-Checkliste ist das wichtigste Werkzeug, um Shell-Skripte auf das Niveau von Anwendungscode zu heben. Die fünf Dimensionen — Fundament, Sicherheit, Robustheit, Logging und Tests — decken das gesamte Spektrum von typischen Bash-Problemen ab. ShellCheck automatisiert die syntaktische und quoting-bezogene Prüfung, BATS ermöglicht funktionale Tests für Bibliotheksfunktionen, und das manuelle Review konzentriert sich auf die nicht automatisierbaren Sicherheits- und Vollständigkeitsfragen.
Der größte praktische Nutzen entsteht durch die Kombination aus automatisierten CI-Gates und regelmäßigen manuellen Reviews. Die Bash-Checkliste als Pull-Request-Template stellt sicher, dass kein Skript ohne Grundprüfung ins Repository gelangt. ShellCheck und BATS als Pflicht-Gates in der CI-Pipeline verhindern Regressionen. Und eine gemeinsame Bash-Bibliothek mit zentralisierten Logging-, Cleanup- und Validierungsfunktionen macht die Bash-Checkliste zur lebenden Dokumentation des Shell-Coding-Standards im Projekt.
Bash-Checkliste — Das Wichtigste auf einen Blick
Fundament & Sicherheit
set -euo pipefail, IFS, SCRIPT_DIR. Externe Eingaben immer quoten. mktemp statt fester /tmp-Pfade. Secrets nie in Kommandozeilenargumenten.
Robustheit
trap cleanup EXIT für alle Beendigungsszenarien. Dependency-Check am Skriptbeginn. Rollback-Logik für destruktive Operationen.
ShellCheck
shellcheck -S warning als CI-Gate. Warnungen beheben oder mit Kommentar unterdrücken. Pre-Commit-Hook für lokale Frühwarnung.
BATS-Tests
Bibliotheksfunktionen mit BATS testen. Setup/teardown für Tmpfiles. Stubs für externe Befehle. Mindestens Erfolgs-, Fehler- und Edge-Case-Test pro Funktion.