Bastian: Verzeichnis leeren

Hallo,

ich möchte ein verzeichnis mittels php leeren, aber es wird nicht geleert, obwohl mir die Dateien aufgelistet werden.

Woran liegt das?

Bastian

// Verzeichnis leeren
$dir = dirname(__FILE__) .'/ordner';
foreach(scandir($dir) as $file) {
    if($file === ".." or $file === ".") {
        continue;
    }
    echo $file."<br>";
    unlink(dirname(__FILE__) .$file);
}
  1. ich möchte ein verzeichnis mittels php leeren, aber es wird nicht geleert, obwohl mir die Dateien aufgelistet werden.

    Woran liegt das?

    Woran es tatsächlich liegt, sollte Dir eine vernünftige Fehlerbehandlung beim unlink aufzeigen können.

    Wenn ich raten müsste: die Dateien gehören einem anderen User als dem, unter dem der PHP-Prozess läuft.

    1. Wenn ich raten müsste: die Dateien gehören einem anderen User als dem, unter dem der PHP-Prozess läuft.

      Oder der Benutzer unter dem PHP läuft, hat keine Schreibrechte am Verzeichnis, in dem diese Dateien sind. Aber wahrscheinlich ist es das, was Robert entdeckt hat. Denn dieser Fehler führt zwingend zu dem Problem...

    2. Hallo Mitleser,

      Wenn ich raten müsste: die Dateien gehören einem anderen User als dem, unter dem der PHP-Prozess läuft.

      Danke für den Hinweis, aber ich verwende es gerade auf einem Win-System.

      Bastian

  2. Moin Bastian,

    ich möchte ein verzeichnis mittels php leeren, aber es wird nicht geleert, obwohl mir die Dateien aufgelistet werden.

    Werden die Dateien auch nach dem Löschen noch aufgelistet?

    Woran liegt das?

    In dem Fall würde „die Dateien“ nicht gelöscht – üblicherweise gibt es dafür eine Fehlermeldung.

    // Verzeichnis leeren
    $dir = dirname(__FILE__) .'/ordner';
    foreach(scandir($dir) as $file) {
        if($file === ".." or $file === ".") {
            continue;
        }
        echo $file."<br>";
        unlink(dirname(__FILE__) .$file);
    }
    

    Du iterierst über dirname(__FILE__) . '/ordner, löscht allerdings dann in dirname(__FILE__). Passt die Fehlermeldung/der Rückgabewert von unlink dazu?

    Viele Grüße
    Robert

    1. Hallo Robert,

      Du iterierst über dirname(__FILE__) . '/ordner, löscht allerdings dann in dirname(__FILE__). Passt die Fehlermeldung/der Rückgabewert von unlink dazu?

      Danke für den Hinweis, ich hatte tatsächlich ein Pfadproblem. So lauft und löscht es:

      // Verzeichnis leeren
      $dir = dirname(__FILE__) .'/ordner';
      foreach(scandir($dir) as $file) {
          if($file === ".." or $file === ".") {
              continue;
          }
          echo $file."<br>";
          unlink($dir."/".$file);
      }
      

      Bastian

      1. Danke für den Hinweis, ich hatte tatsächlich ein Pfadproblem.

        Du hast noch ein weiteres Pfadproblem

        So lauft und löscht es:

        Nein! Nicht wirklich. Bitte lese den zweiten Teil zum Ordner '/ordner':

        https://forum.selfhtml.org/self/2022/may/13/verzeichnis-leeren/1799095#m1799095

    2. Hi,

          unlink(dirname(__FILE__) .$file);
      

      Du iterierst über dirname(__FILE__) . '/ordner, löscht allerdings dann in dirname(__FILE__).

      ich hätte gesagt, noch eine Ebene weiter oben - denn dirname liefert m.W. den Pfad ohne abschließenden '/'.

      unlink(dirname("/bla/blubb/laber/dies.php")."datei"); versucht daher, die laberdatei aus /bla/blubb zu löschen, nicht die datei aus /bla/blubb/laber.

      cu,
      Andreas a/k/a MudGuard

    3. Moin Bastian,

      ich möchte ein verzeichnis mittels php leeren, aber es wird nicht geleert, obwohl mir die Dateien aufgelistet werden.

      Werden die Dateien auch nach dem Löschen noch aufgelistet?

      Woran liegt das?

      In dem Fall würde „die Dateien“ nicht gelöscht – üblicherweise gibt es dafür eine Fehlermeldung.

      // Verzeichnis leeren
      $dir = dirname(__FILE__) .'/ordner';
      foreach(scandir($dir) as $file) {
          if($file === ".." or $file === ".") {
              continue;
          }
          echo $file."<br>";
          unlink(dirname(__FILE__) .$file);
      }
      

      Du iterierst über dirname(__FILE__) . '/ordner, löscht allerdings dann in dirname(__FILE__). Passt die Fehlermeldung/der Rückgabewert von unlink dazu?

      Viele Grüße
      Robert

      Ok. Die Lösung demnach wohl:

      // Verzeichnis leeren
      $dir = dirname(__FILE__) .'/ordner/';
      foreach(scandir($dir) as $file) {
           if($file === ".." or $file === ".") {
               continue;
           }
           echo $file."<br>";
           unlink( $dir . $file);
      }
      

      ABER!

      Schon der Ordner 'ordner' gefällt mir nicht.

      Erzeuge den für jeden Benutzer neu:

      <?php
      ## Diese Funktion legt ein eindeutig benanntes temporäres Verzeichnis
      ## an und gibt im Erfolgsfall den Name zurück, sonst false
      ## Parameter $errorType = siehe https://www.php.net/manual/de/errorfunc.constants.php
      ## Parameter $baseDir: Ein Verzeichnis in dem das Verzeichnis angelegt wird. Wenn nicht übergeben, leer oder falsy wird das TMP-Verzeichnis des Systems benutzt.
      
      function mkTempDir(
          $errorType = E_USER_ERROR,
          $baseDir   = ''
      ) {
      
          if ( ! $baseDir ) {
              $tmpdirName = sys_get_temp_dir() . '/' . uniqid( true );
          } else {
              $tmpdirName = $baseDir  . '/' . uniqid();
          }
          if ( mkdir( $tmpdirName ) ) {
              return true;
          } else {
              trigger_error (
                  "Das Verzeichnis '$tmpdirName' konnte nicht angelegt werden",             
                  $errorType
              );
              return $tmpdirName;
          }
      }
      
      ### Usage:
      # In den meisten Fällen wird derlei über
      # mehrere Skripte hinweg benötigt. Deshalb:
      session_start();
      $_SESSION['tmpdir'] = mkTempDir();
      
      ?>
      

      Machst Du das mit dem statischen Ordnername und zwei Benutzer agieren über Kreuz, dann löscht oder überschreibt der eine Benutzer die Dateien des anderen. Oder der andere bekommt die Rechnungen des einen...

      1. Hallo Willi,

        Machst Du das mit dem statischen Ordnername und zwei Benutzer agieren über Kreuz, dann löscht oder überschreibt der eine Benutzer die Dateien des anderen. Oder der andere bekommt die Rechnungen des einen...

        Ich würde Dir Recht geben, aber das script läuft einmal/Monat oder je Woche über cron. Für User ist es nicht gedacht.

        Es gibt für User aber ebenfalls solche Verzeichnisse, die aber werden dann mit Username und uniqueID generiert.

        Bastian

        1. Ich würde Dir Recht geben, aber das script läuft einmal/Monat oder je Woche über cron.

          Mach es trotzdem RICHTIG. Wie der Zufall es will wird das Skript durch einen Bedienfehler / Angriff / blöden Zufall zweimal zeitnah gestartet und dann hast Du falsche Ergebnisse.

          1. Ich würde Dir Recht geben, aber das script läuft einmal/Monat oder je Woche über cron.

            Mach es trotzdem RICHTIG. Wie der Zufall es will wird das Skript durch einen Bedienfehler / Angriff / blöden Zufall zweimal zeitnah gestartet und dann hast Du falsche Ergebnisse.

            Nagut.
            Hast ja recht, ich ändere das.
            Kann ja auch sein, dass man mal Code aus einem vorhandenen Script kopiert und dieser dann für einen anderen Zweck genutzt wird.

            Bastian

            1. Nagut.

              Zur Belohnung noch schnell die Funktion zum rekursiven Löschen eines ganzen Verzeichnisses:

              <?php
              
              ### rmdirRecursive löscht ein Verzeichnis rekursiv.
              ## Parameter:
              ## $dirName : Verzeichnis
              ## $verbose : true - es wird ein Array mit allen gelöschten
              ##                   Dateien retourniert
              ## $verbose : false - es wird nur der Erfolg (true/false) geliefert.
              ## $errorType : siehe https://www.php.net/manual/de/errorfunc.constants.php
              
              
              function rmdirRecursive(
              	$dirName,
              	$verbose = false,
              	$errorType = E_USER_ERROR
              ) {
              	if ( ! is_dir( $dirName ) ) {
              		trigger_error ( "'$dirName' existiert nicht oder ist kein Verzeichnis.", $errorType );
              		return false;
              	}
              	
              	if ( $verbose ) {
              		$return = [];
              	} else {
              		$return = true;
              	}
              
              	foreach( scandir( $dirName ) as $item ) {
              		if( $item === ".." or $item === ".") {
              			continue;
              		} else {
              			$dso = $dirName . '/' . $item;
              			if ( 
              				is_file( $dso ) 
              				or is_link( $dso )
              			) {
              				if ( ! unlink( $dso ) ) {
              					trigger_error ( "'$dso' konnte nicht gelöscht werden." );
              					return false;
              				} else {
              					$return[] = $dso;
              				}
              			} else {
              				$r = rmdirRecursive( $dso, $verbose, $errorType );
              				if ( false === $r ) {
              					trigger_error ( "'$dso' konnte nicht gelöscht werden." );
              					return false;				   
              				} else {
              					if ( $verbose )  {
              						if ( is_array( $r ) ) {
              							foreach ( $r as $i ) {
              								$return[] = $i;
              							}
              						}
              					}
              				}
              			}
              		}
              	}
              	if ( rmdir( $dirName ) ) {
              		if ( $verbose ) {
              			$return[] = $dirName;
              		}
              		return $return;
              	} else {
              		trigger_error ( "'$dirName' konnte nicht gelöscht werden.", $errorType );
              		return false;		
              	}
              }
              
              ## Usage example:
              # print_r( rmdirRecursive( '/tmp/a', true, E_USER_ERROR ) );
              
              1. Nagut.

                Zur Belohnung noch schnell die Funktion zum rekursiven Löschen eines ganzen Verzeichnisses:

                Hallo Willi,

                das ist aber nett. 😉

                Ist das besser als der bei Euch verlinkte Artikel zu dem Thema? Egal, danke auf jeden Fall!

                Bastian

                1. Ist das besser als der bei Euch verlinkte Artikel zu dem Thema?

                  Ich will nicht derjenige sein, der „Ja“ sagt. Aber meine Fehlerbehandlung gefällt mir besser…

                  1. Ich will nicht derjenige sein, der „Ja“ sagt. Aber meine Fehlerbehandlung gefällt mir besser…

                    😅👍

                    Na dann mal ran an den "Edit" 😉

                    1. Na dann mal ran an den "Edit" 😉

                      Ich sehe jetzt offengestanden keinen Grund, der mir so wichtig wäre, als das ich das Skript im Wiki ersetzen würde. Da steht immerhin ein Autor dahinter, der sich „gepiesackt“ fühlen könnte wenn ich das mache nur weil ich der Meinung bin, dass man dieses oder jenes anders machen kann.

                      Für das, was Rolf geschrieben hat, hat sich mein Gehirn noch nicht dazu durchgerungen, einen „use case“ zu phantasten. (Das heisst jetzt aber nicht, dass Rolf Unrecht hat! Ich hab's nur noch nicht in einem Gedankenexperiment nachvollzogen.)

                      Außerdem ist das Skript ein Schnellschrieb. (Hehe: Ich habe tatsächlich alle 54 Zeilen auf Anhieb ohne Syntaxfehler notiert und hatte nur einen logischen Fehler drin - das war ein falscher Vergleich.)

                      Der „Schnellschrieb“ verdient außerdem eh noch kleine Optimierungen bezüglich der Notation. Das würde sicherlich auf Kritik stoßen.

                      1. Hallo,

                        Na dann mal ran an den "Edit" 😉

                        Ich sehe jetzt offengestanden keinen Grund, der mir so wichtig wäre, als das ich das Skript im Wiki ersetzen würde. Da steht immerhin ein Autor dahinter, der sich „gepiesackt“ fühlen könnte wenn ich das mache nur weil ich der Meinung bin, dass man dieses oder jenes anders machen kann.

                        nein, überhaupt nicht. Es ist ein Wiki. Und wiki-wiki bedeutet nicht nur schnell, sondern im heutigen Verständnis auch, dass jeder zu einem Artikel beitragen kann. Go ahead!

                        Außerdem ist das ein Schnellschrieb. (Hehe: Ich habe tatsächlich alle 54 Zeilen auf Anhieb ohne Syntaxfehler notiert und hatte nur einen logischen Fehler drin - das war ein falscher Vergleich.)

                        Okay, das ist ein Hinweis auf Übung und Routine.

                        Der „Schnellschrieb“ verdient außerdem eh noch kleine Optimierungen bezüglich der Notation.

                        Das kann man ja nach und nach noch verfeinern.
                        Und dazu können dann auch andere außer dir noch beitragen.

                        Einen schönen Tag noch
                         Martin

                        --
                        Мир для України.
                        1. Na gut. Das schon lange existierende Skript hat aus der Mode geratene Eigenheiten, speziell die Fehlerunterdrückung mit @befehl „gefällt 2022 nicht mehr jedem“.

                          Das gab mir Anlass über meine Version nochmal nachzudenken.

                          Die Rückgabe der gelöschte Dateien und Verzeichnisse als Array hat sich dabei als Unsinn erwiesen, das braucht niemand - vor allem weil es bei einem Fehler nicht erfolgte. Eine umfassende Speicherung in einem Array würde die Sache unnötig aufblähen und die Auswertung der Rückgabe zu kompliziert machen. Das will auch kaum jemand ...

                          Allerdings habe ich in die neue Version eine Möglichkeit eingebaut, diese Löschvorgänge (und den Erfolg) ggf. als E_USER_NOTICE zu loggen.

                          Die eigentliche Funktion ist sogar deutlich kürzer als vorher… ich hoffe, sie ist brauchbar.

                          Ich werde das aus $Gründen aber nicht einfach mal ins Wiki setzen.

                          <?php
                          
                          /** function rmdirRecursive löscht ein Verzeichnis rekursiv.
                          * Parameter:
                          *   $dirName   : zu löschendes Verzeichnis
                          *   $logDelete : true ->  Erfolg (gelöschte Dateien) wird geloggt.
                          *              : false -> Gelöschte Dateien) nicht loggen.
                          *              : default = false
                          *   $errorType : siehe https://www.php.net/manual/de/errorfunc.constants.php
                          *              : sinnvoller default =  E_USER_ERROR
                          */
                          
                          function rmdirRecursive (
                          	$dirName,
                          	$logDelete = false,
                          	$errorType = E_USER_ERROR
                          ) {
                          	if ( ! is_dir( $dirName ) ) {
                          		trigger_error (
                                    "'$dirName' existiert nicht oder ist kein Verzeichnis.",
                                    $errorType
                          		);
                          		return false;
                          	}
                          
                          	foreach( scandir( $dirName ) as $item ) {
                          		
                          		if ( 
                          			   '..' == $item
                          			or '.'  == $item
                          		) continue; 
                          		
                          		$dso = $dirName . '/' . $item;
                          		if ( 
                          			   is_file( $dso ) 
                          			or is_link( $dso )
                          		) {
                          			if ( ! unlink( $dso ) ) {
                          				trigger_error (
                          					"Datei '$dso' konnte nicht gelöscht werden.",
                          					$errorType
                          				);
                          				return false;
                          			} elseif ( $logDelete )  {
                          				trigger_error (
                          					"Datei '$dso' wurde gelöscht",
                          					E_USER_NOTICE
                          				);
                          			}
                          		} else {
                          			$r = rmdirRecursive( $dso, $logDelete, $errorType );
                          			if ( false === $r ) {
                          				trigger_error (
                          					"Datei '$dso' konnte nicht gelöscht werden.",
                          					$errorType
                          				);
                          				return false;				   
                          			} 
                          		}
                          	}
                          	if ( rmdir( $dirName ) ) {
                          		if ( $logDelete ) {
                          			trigger_error (
                          				"Verzeichnis '$dirName' wurde gelöscht",
                          				E_USER_NOTICE
                          			);
                          		}
                          		return true;
                          	} else {
                          		trigger_error (
                          			"Verzeichnis '$dirName' konnte nicht gelöscht werden.",
                          			$errorType
                          		);
                          		return false;		
                          	}
                          }
                          
                          ### Usage example:
                          
                          ## 1. Dateien und Verzeichnisse erzeugen (Linux)
                          # /*
                          exec("
                          mkdir /tmp/a;
                          mkdir /tmp/a/a;
                          touch /tmp/a/a/a;
                          touch /tmp/a/a/.a;
                          touch '/tmp/a/a/a a';
                          mkdir /tmp/a/b;
                          touch /tmp/a/b/a;
                          touch /tmp/a/b/b;
                          touch /tmp/a/b/c;
                          mkdir /tmp/a/c;
                          ");
                          # */
                          
                          ## 2. Error-Reporting optimieren
                          #error_reporting ( error_reporting() &~ E_USER_NOTICE );
                          error_reporting ( E_ALL );
                          ini_set( 'display_errors', 0);
                          
                          ## 3. Das Zeug wieder löschen
                          if ( rmdirRecursive( '/tmp/a', true, E_USER_ERROR ) ) { 
                          	echo 'Erfolgreich gelöscht' . PHP_EOL;
                          } else  {
                          	echo 'Fehler. Sehen Sie im Error-Log nach.' . PHP_EOL;
                          }
                          
                          1. Hallo Raketenwilli,

                            deine Lösung ist "klassisch" rekursiv ausprogrammiert und würde vermutlich auch in PHP 4 funktionieren. Es gibt aber ein paar Schätze in PHP, die nicht so bekannt sind.

                            Ist jetzt ungetestet zusammengehauen, ist schlechter lesbar und tut auch weniger als deins. Ich möchte nur zwei Ideen zeigen: Die Iteratorklassen und die Verwendung von anonymen Funktionen (oder Pfeilfunktionen) um Code ein- und auszuschalten.

                            $rd = new RecursiveIteratorIterator(
                                     new RecursiveDirectoryIterator("D:\phpweb")
                                  );
                            
                            if ($logDelete)
                            {
                              // Moderner: Pfeilfunktion
                              $logSuccess = fn($what,$name) => trigger_error("$what '$name' wurde gelöscht", E_USER_NOTICE);
                            }
                            else
                            {
                              // Etwas klassischer: Anonyme Funktion
                              $logSuccess = function($what,$name) { return false; };
                            }
                            
                            // Verzeichnisinhalt einsammeln
                            $fileInfos = [];
                            foreach ($rd as $fileInfo)
                            {
                               $fileInfos[] = $rd;
                            }
                            
                            // Trefferliste rückwärts abarbeiten, weil "." und ".." je Directory als
                            // erstes gelistet werden. Damit kann man bei Erreichen von "." einfach
                            // den Ordner löschen
                            for ($i = count($fileInfos)-1; $i >= 0; $i--) {
                               if ($fileInfos[$i]->isFile() || $fileInfos[$i]->isLink())
                               {
                                  unlink($fileInfos[$i]->getFilename());
                                  // Todo: Check+Log Error
                                  $logSuccess('Datei', $fileInfos[$i]->getFilename());
                               }
                               elseif ($fileInfos[$i]->getFilename() == ".")
                               {
                                  rmdir($fileInfos[$i]->getPath());
                                  // Todo: Check+Log
                                  $logSuccess('Verzeichnis', $fileInfos[$i]->getPath());
                               }
                            }
                            

                            Rolf

                            --
                            sumpsi - posui - obstruxi
                            1. elseif ($fileInfos[$i]->getFilename() == ".")
                              

                              Also „wenn schon denn schon“:

                              elseif ( $fileInfos[$i]->isDot() ) 
                              

                              😀

                            2. Nach einigem Wühlen im Handbuch und kleineren Korrekturen hatte ich das Skript so weit, dass es hätte laufen müssen ... tat es aber nicht.

                              Warning: rmdir(/tmp/a): Directory not empty 
                              …
                              Warning:  rmdir(/tmp/a/b): Directory not empty
                              

                              Ursache:

                              <?php
                              #/* 
                              exec("
                              mkdir /tmp/a;
                              mkdir /tmp/a/a;
                              touch /tmp/a/a/a;
                              touch '/tmp/a/a/a a';
                              touch /tmp/a/a/.a;
                              mkdir /tmp/a/b;
                              touch /tmp/a/b/a;
                              touch /tmp/a/b/b;
                              touch /tmp/a/b/c;
                              mkdir /tmp/a/c;
                              ");
                              # */
                              
                              	// Verzeichnisinhalt einsammeln
                              	$fileInfos = [];
                              	$rd = new RecursiveIteratorIterator(
                              			 new RecursiveDirectoryIterator( '/tmp/a' )
                              		  );
                                  $i=0;
                              	foreach ( $rd as $fileInfo ) {
                              		$fileInfos[] = $fileInfo;
                              		echo $i++ . ": " .  $fileInfo ->getPathname() . PHP_EOL;
                              	}
                              

                              Ausgabe:

                              0: /tmp/a/c/.
                              1: /tmp/a/c/..
                              2: /tmp/a/b/c
                              3: /tmp/a/b/b
                              4: /tmp/a/b/.
                              5: /tmp/a/b/a
                              6: /tmp/a/b/..
                              7: /tmp/a/.
                              8: /tmp/a/a/.
                              9: /tmp/a/a/a a
                              10: /tmp/a/a/.a
                              11: /tmp/a/a/a
                              12: /tmp/a/a/..
                              13: /tmp/a/..
                              

                              Deine Annahmen (Das Handbuch sagt nichts dazu) über die Sortierung des RecursiveDirectoryIterator sind also unzutreffend. Tatsächlich ist diese „zufällig“. Einen Parameter oder eine Methode zum Sortieren hab ich nicht gefunden, die müsste geschrieben werden.

                              Ich vermute, wenn ich das umbaue, geht jeder Vorteil der Nutzung der Objekte verloren.

                              Getestet mit PHP 8.1.2 (cli) (built: Apr 7 2022 17:46:26) (NTS) (Armbian/Ubuntu 22.04 LTS auf dem Raspi400, also aarch64)

                              1. Gesagt:

                                Einen Parameter oder eine Methode zum Sortieren hab ich nicht gefunden, die müsste geschrieben werden

                                Getan:

                                	// Verzeichnisinhalt einsammeln
                                	$fileInfos = [];
                                	$rd = new RecursiveIteratorIterator(
                                			 new RecursiveDirectoryIterator( $dirName )
                                		  );
                                    $i=0; $dtos=[];
                                	foreach ( $rd as $fileInfo ) {
                                		$fileInfos[$fileInfo ->getPathname()] = $fileInfo;
                                		$dtos[] = $fileInfo ->getPathname();
                                	}
                                	asort( $dtos );
                                	$dtos = array_reverse( $dtos );
                                	print_r( $dtos ); exit;
                                

                                Das liefert dann endlich brauchbares:

                                    Fund                 # Aktion
                                    ######################################
                                    [0] => /tmp/a/c/..   # Ignorieren
                                    [1] => /tmp/a/c/.    # rmdir /tmp/a/c
                                    [2] => /tmp/a/b/c    # Löschen
                                    [3] => /tmp/a/b/b    # Löschen
                                    [4] => /tmp/a/b/a    # Löschen
                                    [5] => /tmp/a/b/..   # Ignorieren
                                    [6] => /tmp/a/b/.    # rmdir /tmp/a/b
                                    [7] => /tmp/a/a/a a  # Löschen
                                    [8] => /tmp/a/a/a    # Löschen
                                    [9] => /tmp/a/a/.a   # Löschen
                                    [10] => /tmp/a/a/..  # Ignorieren
                                    [11] => /tmp/a/a/.   # rmdir /tmp/a/a/
                                    [12] => /tmp/a/..    # Ignorieren
                                    [13] => /tmp/a/.     # rmdir /tmp/a
                                

                                Jetzt könnte ich über die sortierten $dtos das richtige Objekt aus $fileInfos rausziehen, ::getFilename() untersuchen, (wenn '..') das Item ignorieren oder (wenn '.') das leere Verzeichnis darunter löschen oder die Datei löschen.

                                Nur habe ich da ja schon den Dateiname.

                                Fazit: Der Schatz [RecursiveDirectoryIterator](https://www.php.net/manual/de/class.recursivedirectoryiterator.php) ist einer. Aber halt dann, wenn ich mehr und andere Informationen verarbeiten will - zum Löschen ist dessen Nutzung zu viel Aufwand: Quasi der Versuch, mit einem 1000PS-Traktor und einer riesigen Maschine Nadeln aus einem Heuhaufen zu ziehen, sie dabei zu vermessen und deren Hersteller, Farbe, Material, Härtegrad zu bestimmen - wobei aber doch eigentlich der Heuhaufen nur von Nadeln befreit werden soll... was ein billiger Magnet ratzfatz erledigt.

                                1. Moin,

                                  Gesagt:

                                  Einen Parameter oder eine Methode zum Sortieren hab ich nicht gefunden, die müsste geschrieben werden

                                  Getan: […]

                                  das geht auch einfacher: RecursiveIteratorIterator und RecursiveDirectoryIterator kennen ganz nützliche Flags als zweiten Parameter im Konstruktor. Wenn man über dein mit exec angelegtes Testverzeichnis den Code:

                                  new RecursiveIteratorIterator(
                                      new RecursiveDirectoryIterator(
                                          '/tmp/a',
                                          FilesystemIterator::SKIP_DOTS
                                      ),
                                      RecursiveIteratorIterator::CHILD_FIRST
                                  );
                                  

                                  laufen lässt, bekommt man als Ausgabe von getPathname() diese Liste der Verzeichnisse und Dateien:

                                  0: /tmp/a/b/b
                                  1: /tmp/a/b/c
                                  2: /tmp/a/b/a
                                  3: /tmp/a/b
                                  4: /tmp/a/c
                                  5: /tmp/a/a/a a
                                  6: /tmp/a/a/.a
                                  7: /tmp/a/a/a
                                  8: /tmp/a/a
                                  

                                  Die Einträge sind dann gleich so sortiert dass man jeden Eintrag einfach löschen kann, wenn man zum Elternverzeichnis kommt, ist dieses bereits leer – nur /tmp/a selbst bleibt dann noch übrig.

                                  Gruß
                                  Tobias

                                  1. Hallo tk,

                                    CHILD_FIRST ist super. Das hatte ich nicht gesehen und das macht die Zwischenspeicherung in einem Array unnötig. Top!

                                    Rolf

                                    --
                                    sumpsi - posui - obstruxi
                                    1. CHILD_FIRST ist super. Das hatte ich nicht gesehen und das macht die Zwischenspeicherung in einem Array unnötig.

                                      Auch wenn Du es nicht selbst (durch notierten Code) tust - im Hintergrund wird die Klasse RecursiveIteratorIterator genau das sehr wohl tun. Immerhin nimmt die ja beim Construct genau einen solchen Array bzw. ein Traversable entgegen - den der FilesystemIterator ergo auch liefern bzw. sein muss. Aber spätestens für die intern stattfindende Sortierung wird es wohl doch ein Array sein.

                                      Man sollte womöglich den Speicherverbrauch und die Performance nicht ganz aus den Augen verlieren: Immerhin ist der FilesystemIterator eine „eierlegende Wollmilchsau“ und dürfte einigen Aufwand betreiben um die vielen gebotenen, aber hier nutzlosen Informationen zu erlangen. (Klar könnte man einen Hifetext ins Error-Log schreiben der bis runter zum Inode alle Informationen gibt, warum sich eine Datei oder ein Verzeichnis nicht löschen ließ...wobei der FilesystemIterator dann aber an acl, erst recht an den „Ausführungsverhinderern“ und readonly gemounteten Dateisystemen scheitert - die kennt er nicht. )

                                      Und wenn ich dann sehe, wie lange das ganze Gewühle im Handbuch dauert um meine 50 Zeilen „primitiven Code“ zu ersetzen kann ich auch nur ein „Naja. Mancher will es so…“ seufzen.

                                      Das es ja um das Wiki ging:

                                      Interessanter und sinnvoller wäre es vielleicht zu zeigen, wie man den „Spaß“ auf Verzeichnisse begrenzt, die ein Kindverzeichnis eines temporären Verzeichnisses sind. (Etwas wie "/tmp/" oder "/temp/" im Pfadname haben.)

                                      1. Das es ja um das Wiki ging:

                                        Interessanter und sinnvoller wäre es vielleicht zu zeigen, wie man den „Spaß“ auf Verzeichnisse begrenzt, die ein Kindverzeichnis eines temporären Verzeichnisses sind. (Etwas wie "/tmp/" oder "/temp/" im Pfadname haben.)

                                        Denn mit einem temporären Verzeichnis begann ja die Diskussion um das Löschen von Verzeichnissen – und das dürfte in PHP der häufigste Grund sein, warum man das per Skript tut.

                          2. Jetzt bekomme ich „Haue“.😇

                            Weil bei Massenhostern exec verboten sein könnte.

                            <?php
                            function rmdirRecursiveBrutal(
                            	$dir,
                            	$errorType = E_USER_ERROR
                            ) {
                            
                                if ( is_dir( $dir ) ) {
                                    if ( strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN') {
                                        exec ( 'rmdir ' . escapeshellarg( $dir ) . ' /s', $dummy, $result );
                                    } else {
                                        exec ( 'rm -rf ' . escapeshellarg( $dir ), $dummy, $result);
                                    }
                                    if  (  0 === $result ) {
                            			return true;
                            		} else {
                            			trigger_error( 
                            				"'$dir' konnte nicht gelöscht werden.",
                            				$errorType
                            			);
                            			return false;
                            		}
                                } else {
                                    trigger_error( 
                                        "'$dir' existiert nicht oder ist kein Verzeichnis.",
                                        $errorType
                                    );
                                }
                            }
                            

                            Hinweis: MacOS, Linux, *BSD und alle mir bekannten Unixe kennen rm -rf.

              2. Moin,

                Zur Belohnung noch schnell die Funktion zum rekursiven Löschen eines ganzen Verzeichnisses:

                <?php
                
                ### rmdirRecursive löscht ein Verzeichnis rekursiv.
                ## Parameter:
                ## $dirName : Verzeichnis
                ## $verbose : true - es wird ein Array mit allen gelöschten
                ##                   Dateien retourniert
                ## $verbose : false - es wird nur der Erfolg (true/false) geliefert.
                ## $errorType : siehe https://www.php.net/manual/de/errorfunc.constants.php
                
                
                function rmdirRecursive(
                	$dirName,
                	$verbose = false,
                	$errorType = E_USER_ERROR
                ) {
                	if ( ! is_dir( $dirName ) ) {
                		trigger_error ( "'$dirName' existiert nicht oder ist kein Verzeichnis.", $errorType );
                		return false;
                	}
                	
                	if ( $verbose ) {
                		$return = [];
                	} else {
                		$return = true;
                	}
                
                	foreach( scandir( $dirName ) as $item ) {
                		if( $item === ".." or $item === ".") {
                			continue;
                

                Nach dem continue brauchst du das else doch gar nicht mehr.

                		} else {
                			$dso = $dirName . '/' . $item;
                			if ( 
                				is_file( $dso ) 
                				or is_link( $dso )
                			) {
                				if ( ! unlink( $dso ) ) {
                					trigger_error ( "'$dso' konnte nicht gelöscht werden." );
                					return false;
                				} else {
                					$return[] = $dso;
                				}
                			} else {
                				$r = rmdirRecursive( $dso, $verbose, $errorType );
                

                Ist !(is_file or is_link) == is_dir?

                				if ( false === $r ) {
                					trigger_error ( "'$dso' konnte nicht gelöscht werden." );
                					return false;				   
                				} else {
                					if ( $verbose )  {
                						if ( is_array( $r ) ) {
                							foreach ( $r as $i ) {
                								$return[] = $i;
                							}
                						}
                					}
                				}
                			}
                		}
                	}
                	if ( rmdir( $dirName ) ) {
                		if ( $verbose ) {
                			$return[] = $dirName;
                		}
                		return $return;
                	} else {
                		trigger_error ( "'$dirName' konnte nicht gelöscht werden.", $errorType );
                		return false;		
                	}
                }
                
                ## Usage example:
                # print_r( rmdirRecursive( '/tmp/a', true, E_USER_ERROR ) );
                

                Viele Grüße
                Robert

  3. Hallo Bastian,

    ich weiß nicht, ob Du es versehentlich richtig gemacht hast oder ob Du das so überlegt hast, aber ich möchte auf diesen Punkt hinweisen.

    scandir liest das Verzeichnis in ein Array ein. Die foreach-Schleife verarbeitet dann das Array. D.h. wenn die unlink Aufrufe laufen, wird das Verzeichnis nicht gleichzeitig von Dir gelesen.

    Es gibt alternativ auch readdir. Damit liest man das Verzeichnis Datei für Datei - und hier würde die Gefahr eines Konflikts bestehen, wenn man in der Schleife löscht. Es könnte dann passieren, dass der Lesezugriff auf das Verzeichnis durch die Löschungen durcheinander kommt und man Dateien überspringt.

    Da das Verhalten von readdir je nach OS und Filesystem anders sein kann, sind das schwer testbare Szenarien. Deswegen: scandir ist hier genau richtig.

    Oder glob, wenn ein Suchmuster gebraucht wird.

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hallo Bastian,

      ich weiß nicht, ob Du es versehentlich richtig gemacht hast oder ob Du das so überlegt hast, aber ich möchte auf diesen Punkt hinweisen.

      scandir liest das Verzeichnis in ein Array ein. Die foreach-Schleife verarbeitet dann das Array. D.h. wenn die unlink Aufrufe laufen, wird das Verzeichnis nicht gleichzeitig von Dir gelesen.

      Es gibt alternativ auch readdir. Damit liest man das Verzeichnis Datei für Datei - und hier würde die Gefahr eines Konflikts bestehen, wenn man in der Schleife löscht. Es könnte dann passieren, dass der Lesezugriff auf das Verzeichnis durch die Löschungen durcheinander kommt und man Dateien überspringt.

      Da das Verhalten von readdir je nach OS und Filesystem anders sein kann, sind das schwer testbare Szenarien. Deswegen: scandir ist hier genau richtig.

      Hallo Rolf,

      danke für die Erklärung, das wußte ich nicht.

      Ich habe trotzdem bewußt scandir() gewählt, weil ich nochmal nachgelesen habe, ob ich mit readdir() noch auf dem aktuellen Stand bin.

      Bastian

      1. Hallo Bastian,

        naja gut, readdir ist aus PHP 4 und scandir wurde in PHP 5 eingeführt.

        Aber deswegen ist readdir nicht missbilligt. Und der liebe Tim macht ein paar Fehler.

        (1) dass readdir false liefert, wenn keine Datei mehr kommt, ist kein Problem. Aus Gründen:

        • Eine Datei namens "0" hätte das Potenzial, mit false verwechselt zu werden. Der einzige andere String, der sonst noch falsy ist, ist "". Aber eine Datei namens "" gibt's nicht. Kann ich mir nicht vorstellen.
        • Wenn man weiß, dass ein API einen Typ X oder FALSE zurückliefert, und man trotzdem mit while ($data = getdata()) { ... } arbeitet, gehört man standrechtlich mit dem COBOL Handbuch erschlagen. In solchen Fällen arbeitet man mit while (FALSE !== ($data = getdata())) { ... } und alles ist gut.

        (2) sein Beispiel mit dem Filestream API ist eine Kopie von readdir. Schade. Ich hätt gern gewusst, ob diese Variante auch unter Windows funktioniert.

        (3) er hat die Alternative glob() glott - öh - glatt vergessen. Und die DirectoryIterator sowieso. Ich hab ihm das mal als Kommentar geschrieben, der muss aber wohl noch freigegeben werden.

        Wenn Du sicher bist, dass Dein Ordner nur Dateien enthält und keine Unterverzeichnisse, bist Du mit scandir am schnellsten bedient. Wenn Du erstmal prüfen musst, ob es Verzeichnisse sind, sieht die Sache anders aus.

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Hallo Rolf,

          (3) er hat die Alternative glob() glott - öh - glatt vergessen. Und die DirectoryIterator sowieso. Ich hab ihm das mal als Kommentar geschrieben, der muss aber wohl noch freigegeben werden.

          Na dann bin ich aber gespannt, ob Dein Kommentar jemals freigegeben wird? 😉

          Wenn Du sicher bist, dass Dein Ordner nur Dateien enthält und keine Unterverzeichnisse, bist Du mit scandir am schnellsten bedient. Wenn Du erstmal prüfen musst, ob es Verzeichnisse sind, sieht die Sache anders aus.

          Ja, da bin ich sicher, weshalb ich gar nicht nach "rekursiv" gesucht hatte.

          Bastian

          1. Ja, da bin ich sicher, weshalb ich gar nicht nach "rekursiv" gesucht hatte.

            Im Hinblick auf die Wiederverwertung des Codes macht es aber Sinn, statt eines Codeschnipsels, der nur ein Verzeichnis mit ausschließlich Dateien darin löscht, gleich einen zu nehmen, der das Verzeichnis mit Dateien und Unterverzeichnissen darin löscht. Der Test, ob es ein Verzeichnis ist (und also die ggf. nachfolgende Rekursion) findet ja nur statt wenn etwas anderes als Dateien (oder symbolische Links) vorgefunden wurden, so dass Du keine überbordende Performanceanforderungen fürchten musst.

            1. Hallo Raketenwilli,

              Im Hinblick auf die Wiederverwertung des Codes macht es aber Sinn

              ...die Verzeichnisiteration von der eigentlichen Aufgabe zu trennen.

              Dazu werde ich mir jetzt auch noch mal Gedanken machen - ein Traversierungsobjekt mit abstrakten Methoden für "Datei angetroffen", "Symlink angetroffen", "Ordner angetroffen". Die überschreibt man dann mit den erforderlichen Aktionen

              Rolf

              --
              sumpsi - posui - obstruxi
  4. Hello,

    auf jeden Fall musst Du interne Rekursionen abfangen, sonst kann das Script auf die Schnauze fallen, aka sich aufhängen.

    Es ist also wichtig, symbolische Links, auszuschließen, denn die könnten auf eine andere Ebene im Verzeichnisbaum verweisen, die deinen Ablauf dann durcheinander bringt.

    Vielleicht wäre as aber uch möglich, mittels exec() oder ähnlicher Funktionen, das Dateisystem direkt um Hilfe zu bitten.

    Hallo,

    ich möchte ein verzeichnis mittels php leeren, aber es wird nicht geleert, obwohl mir die Dateien aufgelistet werden.

    Woran liegt das?

    Bastian

    // Verzeichnis leeren
    $dir = dirname(__FILE__) .'/ordner';
    foreach(scandir($dir) as $file) {
        if($file === ".." or $file === ".") {
            continue;
        }
        echo $file."<br>";
        unlink(dirname(__FILE__) .$file);
    }
    

    Glück Auf
    Tom vom Berg

    --
    Es gibt soviel Sonne, nutzen wir sie. Solar Harz DE Sonnige Grüße aus dem Oberharz