Layer 8Ehrlicher Tech-Support

Linux: Der Wurzelschutz von rm fängt nur ein Argument ab

Der Wurzelschutz von GNU rm weist nur das Argument / zurück. Die Shell-Expansion von /* und eine leere Variable laufen ungebremst durch.

Aufwand:
ca. 20 Min.
Voraussetzung:
Shell mit GNU coreutils, Handbuchseite rm(1), set -u und ${VAR:?} in Skripten, Ein Wegwerf-Verzeichnis zum Ausprobieren
Gilt für:
Linux mit GNU coreutils (rm(1), Handbuchseite in der am 2026-10-15 abrufbaren Fassung), Nicht erfasst: BSD-rm und BusyBox, deren Schutzverhalten hier nicht geprüft ist

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.

Die echte, verifizierte Lösung

1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:

  1. 1.

    Verstehen, was geschützt ist und was nicht

    Der Standard --preserve-root verweigert nach rm(1) nur das Argument /, nichts sonst. Nicht geschützt sind /* nach der Expansion durch die Shell, Pfade aus leeren Variablen, Verzeichnisse auf eingehängten Laufwerken und alles, was einen Schrägstrich mehr trägt als die Wurzel. Wer das weiß, verlässt sich nicht auf rm, sondern auf das, was vor rm passiert.

  2. 2.

    Variablen vor rm -rf absichern

    Am Anfang jedes Skripts set -u setzen, damit eine ungesetzte Variable den Lauf abbricht statt zu einem leeren String zu werden. Zusätzlich das Ziel in der Form "${ZIEL:?Ziel leer}" verwenden: Ist die Variable leer, bricht die Shell mit der Meldung ab, bevor rm überhaupt aufgerufen wird. Beides zusammen kostet zwei Zeilen und fängt den klassischen Fall ab.

    set -u
    rm -rf -- "${ZIEL:?Ziel leer}"/cache
    
  3. 3.

    Keine Globs an rm -rf übergeben

    Statt "$DIR"/* zu löschen, das Verzeichnis selbst löschen und neu anlegen. Dann bekommt rm genau ein Argument, das man vorher prüfen kann, und die Shell expandiert nichts. Wer wirklich nur den Inhalt leeren muss, lässt sich vorher mit ls oder echo zeigen, was die Expansion ergibt.

    rm -rf -- "${DIR:?}" && mkdir -p -- "$DIR"
    
  4. 4.

    Nahe an Einhängepunkten --preserve-root=all oder --one-file-system nutzen

    Beim rekursiven Löschen in Verzeichnissen, unter denen etwas eingehängt sein könnte, --preserve-root=all setzen, das nach rm(1) Argumente auf einem fremden Gerät zurückweist, oder --one-file-system, damit rm den Dateisystemwechsel nicht mitmacht. Backup-Laufwerke, Netzfreigaben und USB-Platten liegen fast immer unter einem Verzeichnis, das jemand irgendwann rekursiv aufräumt.

  5. 5.

    Vor dem Ernstfall die Expansion ansehen

    Den geplanten Aufruf mit echo davor oder mit ls statt rm ausführen und lesen, welche Argumente die Shell tatsächlich erzeugt. Eine leere Variable, ein Tippfehler im Pfad oder ein unerwartetes Muster werden dort sichtbar, bevor rm sie bekommt. Was rm einmal hat, gibt es nicht zurück, und die Handbuchseite verspricht nichts anderes.

    echo rm -rf -- "${ZIEL:?}"/*
    

Häufige Fragen (FAQ)

?Vorsicht, Datenverlust: rm -rf / wird abgefangen, rm -rf /* nicht. Warum?

Weil rm das Argument / gar nicht zu sehen bekommt. Die Handbuchseite rm(1) beschreibt den Schutz als Option „--preserve-root[=all]“ mit der Erklärung „do not remove '/' (default)“. Geprüft wird also, ob ein Argument buchstäblich die Wurzel ist. Bei rm -rf /* expandiert die Shell das Muster vorher zu /bin, /etc, /home und so weiter, und jedes dieser Argumente ist für rm ein gewöhnliches Verzeichnis. Der Schutz greift nicht, weil nichts, was rm erhält, / heißt.

?Was passiert bei rm -rf "$ZIEL"/* mit leerem $ZIEL?

Dasselbe. Ist die Variable leer, wird aus "$ZIEL"/* der Ausdruck /*, und die Shell expandiert ihn zur Liste der obersten Verzeichnisse. Weder rm noch die Shell melden etwas Ungewöhnliches, denn für beide ist das ein gültiger Aufruf. Genau dieser Fall ist der Grund, warum Skripte mit set -u beginnen sollten, das auf ungesetzte Variablen mit Abbruch reagiert, und warum rm -rf "${ZIEL:?Ziel leer}"/… die Variable vor der Expansion prüft.

?Gibt es einen schärferen Schutz, der auch eingehängte Laufwerke verschont?

Ja, und er steht in derselben Zeile der Handbuchseite: Mit dem Zusatz all weist die Option jedes Kommandozeilenargument zurück, das auf einem anderen Gerät liegt als sein Elternverzeichnis („with 'all', reject any command line argument on a separate device from its parent“). Damit bleibt ein unter /mnt/backup eingehängtes Laufwerk beim rekursiven Löschen des Elternverzeichnisses verschont. Ohne den Zusatz unterscheidet rm nicht, ob ein Unterverzeichnis auf derselben Platte liegt oder auf dem Sicherungsmedium.

?Wozu gibt es --no-preserve-root überhaupt?

Die Handbuchseite erklärt die Option mit „do not treat '/' specially“. Sie schaltet den Standardschutz ausdrücklich ab und ist damit zugleich der Beweis, dass er im Normalfall aktiv ist. Wer sie setzt, sagt rm, dass die Wurzel ein Verzeichnis wie jedes andere ist. Es gibt sehr wenige Gründe dafür, etwa das Leeren eines chroot-Verzeichnisses, das an dieser Stelle als Wurzel erscheint, und in jedem dieser Fälle sollte vorher eine andere Person draufgeschaut haben.

Belege

Diese Seite ist gegen die folgenden Primärquellen geprüft – Hersteller, Standard oder Gesetzestext. Wer es nicht glaubt, muss es nicht: Da steht, wo es nachzulesen ist.

Zuletzt geprüft am . Dokumentation altert – ein Datum daneben ist der einzige ehrliche Umgang damit.

Auch zu finden unter

Verwandte Fehler

OSLästigArgument list too long

bash: Argument list too long

Der Befehl bricht mit 'Argument list too long' ab, weil ein * zu viele Dateien liefert. So löst du es mit find und xargs statt Globbing.

SWAnfängerfehlerPermission denied

bash: ./script.sh: Permission denied

Shell-Skript startet nicht mit 'Permission denied'? Meist fehlt nur das Ausführbar-Recht. So machst du es lauffähig – und findest seltenere Ursachen.

SWAnfängerfehlerrunning scripts is disabled on this system

PowerShell blockiert die Ausführung von Skripten

Ein Skript startet nicht, weil die Ausführung deaktiviert sei? Das ist kein Defekt, sondern eine Schutzeinstellung — mit bewusst gewähltem Ausweg.