Linux-Tutorial / Umgang mit Bash-Fehlern und sicheres Arbeiten mit Dateien

Ein sichereres Dateiskript validiert Eingaben und Ziele, behandelt Fehler ausdrücklich und vermeidet es, teilweise Ausgaben als fertiges Ergebnis darzustellen. Anstatt sich ausschließlich auf set -e zu verlassen, sollte man bei jedem Schritt definieren, was ein Fehler bedeutet, und das Verhalten für Aufräumen und Überschreiben festlegen.

Bauen Sie auf dem ersten Bash-Skript auf, um Fehlerzweige und Beschränkungen bei Dateioperationen zu behandeln. Überprüfen Sie &&, || und die Pipeline-Regeln im Shell-Ausführungsfluss.

Definieren Sie die Sicherheitsgrenze des Beispiels

Das Beispiel kopiert eine reguläre Datei unter einem neuen Namen in ein vertrauenswürdiges Verzeichnis, das Sie kontrollieren, und vergleicht dann den Inhalt. Das Zielverzeichnis muss bereits existieren und darf von nicht vertrauenswürdigen Benutzern nicht beschreibbar sein. Es ist keine Sicherheitsgrenze gegen feindliche gleichzeitige Pfadänderungen oder ein Datenbanksicherungswerkzeug. Führen Sie es ohne Administratorrechte aus.

Erstellen Sie ein vollständiges Skript für geprüfte Kopien

lab=$(mktemp -d "$HOME/linux-safe-copy.XXXXXX")
cd "$lab"
mkdir output
printf "important sample\n" > "source file.txt"
cat > checked-copy.sh <<'EOF'
#!/usr/bin/env bash
set -u
set -o pipefail
umask 077
if (( $# != 2 )); then
  printf "Usage: %s SOURCE DESTINATION\n" "$0" >&2
  exit 2
fi
source_file=$1
destination=$2
if [[ ! -f "$source_file" || -L "$source_file" ]]; then
  printf "Source must be a regular non-symlink file\n" >&2
  exit 1
fi
if [[ -e "$destination" || -L "$destination" ]]; then
  printf "Destination already exists\n" >&2
  exit 1
fi
parent=$(dirname -- "$destination")
if [[ ! -d "$parent" ]]; then
  printf "Destination directory is missing\n" >&2
  exit 1
fi
work=$(mktemp -d "$parent/.copy-work.XXXXXX") || exit 1
cleanup() { rm -rf -- "$work"; }
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
if ! cp -- "$source_file" "$work/payload"; then
  printf "Copy failed\n" >&2
  exit 1
fi
if ! cmp -s -- "$source_file" "$work/payload"; then
  printf "Verification failed\n" >&2
  exit 1
fi
if ! ln -- "$work/payload" "$destination"; then
  printf "Destination creation failed\n" >&2
  exit 1
fi
printf "Created: %s\n" "$destination"
EOF
bash -n checked-copy.sh
bash checked-copy.sh "source file.txt" "output/copy file.txt"
cmp "source file.txt" "output/copy file.txt"

Das Skript kopiert in ein temporäres Verzeichnis, vergleicht den Inhalt und erstellt dann den Zielnamen mithilfe eines Hardlinks. Der temporäre Speicher befindet sich im Dateisystem des Ziels; ln schlägt fehl, wenn dieser Name bereits existiert, anstatt ihn zu überschreiben. Die EXIT-Bereinigung entfernt den temporären Namen, während der Ziellink erhalten bleibt. Wenn das Dateisystem keine Hardlinks erstellen kann, schlägt der letzte Schritt fehl und das Design muss überdacht werden.

Das Beispiel überprüft den Inhalt, nicht Eigentümerinformationen, ACLs, erweiterte Attribute oder dauerhaften Speicher nach einem Stromausfall. Es ist für eine Quelle, die kontinuierlich geändert wird, ungeeignet. Die rekursive Bereinigung richtet sich nur auf das erfolgreich erstellte mktemp-Arbeitsverzeichnis, niemals auf die vom Benutzer angegebene Quelle oder das Ziel.

Testen Sie erwartete Fehler absichtlich

bash checked-copy.sh "source file.txt" "output/copy file.txt"
printf "exit=%s\n" "$?"
bash checked-copy.sh missing.txt output/new.txt
printf "exit=%s\n" "$?"
find output -maxdepth 1 -name ".copy-work.*" -print

Das wiederholte erfolgreiche Kopieren sollte den Status 1 zurückgeben und das vorhandene Ziel beibehalten. Eine fehlende Quelle muss ebenfalls fehlschlagen. Nach normalen Beendigungen oder behandelten Fehlern sollte find keine temporären Arbeitsverzeichnisse anzeigen. Signal- und EXIT-Handler können nach SIGKILL oder Stromausfall nicht ausgeführt werden; verlassene temporäre Daten erfordern eine separate Aufräumrichtlinie. Überprüfen Sie die Original- und die erste Kopie nach den Fehlertests.

Verstehen Sie die Grenzen von errexit und pipefail

bash -c 'false | cat; printf "status=%s\n" "$?"'
bash -o pipefail -c 'false | cat; printf "status=%s\n" "$?"'

Der erste Befehl gibt normalerweise status=0 aus, weil cat erfolgreich ist; der zweite gibt status=1 aus, weil pipefail die fehlgeschlagene Komponente sichtbar macht. Pipefail ändert die Statusweitergabe, nicht die Fehlerbehebung. set -e hat kontextabhängige Ausnahmen, die bedingte Anweisungen und Pipelines betreffen, also testen Sie den genauen Kontext, in dem Sie erwarten, dass die Ausführung stoppt.

if ! command; then ist praktisch für Fehlerzweige, aber $? innerhalb dieses Zweigs spiegelt die Negation und nicht den ursprünglichen Status wider. Bewahren Sie den ursprünglichen Code sofort in einem else-Zweig auf, wenn es relevant ist. Definieren Sie nützliche Rückgabecodes und Fehlermeldungen für Aufrufer.

Überprüfen Sie die Betriebsannahmen vor der Automatisierung

  • Setzen Sie Anführungszeichen für Erweiterungen und verwenden Sie -- vor Dateinamenargumenten, wo dies unterstützt wird.
  • Trennen Sie die Vorschau von der Mutation und stellen Sie sicher, dass die Vorschau die Daten nicht ändern kann.
  • Überprüfen Sie Besitz, Berechtigungen, symbolische Links, Parallelität und Quelländerungen.
  • Testen Sie Erfolge, Misserfolge und behandelte Signale und behalten Sie wiederherstellbare Sicherungen.

Offizielle Referenzen