tar- und gzip-Automatisierung in Bash-Skripten: robuste Backups mit inkrementellen Läufen
AI generated
$_
#!/
Bash · Backup · tar · gzip
tar- und gzip-Automatisierung in Bash
Robuste, inkrementelle Backup-Skripte, die auch bei vollem Speicher kontrolliert scheitern

Ein Backup-Skript, das im Normalfall funktioniert, aber bei vollem Speicherplatz eine unvollständige Archivdatei zurücklässt, die niemand als defekt erkennt, ist schlimmer als gar kein Backup, weil es falsche Sicherheit vorgaukelt. Robuste tar- und gzip-Automatisierung bedeutet, Fehlerfälle genauso sorgfältig zu behandeln wie den Erfolgsfall.

17 Min. Lesezeit tar · gzip · listed-incremental Bash 4.x · 5.x · Linux

1. Das Grundgerüst eines robusten Backup-Skripts

Ein Backup-Skript unterscheidet sich von den meisten anderen Automatisierungsskripten dadurch, dass ein unbemerkter Fehler oft erst Monate später sichtbar wird, nämlich genau dann, wenn das Backup tatsächlich gebraucht wird. Deshalb gehört an den Anfang jedes Backup-Skripts set -euo pipefail, damit ein fehlgeschlagener Befehl das Skript sofort abbricht, statt mit einer stillschweigend unvollständigen Archivdatei weiterzumachen, die später wie ein gültiges Backup aussieht.

Ebenso wichtig ist ein aussagekräftiger Exit-Code und eine strukturierte Log-Ausgabe, damit ein übergeordnetes Monitoring-System sofort erkennt, ob ein Backup-Lauf erfolgreich war, ohne den kompletten Log-Text interpretieren zu müssen. Ein Backup-Skript, das nur bei einem Terminal-Fehler auffällt, aber in einem unbeaufsichtigten Cron-Job stillschweigend fehlschlägt, verfehlt seinen eigentlichen Zweck.


#!/usr/bin/env bash
set -euo pipefail

readonly BACKUP_SRC="/var/www/app"
readonly BACKUP_DST="/backups/app-$(date +%Y%m%d-%H%M%S).tar.gz"
readonly LOG_FILE="/var/log/backup-app.log"

log() {
  echo "$(date '+%Y-%m-%d %H:%M:%S') $*" | tee -a "$LOG_FILE"
}

trap 'log "FAILED: backup aborted (exit code $?)"' ERR

log "Starting backup: $BACKUP_SRC -> $BACKUP_DST"
tar -czf "$BACKUP_DST" -C "$(dirname "$BACKUP_SRC")" "$(basename "$BACKUP_SRC")"
log "Backup completed successfully: $(du -h "$BACKUP_DST" | cut -f1)"

2. Inkrementelle Backups mit --listed-incremental

Für große Datenbestände ist ein tägliches Vollbackup oft weder nötig noch praktikabel, weil sich zwischen zwei Läufen meist nur ein kleiner Bruchteil der Dateien tatsächlich ändert. tar unterstützt genau für diesen Fall die Option --listed-incremental, die in einer separaten Snapshot-Datei den Zustand aller Dateien beim letzten Lauf festhält und beim nächsten Aufruf nur noch die seither veränderten oder neuen Dateien in das Archiv aufnimmt.

Der entscheidende Unterschied zu einem einfachen Vollbackup liegt in dieser Snapshot-Datei: Sie muss über alle Läufe hinweg erhalten bleiben und darf niemals versehentlich mit dem Archiv selbst gelöscht werden, sonst verliert tar beim nächsten Lauf die Information darüber, was bereits gesichert wurde, und fällt unbemerkt auf ein Vollbackup zurück. Ein robustes Skript legt die Snapshot-Datei deshalb an einem dauerhaften, vom eigentlichen Archivziel getrennten Ort ab.


#!/usr/bin/env bash
set -euo pipefail

readonly SRC="/var/www/app"
readonly SNAPSHOT="/backups/state/app.snapshot"
readonly ARCHIVE="/backups/app-incr-$(date +%Y%m%d).tar.gz"

mkdir -p "$(dirname "$SNAPSHOT")"

# First run: SNAPSHOT does not exist yet -> full backup, snapshot is created
# Subsequent runs: only files changed since the last run are included
tar --listed-incremental="$SNAPSHOT" -czf "$ARCHIVE" -C "$(dirname "$SRC")" "$(basename "$SRC")"

echo "Incremental backup written: $ARCHIVE ($(du -h "$ARCHIVE" | cut -f1))"

3. Volle und inkrementelle Backups sinnvoll kombinieren

Reine inkrementelle Backups über viele Monate hinweg machen die Wiederherstellung mühsam, weil jedes einzelne Inkrement in der richtigen Reihenfolge eingespielt werden muss, um den aktuellen Stand zu erhalten. Bewährt hat sich deshalb ein rotierendes Schema: einmal pro Woche ein Vollbackup mit einer frischen Snapshot-Datei, an den übrigen Tagen jeweils ein Inkrement gegenüber dem letzten Vollbackup.

Diese Rotation lässt sich direkt im Skript über den Wochentag steuern, indem die Snapshot-Datei am Tag des Vollbackups gezielt gelöscht wird, bevor tar erneut aufgerufen wird. Damit erzwingt das Skript an diesem Tag automatisch ein neues Vollbackup, ohne dass eine separate Codepfad-Verzweigung für Voll- und Inkrementbackup nötig wird, tar erkennt die fehlende Snapshot-Datei selbst und beginnt von vorne.


#!/usr/bin/env bash
set -euo pipefail

readonly SNAPSHOT="/backups/state/app.snapshot"
readonly ARCHIVE="/backups/app-$(date +%Y%m%d).tar.gz"

# Force a fresh full backup every Sunday by resetting the snapshot file
if [[ "$(date +%u)" == "7" ]]; then
  rm -f "$SNAPSHOT"
  echo "Sunday: forcing a full backup"
fi

tar --listed-incremental="$SNAPSHOT" -czf "$ARCHIVE" -C /var/www app

4. Fehlerbehandlung bei vollem Speicher: früh erkennen statt spät scheitern

Ein voller Datenträger während des Schreibens einer Archivdatei ist einer der gefährlichsten Fehlerfälle, weil tar mit set -e zwar den Exit-Code korrekt setzt, aber die bereits teilweise geschriebene Archivdatei am Zielort liegen bleibt und je nach Zeitpunkt des Abbruchs sogar noch scheinbar gültig aussehen kann. Ein robustes Skript prüft deshalb vor dem Start bereits, ob genügend freier Speicherplatz vorhanden ist, statt das Problem erst mitten im Schreibvorgang zu bemerken.

Zusätzlich gehört eine Nachprüfung nach dem Schreiben in jedes Backup-Skript: Die fertige Archivdatei sollte mit tar -tzf auf Lesbarkeit geprüft werden, bevor sie als gültiges Backup markiert wird, und eine unvollständige Datei aus einem abgebrochenen Lauf gehört konsequent gelöscht statt am Zielort belassen, damit spätere Wiederherstellungsversuche nicht auf ein scheinbar vorhandenes, aber tatsächlich kaputtes Archiv stoßen.


#!/usr/bin/env bash
set -euo pipefail

readonly ARCHIVE="/backups/app-$(date +%Y%m%d).tar.gz"
readonly DEST_DIR="/backups"
readonly MIN_FREE_MB=2048

cleanup_on_failure() {
  [[ -f "$ARCHIVE" ]] && rm -f "$ARCHIVE"
  echo "Backup FAILED, removed incomplete archive: $ARCHIVE" >&2
}
trap cleanup_on_failure ERR

# Fail fast if there is not enough free space before starting
free_mb=$(df --output=avail -m "$DEST_DIR" | tail -n1 | tr -d ' ')
if (( free_mb < MIN_FREE_MB )); then
  echo "Not enough free space: ${free_mb}MB available, ${MIN_FREE_MB}MB required" >&2
  exit 1
fi

tar -czf "$ARCHIVE" -C /var/www app

# Verify the archive is actually readable before trusting it
if ! tar -tzf "$ARCHIVE" > /dev/null; then
  echo "Archive verification FAILED: $ARCHIVE is corrupt" >&2
  exit 1
fi
echo "Backup verified OK: $ARCHIVE"

5. Kompressions-Level vs. Laufzeit: den richtigen Tradeoff wählen

gzip bietet über die Option -1 bis -9 eine direkte Kontrolle über den Kompressionsgrad, wobei -1 die schnellste, aber am wenigsten komprimierende Stufe ist und -9 die höchste Kompression bei deutlich höherem Rechenaufwand liefert. Der Standardwert liegt bei -6 und stellt für die meisten Anwendungsfälle bereits einen vernünftigen Kompromiss dar, aber für ein Backup-Skript lohnt sich eine bewusste Entscheidung statt des Defaults.

Für sehr große Datenmengen, bei denen die Backup-Laufzeit in ein enges Zeitfenster passen muss, ist eine niedrigere Kompressionsstufe wie -3 oft die bessere Wahl, weil die eingesparte Rechenzeit den zusätzlichen Speicherbedarf der etwas größeren Archivdatei meist aufwiegt. Für Archive, die selten geschrieben, aber häufig über eine langsame Netzwerkverbindung übertragen oder über lange Zeiträume gespeichert werden, überwiegt dagegen der Vorteil einer hohen Kompressionsstufe.


#!/usr/bin/env bash
set -euo pipefail

# Fast backup within a tight maintenance window: lower compression
tar -cf - -C /var/www app | gzip -3 > /backups/app-fast.tar.gz

# Long-term archive storage: higher compression, more CPU time accepted
tar -cf - -C /var/www app | gzip -9 > /backups/app-archive.tar.gz

6. Kompression parallelisieren, um den Tradeoff zu entschärfen

Ein häufig übersehener Ausweg aus dem Kompromiss zwischen Laufzeit und Kompressionsgrad ist, die Kompression selbst auf mehrere CPU-Kerne zu verteilen, statt sich zwischen einer niedrigen Stufe und langer Laufzeit entscheiden zu müssen. Werkzeuge wie pigz sind ein direkter Drop-in-Ersatz für gzip mit identischer Kommandozeilen-Syntax, nutzen aber alle verfügbaren Kerne parallel und erreichen dieselbe Kompressionsstufe in einem Bruchteil der Zeit.

Wichtig ist, ein Skript so zu schreiben, dass es bei fehlendem pigz automatisch auf das immer verfügbare gzip zurückfällt, damit dasselbe Skript auf einem minimalen Server ohne zusätzliche Pakete genauso funktioniert wie auf einem gut ausgestatteten Backup-Host. Diese Rückfalllogik kostet nur wenige Zeilen, verhindert aber, dass ein Skript auf einem System ohne pigz einfach mit einem Fehler abbricht.


#!/usr/bin/env bash
set -euo pipefail

# Use pigz (parallel gzip) if available, fall back to plain gzip otherwise
compressor() {
  if command -v pigz &> /dev/null; then
    pigz -9
  else
    gzip -9
  fi
}

tar -cf - -C /var/www app | compressor > /backups/app-parallel.tar.gz

7. Backups regelmäßig testweise wiederherstellen

Ein Archiv, das mit tar -tzf als lesbar geprüft wurde, garantiert nur, dass die Struktur intakt ist, nicht dass jede einzelne Datei tatsächlich fehlerfrei wiederhergestellt werden kann und dass die Anwendung mit dem wiederhergestellten Zustand tatsächlich funktioniert. Die einzige Prüfung, die dieses Vertrauen wirklich rechtfertigt, ist eine regelmäßige, echte Wiederherstellung in eine isolierte Testumgebung, gefolgt von einem einfachen funktionalen Check.

Ein solcher Restore-Test lässt sich problemlos automatisieren und wöchentlich oder monatlich als eigener Cron-Job laufen lassen, der ein zufällig ausgewähltes Backup entpackt, gegen eine erwartete Dateiliste prüft und das Ergebnis an dasselbe Monitoring-System meldet wie der eigentliche Backup-Lauf. Ein Team, das erst beim tatsächlichen Notfall zum ersten Mal ein Backup einspielt, entdeckt Probleme immer zum denkbar schlechtesten Zeitpunkt.

8. Aufbewahrungsfristen automatisiert durchsetzen

Ohne automatisierte Bereinigung wachsen Backup-Verzeichnisse unbegrenzt und laufen früher oder später selbst in den vollen Speicher, den das Skript eigentlich behandeln soll. Eine einfache Aufbewahrungsregel, etwa tägliche Backups sieben Tage und wöchentliche Vollbackups zwölf Wochen zu behalten, lässt sich mit find anhand des Datei-Änderungsdatums zuverlässig durchsetzen.

Diese Bereinigung sollte immer nach der erfolgreichen Verifikation des neuen Backups laufen, niemals davor, damit im Fehlerfall eines fehlgeschlagenen neuen Laufs das letzte funktionierende Backup nicht versehentlich mitgelöscht wird. Diese Reihenfolge ist der wichtigste Unterschied zwischen einer Bereinigung, die im Ernstfall hilft, und einer, die im Ernstfall zusätzlichen Schaden anrichtet.


#!/usr/bin/env bash
set -euo pipefail

readonly BACKUP_DIR="/backups"
readonly KEEP_DAYS=7

# Only prune AFTER the new backup was verified successfully
find "$BACKUP_DIR" -maxdepth 1 -name '*.tar.gz' -mtime "+${KEEP_DAYS}" -print -delete

9. Backup-Strategien im Vergleich

Die Wahl zwischen reinem Vollbackup, reinem Inkrementalbackup und einer Kombination aus beiden hängt vor allem von der Größe der Datenmenge, der Häufigkeit von Änderungen und den Anforderungen an die Wiederherstellungszeit ab. Für kleine bis mittlere Datenmengen mit ausreichendem Zeitfenster bleibt ein tägliches Vollbackup oft die einfachste und zuverlässigste Lösung, weil jede Wiederherstellung nur ein einziges Archiv benötigt.

Strategie Backup-Laufzeit Wiederherstellungs-Komplexität Typischer Einsatz
Tägliches Vollbackup Hoch, unabhängig von Änderungen Niedrig, ein einziges Archiv Kleine bis mittlere Datenmengen
Reines Inkrementalbackup Niedrig Hoch, alle Inkremente in Reihenfolge Sehr große, selten wiederherzustellende Daten
Wöchentlich voll + täglich inkrementell Ausgewogen Mittel, ein Voll- plus wenige Inkremente Die meisten produktiven Serverumgebungen
Parallele Kompression (pigz) Deutlich reduziert Unverändert Große Datenmengen mit engem Zeitfenster

Mironsoft

Shell-Automatisierung, DevOps-Tooling und Deployment-Infrastruktur

Shell-Skripte, die in der Produktion zuverlässig laufen?

Wir analysieren bestehende Bash-Skripte, erkennen fragile Muster und ersetzen sie durch robuste Bash-Patterns: mit vollständiger Fehlerbehandlung, Logging und sicherer Parallelisierung für euren Deployment-Stack.

Code-Review

ShellCheck-Analyse und manuelle Prüfung auf kritische Bash-Pattern-Verstöße.

Refactoring

Fehlerbehandlung, Logging und sichere Dateioperationen nachrüsten.

CI-Integration

ShellCheck und BATS in Pipelines integrieren und Regressionstests aufbauen.

10. Zusammenfassung

tar- und gzip-Automatisierung in Bash: Das Wichtigste auf einen Blick

Fehler früh erkennen

Freien Speicherplatz vor dem Start prüfen, set -euo pipefail nutzen und die fertige Archivdatei mit tar -tzf verifizieren, bevor sie als gültig gilt.

Inkrementell mit Snapshot-Datei

--listed-incremental spart Zeit und Platz, die Snapshot-Datei muss aber dauerhaft und getrennt vom Archivziel aufbewahrt werden.

Kompression bewusst wählen

Niedrigere gzip-Stufen sparen Zeit bei engen Zeitfenstern, pigz nutzt mehrere CPU-Kerne und entschärft den Tradeoff fast vollständig.

Aufräumen erst nach Verifikation

Alte Backups erst löschen, nachdem das neue Backup erfolgreich verifiziert wurde, sonst geht im Fehlerfall das letzte funktionierende Backup verloren.

11. FAQ: tar- und gzip-Automatisierung in Bash: Das Wichtigste auf einen Blick

1Warum sollte jedes Backup-Skript set -euo pipefail nutzen?
Damit ein fehlgeschlagener Befehl das Skript sofort abbricht, statt mit einer stillschweigend unvollständigen Archivdatei weiterzumachen, die später wie ein gültiges Backup aussieht.
2Was macht tar --listed-incremental genau?
Es speichert den Dateizustand des letzten Laufs in einer Snapshot-Datei und nimmt beim nächsten Aufruf nur noch seither veränderte oder neue Dateien in das Archiv auf.
3Was passiert, wenn ich die Snapshot-Datei eines inkrementellen Backups verliere?
tar erkennt beim nächsten Lauf keinen vorherigen Zustand mehr und erstellt effektiv wieder ein Vollbackup, ohne das im Skript explizit zu vermerken.
4Wie erzwinge ich regelmäßig ein neues Vollbackup trotz inkrementeller Strategie?
Die Snapshot-Datei an einem festen Tag gezielt löschen, bevor tar erneut aufgerufen wird. tar erkennt die fehlende Datei und beginnt automatisch mit einem Vollbackup.
5Wie erkenne ich einen vollen Speicher, bevor das Backup fehlschlägt?
Mit df --output=avail vor dem Start prüfen, ob genügend freier Speicherplatz vorhanden ist, und das Skript kontrolliert abbrechen, wenn der Wert unter einer definierten Schwelle liegt.
6Wie stelle ich sicher, dass ein fertiges Archiv tatsächlich lesbar ist?
Mit tar -tzf archiv.tar.gz nach dem Schreiben die Struktur prüfen. Schlägt dieser Befehl fehl, ist die Datei korrupt und sollte nicht als gültiges Backup behandelt werden.
7Welchen Kompressionsgrad sollte ich für Backups wählen?
Für enge Zeitfenster eine niedrigere Stufe wie -3, für selten geschriebene, langfristig gespeicherte Archive eine hohe Stufe wie -9. Der Standard -6 ist meist ein brauchbarer Mittelweg.
8Wie kann ich Kompression beschleunigen, ohne die Kompressionsstufe zu senken?
Mit pigz statt gzip, das dieselbe Kompressionsstufe parallel auf mehrere CPU-Kerne verteilt und dadurch die Laufzeit deutlich reduziert, ohne Kompatibilität zu gzip-Archiven zu verlieren.
9Warum reicht die Verifikation mit tar -tzf allein nicht aus?
Sie prüft nur die Struktur des Archivs, nicht ob jede Datei fehlerfrei wiederhergestellt werden kann und die Anwendung mit dem Ergebnis tatsächlich funktioniert. Nur ein echter Restore-Test in einer Testumgebung schafft diese Sicherheit.
10In welcher Reihenfolge sollten Backup und Aufräumen alter Backups laufen?
Erst das neue Backup erstellen und verifizieren, danach alte Backups löschen. So bleibt im Fehlerfall eines neuen Laufs das letzte funktionierende Backup unangetastet.