Navigation
 Startseite
 Fachbücher
 Anzeigenmarkt
 Forum
 Webmaster News
 Script Newsletter
 Push Meldungen
 Kontakt
 Script Installation
 Php
 Php Tutorials
 Lernpfade
 Webhoster Vergleich
 Impressum

Community-Bereich
 kostenlos Registrieren
 Anmelden
 Benutzerliste

Script Datenbank
 Script Archiv
 Script Top 20
 Screenshots
 Testberichte

Suche
 

Unsere Php Scripts
 Counter Script
 Umfrage Script
 Bilder Upload Script
 Terminverwaltung
 Simple PHP Forum
 RSS Grabber

Tools und Generatoren
 .htpasswd Generator
 md5 Generator
 base64 Generator
 Markdown to HTML
 Colorpicker
 Unix timestamp Tool
 Unit Test Generator
 TLD Liste
 Webkatalog‑Verzeichnis

Hosterplus.de
Bekommen Sie Speicherplatz (Webspace), Domains...
https://www.Hosterplus.de
Artfiles.de
Bietet Serviceorientierte...
https://www.Artfiles.de
 
 
 

PHP rmdir(): Verzeichnisse rekursiv und sicher löschen

Sie befinden sich: Home > Php Tutorial > PHP rmdir(): Verzeichnisse ...

PHP rmdir(): Verzeichnisse rekursiv und sicher löschen
Veröffentlicht am:
10.09.2026
Hits / Besucher:
6
Sprache:
  Deutsch
Tutorial Art:
eigenes
Eingetragen von:
Merkliste:
 
Beschreibung

Ein Kundenportal legt für jeden Auftrag einen Ordner mit Druckdaten an. Nach dem Abschluss soll er verschwinden, doch der Aufruf meldet nur eine Warnung und tut nichts. Der Ordner ist nicht leer, und genau daran scheitert der erste Versuch fast immer.

Illustration zum Tutorial: PHP rmdir(): Verzeichnisse rekursiv und sicher löschen

Im Bild verschwinden zuerst die Dateien tief im Baum, der äußere Ordner zuletzt. Diese Reihenfolge ist der ganze Trick.

Was PHP rmdir() macht und was nicht

PHP rmdir() entfernt ein Verzeichnis, aber nur dann, wenn es vollständig leer ist. Enthält es auch nur eine versteckte Datei, gibt die Funktion false zurück und meldet eine Warnung. Es gibt keinen Schalter, der das ändert; das Gegenstück zum rekursiven Anlegen fehlt bewusst.

<?php

$ordner = '/var/www/tmp/auftrag-4711';

if (!rmdir($ordner)) {
echo 'Loeschen fehlgeschlagen';
}

/* Warning: rmdir(...): Directory not empty */

Wer Verzeichnisse anlegt, kennt den Unterschied bereits: PHP mkdir() erstellt Verzeichnisse und setzt Berechtigungen und legt mit einem einzigen Schalter ganze Pfade an. Beim Löschen gibt es diesen Schalter nicht, und deshalb braucht jedes Projekt früher oder später eine eigene rekursive Funktion.

Die Signatur im Original

Die Handbuchseite ist kurz, nennt aber die eine Bedingung, an der die Funktion in der Praxis am häufigsten scheitert:

rmdir(string $directory, ?resource $context = null): bool


/* directory - the path of the directory to delete
context - an optional stream context parameter
return - true on success, false on failure

The directory must be empty before using this function. Delete its
contents first, for example with a recursive helper. */

Genau diese Bedingung ist der Grund für den rekursiven Ansatz weiter unten: Ohne ihn scheitert der Aufruf an jedem einzelnen verbliebenen Eintrag.

rmdir() und unlink(): wer löscht was

Bevor der rekursive Teil entsteht, lohnt ein Blick auf die Zuständigkeiten. PHP rmdir() ist ausschließlich für Verzeichnisse zuständig, unlink() ausschließlich für Dateien. Bei symbolischen Verweisen wird es interessant.

Ziel rmdir() unlink()
Datei schlägt fehl löscht
leeres Verzeichnis löscht schlägt fehl
gefülltes Verzeichnis schlägt fehl schlägt fehl
Symlink auf Verzeichnis schlägt unter Linux fehl löscht den Verweis

Die letzte Zeile ist der Grund, warum eine rekursive Funktion ohne Symlink-Prüfung frueher oder später Schaden anrichtet. Wer Dateien einzeln entfernt, findet die Details im Tutorial zu Dateien mit PHP löschen.

Ein Verzeichnis mit Inhalt löschen

Die verständliche Variante liest den Ordnerinhalt mit scandir(), arbeitet ihn ab und ruft PHP rmdir() erst ganz am Ende auf. Wichtig sind die beiden Einträge . und .., die jedes Verzeichnis mitliefert. Wer sie nicht herausfiltert, läuft in eine Endlosschleife.

<?php

function ordnerLoeschen(string $pfad): bool
{
if (!is_dir($pfad)) {
return false;
}

foreach (scandir($pfad) as $eintrag) {
if ($eintrag === '.' || $eintrag === '..') {
continue;
}
$voll = $pfad . DIRECTORY_SEPARATOR . $eintrag;

if (is_link($voll)) {
unlink($voll); /* Verweis entfernen, nicht folgen */
} elseif (is_dir($voll)) {
ordnerLoeschen($voll); /* eine Ebene tiefer */
} else {
unlink($voll);
}
}

return rmdir($pfad);
}

Diese Funktion ist gut lesbar und für überschaubare Verzeichnisse völlig ausreichend. Sie hat allerdings eine Schwachstelle: scandir() baut den kompletten Inhalt als Array im Speicher auf. Bei einem Cache-Verzeichnis mit 200.000 Dateien wird das zum Problem.

Die speicherschonende Variante für große Bäume

Die Standardbibliothek von PHP bringt dafür einen Iterator mit, der den Baum durchläuft, ohne ihn vollständig einzulesen. Entscheidend ist die Konstante CHILD_FIRST: sie sorgt dafür, dass Kinder vor ihren Eltern geliefert werden. Ohne sie käme das Verzeichnis vor seinem Inhalt, und PHP rmdir() würde jedes Mal scheitern.

<?php

function baumLoeschen(string $pfad): bool
{
if (!is_dir($pfad)) {
return false;
}

$eintraege = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
$pfad,
FilesystemIterator::SKIP_DOTS
),
RecursiveIteratorIterator::CHILD_FIRST
);

foreach ($eintraege as $eintrag) {
if ($eintrag->isLink() || $eintrag->isFile()) {
unlink($eintrag->getPathname());
} else {
rmdir($eintrag->getPathname());
}
}

return rmdir($pfad);
}

FilesystemIterator::SKIP_DOTS erledigt die Filterung der beiden Sondereinträge gleich mit. Der Speicherbedarf bleibt bei dieser Variante praktisch konstant, unabhängig davon, ob hundert oder hunderttausend Dateien im Baum liegen.

Warum "Permission denied" am falschen Ort steht

Ein Skript läuft lokal und auf dem Server nicht. Die Fehlermeldung nennt den Pfad des Verzeichnisses, und genau dort sucht man dann vergeblich. Der Grund: zum Entfernen eines Verzeichnisses braucht der Prozess Schreibrecht am übergeordneten Verzeichnis, nicht am Verzeichnis selbst. Das Verzeichnis ist ein Eintrag in seinem Elternordner, und dieser Eintrag wird gelöscht.

<?php

$ziel = '/var/www/tmp/auftrag-4711';

if (!is_writable(dirname($ziel))) {
echo 'Kein Schreibrecht im uebergeordneten Verzeichnis';
} elseif (!rmdir($ziel)) {
echo 'Loeschen fehlgeschlagen: ' . (error_get_last()['message'] ?? '');
}

Der Aufruf von error_get_last() ersetzt die verbreitete Unsitte, die Warnung mit einem vorangestellten Klammeraffen zu unterdrücken. Die unterdrückte Warnung verschwindet zwar aus dem Protokoll, das Problem bleibt aber bestehen und fällt erst später auf.

Symlinks nicht verfolgen

Zeigt im Baum ein symbolischer Verweis auf ein anderes Verzeichnis, und steigt die rekursive Funktion dort hinein, räumt sie Daten ab, die gar nicht zum Zielordner gehören. Beide Beispiele oben prüfen deshalb mit is_link(), bevor sie absteigen. Ein Verweis wird mit unlink() entfernt, nicht mit PHP rmdir(), auch wenn er auf ein Verzeichnis zeigt.

<?php

$verweis = '/var/www/tmp/auftrag-4711/vorlagen';

var_dump(is_link($verweis)); // bool(true)
var_dump(is_dir($verweis)); // bool(true), folgt dem Verweis

/* richtig: */
unlink($verweis);

Die beiden ersten Ausgaben zeigen die Falle: is_dir() folgt dem Verweis und meldet ein Verzeichnis. Wer nur darauf prüft, steigt ab. Deshalb steht die Prüfung auf is_link() in der Reihenfolge immer zuerst.

Pfade aus Benutzereingaben absichern

Sobald ein Teil des Pfades aus einem Formular, einer URL oder einer Konfiguration kommt, muss er geprüft werden, bevor er an das Dateisystem geht. Ein ../ reicht, um aus dem vorgesehenen Bereich herauszuführen. Die Absicherung besteht aus zwei Schritten: auflösen und vergleichen.

<?php

$basis = realpath('/var/www/tmp');
$roh = $basis . '/' . ($_POST['ordner'] ?? '');
$ziel = realpath($roh);

if ($ziel === false || !str_starts_with($ziel, $basis . DIRECTORY_SEPARATOR)) {
exit('Ungueltiger Pfad');
}

baumLoeschen($ziel);

realpath() löst .. und symbolische Verweise vollständig auf und liefert false, wenn der Pfad nicht existiert. Erst der Vergleich mit dem Basispfad einschließlich Trennzeichen macht die Prüfung dicht; ohne das Trennzeichen würde auch ein Ordner namens tmp-alt durchgehen.

Die folgende Übersicht fasst die Reihenfolge zusammen, in der ein Eintrag geprüft und behandelt wird.

flowchart TD
  A[Pfad pruefen] --> B{is_link}
  B -->|ja| C[unlink, nicht absteigen]
  B -->|nein| D{is_dir}
  D -->|nein| E[unlink]
  D -->|ja| F[Inhalt durchlaufen]
  F --> G[Kinder zuerst loeschen]
  G --> H[rmdir auf Verzeichnis]

Der Ablauf zeigt, warum die Symlink-Prüfung ganz oben steht und nicht erst nach der Verzeichnisprüfung.

Wenn scheinbar nichts passiert

Manchmal meldet ein Skript Erfolg, und der Ordner ist trotzdem noch da, oder umgekehrt. Drei Ursachen decken die meisten Fälle ab.

Erstens der Stat-Cache: PHP merkt sich Informationen über Dateien innerhalb einer Anfrage. Eine Prüfung mit file_exists() direkt nach dem Löschen kann deshalb noch true liefern. clearstatcache() leert den Zwischenspeicher.

<?php

rmdir('/var/www/tmp/auftrag-4711');

var_dump(file_exists('/var/www/tmp/auftrag-4711')); // evtl. bool(true)

clearstatcache();
var_dump(file_exists('/var/www/tmp/auftrag-4711')); // bool(false)

Zweitens der nicht geprüfte Rückgabewert. PHP rmdir() liefert einen Wahrheitswert, und wer ihn ignoriert, erfährt vom Fehlschlag nichts. Drittens offene Handles unter Windows: dort blockiert eine noch geöffnete Datei das Löschen des Verzeichnisses. Unter Linux fällt das nicht auf, weil der Verzeichniseintrag sofort verschwindet. Die Reihenfolge lautet also fclose(), dann unlink(), dann PHP rmdir().

Ein Aufräumhelfer für Tests

In Testfällen wird derselbe temporäre Ordner immer wieder angelegt und entfernt. Eine Hilfsfunktion sollte deshalb mehrfach aufrufbar sein, ohne beim zweiten Mal zu scheitern.

<?php

function aufraeumen(string $pfad): void
{
if (!file_exists($pfad)) {
return; /* schon weg, kein Fehler */
}
baumLoeschen($pfad);
clearstatcache(true, $pfad);
}

Ob ein Pfad überhaupt existiert und was er ist, klärt das Tutorial zu Datei- und Verzeichnisprüfung mit PHP im Detail.

Fazit

PHP rmdir() entfernt genau eine Sache: ein leeres Verzeichnis. Alles darüber hinaus ist Eigenarbeit, und die besteht aus drei Entscheidungen. Die Reihenfolge muss von innen nach außen laufen, symbolische Verweise dürfen nicht verfolgt werden, und Pfade aus fremder Hand gehören mit realpath() geprüft.

Für kleine Ordner reicht die scandir()-Variante, für große Bäume nimmt man den Iterator mit CHILD_FIRST. Und wenn PHP rmdir() trotz allem fehlschlägt, lohnt der Blick auf das übergeordnete Verzeichnis, nicht auf das Ziel selbst.

 

Tags:

 

 

Kommentare (0)

Noch keine Kommentare. Sei der Erste!

Melde dich an, um einen Kommentar zu schreiben.
Bücherregal mit drei Büchern: 'PHP 4 - Grundlagen und Profiwissen' von Hanser Verlag, 'Webdesign in a Nutshell' von O'Reilly Verlag, und 'Webgestaltung' von Galileo Computing.