Das Symptom
Warnung vorweg: Keiner der hier zitierten Befehle gehört in eine Shell,
jeder von ihnen zerstört ein System. Aus Foren und Bildwitzen ist bekannt,
dass moderne Linux-Systeme rm -rf / verweigern, und daraus wächst ein trügerisches Sicherheitsgefühl: Das
Schlimmste könne gar nicht mehr passieren. Dann läuft ein Aufräumskript mit
rm -rf "$ZIEL"/*, die Variable ist leer, und die Shell übergibt rm nicht
die Wurzel, sondern /bin, /etc, /home und alles andere, was auf der obersten
Ebene liegt. Jedes dieser Argumente ist für rm ein gewöhnliches Verzeichnis.
Ebenso gefährlich: Ein unter /mnt/backup eingehängtes Sicherungslaufwerk
wird beim rekursiven Löschen des Elternverzeichnisses mitgerissen, weil der
Standardschutz Geräte nicht unterscheidet.
Die „Layer 8“-Wahrheit
Die Handbuchseite rm(1) beschreibt den Schutz knapp. Die Option heißt „–preserve-root[=all]“, sie ist Standard, und ihre Erklärung lautet „do not remove ‘/’ (default)“. Geprüft wird, ob ein Argument buchstäblich die Wurzel ist. Alles, was nicht exakt / heißt, fällt nicht darunter: nicht die Liste, die die Shell aus /* macht, nicht ein Verzeichnis, dessen Pfad aus einer leeren Variablen entstanden ist, und nicht ein Unterverzeichnis, hinter dem ein anderes Laufwerk hängt.
Dieselbe Zeile nennt die schärfere Variante. Mit dem Zusatz all gilt: „with ‘all’, reject any command line argument on a separate device from its parent“. Damit weist rm jedes Argument zurück, das auf einem anderen Gerät liegt als sein Elternverzeichnis, und ein eingehängtes Laufwerk bleibt beim versehentlichen rekursiven Löschen verschont. Und es gibt die Gegenoption „–no-preserve-root“ mit der Erklärung „do not treat ‘/’ specially“; dass sie existiert, verrät, dass der Schutz im Normalfall aktiv ist, und dass er sich abschalten lässt, wenn jemand es ausdrücklich will.
Wer sich auf den Wurzelschutz verlässt, hat ihn nicht gelesen. Er fängt genau einen Fall ab, den Aufruf mit dem Argument /. Den Rest müssen Skript und Gewohnheit abfangen: set -u, damit eine ungesetzte Variable den Lauf beendet; die Form ${VAR:?}, damit eine leere Variable die Shell abbrechen lässt, bevor rm startet; keine Globs hinter rm -rf; und in der Nähe von Einhängepunkten –preserve-root=all oder –one-file-system. Das sind keine Tricks, sondern die Stellen, an denen die Entscheidung tatsächlich fällt, nämlich vor dem Aufruf, nicht in ihm.
Der Irrtum hat eine erkennbare Form: Eine Schutzmaßnahme, die für einen bestimmten Fall gebaut wurde, wird zur allgemeinen Zusage erklärt. rm hat nie versprochen, Skripte zu retten. Es hat versprochen, die Wurzel nicht anzurühren, und genau das hält es. Alles andere expandiert die Shell, und die Shell hat gar nichts versprochen.