Jak rekurencyjnie usunąć katalog i całą jego zawartość (pliki + podkatalogi) w PHP?

Odpowiedzi:

207

Sekcja rmdirstworzona przez użytkowników na stronie podręcznika zawiera przyzwoitą implementację:

 function rrmdir($dir) { 
   if (is_dir($dir)) { 
     $objects = scandir($dir);
     foreach ($objects as $object) { 
       if ($object != "." && $object != "..") { 
         if (is_dir($dir. DIRECTORY_SEPARATOR .$object) && !is_link($dir."/".$object))
           rrmdir($dir. DIRECTORY_SEPARATOR .$object);
         else
           unlink($dir. DIRECTORY_SEPARATOR .$object); 
       } 
     }
     rmdir($dir); 
   } 
 }
Artefacto
źródło
1
@The Pixel Developer - dodałem odpowiedź, która to pokazuje.
salathe
2
sprawdź rozwiązanie, które ktoś mi dał na to samo pytanie: glob wydaje się działać lepiej: stackoverflow.com/questions/11267086/…
NoodleOfDeath
To wywołuje is_dirdwa razy dla każdego katalogu rekurencyjnego. Jeśli argument jest dowiązaniem symbolicznym, on również podąża za nim zamiast usuwać dowiązanie symboliczne, co może być tym, czego chcesz, ale nie musi. W każdym razie tak nie rm -rfjest.
Vladimir Panteleev
116

Opierając się na komentarzu The Pixel Developer's , fragment wykorzystujący SPL może wyglądać następująco:

$files = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
    RecursiveIteratorIterator::CHILD_FIRST
);

foreach ($files as $fileinfo) {
    $todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
    $todo($fileinfo->getRealPath());
}

rmdir($dir);

Uwaga: To nie ma sprawdzania czystości i korzysta z flagą SKIP_DOTS wprowadzonego z FilesystemIterator w PHP 5.3.0. Oczywiście $todomoże to być if/ else. Ważną kwestią jest to, że CHILD_FIRSTjest używany do iteracji po elementach podrzędnych (plikach) przed ich nadrzędnymi (folderami).

salathe
źródło
SKIP_DOTSzostał wprowadzony tylko w PHP 5.3? Gdzie to widziałeś?
Alix Axel
Dziękuję Ci. Ponadto: czy nie powinieneś używać tej getPathname()metody zamiast getRealPath()?
Alix Axel
3
To rozwiązanie działa dobrze, jednak usuwa wszystko ... z wyjątkiem katalogu (pustego lub nie). Na rmdir($dir)końcu skryptu powinien znajdować się.
laurent
3
Oto ta sama funkcja, która została rozpakowana, zablokowana w dokumentach i dostosowana do rmdir()i unlink(), np. Przerywa E_WARNINGi zwraca truelub falsewskazuje sukces.
mindplay.dk
2
@dbf nie, nie będzie, FilesystemIteratornie jest iteratorem rekurencyjnym.
salathe
17

Usuwa wszystkie pliki i foldery ze ścieżki.

function recurseRmdir($dir) {
  $files = array_diff(scandir($dir), array('.','..'));
  foreach ($files as $file) {
    (is_dir("$dir/$file")) ? recurseRmdir("$dir/$file") : unlink("$dir/$file");
  }
  return rmdir($dir);
}
Liko
źródło
1
rm -rf /== recurseRmdir('/'):)
Aaron Esau
5
Należy pamiętać, że to nie jest bezpieczne łącze symboliczne! Potrzebujesz sprawdzenia poczytalności po is_dir, aby również sprawdzić, czy to jest! Is_link, ponieważ w przeciwnym razie możesz utworzyć dowiązanie symboliczne do folderu zewnętrznego, który jest następnie usuwany i może to zostać uznane za lukę w zabezpieczeniach. Więc powinieneś zmienić is_dir("$dir/$file")nais_dir("$dir/$file") && !is_link("$dir/$file")
Kira M. Backes
13

W przypadku * nix możesz użyć shell_execfor rm -Rlub DEL /S folder_namefor Windows.

ankitjaininfo
źródło
2
co powiesz DEL /S folder_namena Windows
ankitjaininfo
@Gordon RMDIR /S /Q folder_namejest tym, co zadziałało dla mnie
Brian Leishman
2
@ WiR3D, o ile polecenie exec nie zawiera danych wejściowych użytkownika, powinno być dobrze. Np .:exec('rm -rf ' . __DIR__ . '/output/*.log');
Brian Hannay
5
<?php

use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;

# http://stackoverflow.com/a/3352564/283851
# https://gist.github.com/XzaR90/48c6b615be12fa765898

# Forked from https://gist.github.com/mindplay-dk/a4aad91f5a4f1283a5e2

/**
 * Recursively delete a directory and all of it's contents - e.g.the equivalent of `rm -r` on the command-line.
 * Consistent with `rmdir()` and `unlink()`, an E_WARNING level error will be generated on failure.
 *
 * @param string $source absolute path to directory or file to delete.
 * @param bool   $removeOnlyChildren set to true will only remove content inside directory.
 *
 * @return bool true on success; false on failure
 */
function rrmdir($source, $removeOnlyChildren = false)
{
    if(empty($source) || file_exists($source) === false)
    {
        return false;
    }

    if(is_file($source) || is_link($source))
    {
        return unlink($source);
    }

    $files = new RecursiveIteratorIterator
    (
        new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::CHILD_FIRST
    );

    //$fileinfo as SplFileInfo
    foreach($files as $fileinfo)
    {
        if($fileinfo->isDir())
        {
            if(rrmdir($fileinfo->getRealPath()) === false)
            {
                return false;
            }
        }
        else
        {
            if(unlink($fileinfo->getRealPath()) === false)
            {
                return false;
            }
        }
    }

    if($removeOnlyChildren === false)
    {
        return rmdir($source);
    }

    return true;
}
XzaR
źródło
Dość złożona sugestia ;-)
Filip
@Philipp tak, tak myślę. Cóż, zrobiłem widelec z gist.github.com/mindplay-dk/a4aad91f5a4f1283a5e2, ponieważ nie udało mi się go uruchomić, więc pomyślałem, że mogę go również udostępnić.
XzaR
Tam jest problem. Nie usuwa pustych folderów po usunięciu wszystkich plików. Zamieszczam nieco zmodyfikowaną wersję jako odpowiedź poniżej.
Vladislav Rastrusny
@Vladislav Rastrusny naprawdę? Mi to pasuje. Może masz folder tylko do odczytu lub coś takiego.
XzaR
4

Jest inny wątek z większą ilością przykładów tutaj: rekurencyjna funkcja usuwania katalogu dla PHP?

Jeśli używasz Yii, możesz zostawić to frameworkowi:

CFileHelper::removeDirectory($my_directory);
David Newcomb
źródło
1

„prosty” kod, który działa i może być odczytany przez dziesięciolatka:

function deleteNonEmptyDir($dir) 
{
   if (is_dir($dir)) 
   {
        $objects = scandir($dir);

        foreach ($objects as $object) 
        {
            if ($object != "." && $object != "..") 
            {
                if (filetype($dir . "/" . $object) == "dir")
                {
                    deleteNonEmptyDir($dir . "/" . $object); 
                }
                else
                {
                    unlink($dir . "/" . $object);
                }
            }
        }

        reset($objects);
        rmdir($dir);
    }
}

Zwróć uwagę, że wszystko, co zrobiłem, to rozwinięcie / uproszczenie i naprawienie (nie działało dla niepustego katalogu) rozwiązanie tutaj: W PHP jak rekurencyjnie usunąć wszystkie foldery, które nie są puste?

Programista
źródło
1

Ulepszone rozwiązanie @Artefacto - poprawione literówki i uproszczony kod, działające zarówno dla pustych, jak i niepustych katalogów.

  function recursive_rmdir($dir) { 
    if( is_dir($dir) ) { 
      $objects = array_diff( scandir($dir), array('..', '.') );
      foreach ($objects as $object) { 
        $objectPath = $dir."/".$object;
        if( is_dir($objectPath) )
          recursive_rmdir($objectPath);
        else
          unlink($objectPath); 
      } 
      rmdir($dir); 
    } 
  }
jave.web
źródło
1

W 100% działające rozwiązanie

public static function rmdir_recursive($directory, $delete_parent = null)
  {
    $files = glob($directory . '/{,.}[!.,!..]*',GLOB_MARK|GLOB_BRACE);
    foreach ($files as $file) {
      if (is_dir($file)) {
        self::rmdir_recursive($file, 1);
      } else {
        unlink($file);
      }
    }
    if ($delete_parent) {
      rmdir($directory);
    }
  }
David Pankov
źródło
0

Coś takiego?

function delete_folder($folder) {
    $glob = glob($folder);
    foreach ($glob as $g) {
        if (!is_dir($g)) {
            unlink($g);
        } else {
            delete_folder("$g/*");
            rmdir($g);
        }
    }
}
K-Gun
źródło
Nie potrafię wyjaśnić, dlaczego, ale to nie zadziałało. Ciągle próbował usunąć folder, który nie był pusty. Druga odpowiedź powyżej działała dobrze.
laurent
1
@ buggy3 Do jakiego konkretnego kodu się odnosisz? Link po prostu prowadzi do tej strony z pytaniami.
cgogolin
0

Przykład z funkcją glob () . Będzie usuwał wszystkie pliki i foldery rekurencyjnie, w tym pliki zaczynające się od kropki.

delete_all( 'folder' );

function delete_all( $item ) {
    if ( is_dir( $item ) ) {
        array_map( 'delete_all', array_diff( glob( "$item/{,.}*", GLOB_BRACE ), array( "$item/.", "$item/.." ) ) );
        rmdir( $item );
    } else {
        unlink( $item );
    }
};
Danijel
źródło
Poszedłem zsystem('rm -fr folder')
Itay Moav -Malimovka
0

unlinkr rekurencyjnie usuwa wszystkie foldery i pliki w podanej ścieżce, upewniając się, że nie usuwa samego skryptu.

function unlinkr($dir, $pattern = "*") {
    // find all files and folders matching pattern
    $files = glob($dir . "/$pattern"); 

    //interate thorugh the files and folders
    foreach($files as $file){ 
    //if it is a directory then re-call unlinkr function to delete files inside this directory     
        if (is_dir($file) and !in_array($file, array('..', '.')))  {
            echo "<p>opening directory $file </p>";
            unlinkr($file, $pattern);
            //remove the directory itself
            echo "<p> deleting directory $file </p>";
            rmdir($file);
        } else if(is_file($file) and ($file != __FILE__)) {
            // make sure you don't delete the current script
            echo "<p>deleting file $file </p>";
            unlink($file); 
        }
    }
}

jeśli chcesz usunąć wszystkie pliki i foldery, w których umieścisz ten skrypt, nazwij go w następujący sposób

//get current working directory
$dir = getcwd();
unlinkr($dir);

jeśli chcesz po prostu usunąć tylko pliki php, nazwij to w następujący sposób

unlinkr($dir, "*.php");

możesz również użyć dowolnej innej ścieżki do usunięcia plików

unlinkr("/home/user/temp");

Spowoduje to usunięcie wszystkich plików w katalogu home / user / temp.

Tofeeq
źródło
0

Używam tego kodu ...

 function rmDirectory($dir) {
        foreach(glob($dir . '/*') as $file) {
            if(is_dir($file))
                rrmdir($file);
            else
                unlink($file);
        }
        rmdir($dir);
    }

albo ten...

<?php 
public static function delTree($dir) { 
   $files = array_diff(scandir($dir), array('.','..')); 
    foreach ($files as $file) { 
      (is_dir("$dir/$file")) ? delTree("$dir/$file") : unlink("$dir/$file"); 
    } 
    return rmdir($dir); 
  } 
?>
Hamed
źródło
Czy to jest rekurencyjne?
Martin Tournoij,
0

Po zakończeniu testów wystarczy usunąć # z #unlink i #rmdir w klasie.

<?php 
class RMRFiles {

        function __construct(){
        }

    public function recScan( $mainDir, $allData = array() )
    {

    // hide files
    $hidefiles = array(
    ".",
    "..") ;

    //start reading directory
    $dirContent = scandir( $mainDir ) ;

        //cycle through
        foreach ( $dirContent as $key => $content )
        {
            $path = $mainDir . '/' . $content ;

            // if is readable / file
            if ( ! in_array( $content, $hidefiles ) )
            {
            if ( is_file( $path ) && is_readable( $path ) )
            {
            #delete files within directory
            #unlink($path);
            $allData['unlink'][] = $path ;
            }

            // if is readable / directory
            else
            if ( is_dir( $path ) && is_readable( $path ) )
            {
            /*recursive*/
            $allData = $this->recScan( $path, $allData ) ;

            #finally remove directory
            $allData['rmdir'][]=$path;
            #rmdir($path);
            }
            }
        }

    return $allData ;

    }

}

header("Content-Type: text/plain");

/* Get absolute path of the running script 
Ex : /home/user/public_html/   */
define('ABPATH', dirname(__file__) . '/'); 

/* The folder where we store cache files 
Ex: /home/user/public_html/var/cache   */
define('STOREDIR','var/cache'); 

$rmrf = new RMRFiles();
#here we delete folder content files & directories
print_r($rmrf->recScan(ABPATH.STOREDIR));
#finally delete scanned directory ? 
#rmdir(ABPATH.STOREDIR);

?>
Alin Razvan
źródło
0
<?php

/**
 * code by Nk ([email protected])
 */

class filesystem
{
    public static function remove($path)
    {
        return is_dir($path) ? rmdir($path) : unlink($path);
    }

    public static function normalizePath($path)
    {
        return $path.(is_dir($path) && !preg_match('@/$@', $path) ? '/' : '');      
    }

    public static function rscandir($dir, $sort = SCANDIR_SORT_ASCENDING)
    {
        $results = array();

        if(!is_dir($dir))
        return $results;

        $dir = self::normalizePath($dir);

        $objects = scandir($dir, $sort);

        foreach($objects as $object)
        if($object != '.' && $object != '..')
        {
            if(is_dir($dir.$object))
            $results = array_merge($results, self::rscandir($dir.$object, $sort));
            else
            array_push($results, $dir.$object);
        }

        array_push($results, $dir);

        return $results;
    }

    public static function rrmdir($dir)
    {
        $files = self::rscandir($dir);

        foreach($files as $file)
        self::remove($file);

        return !file_exists($dir);
    }
}

?>

cleanup.php:

<?php

/* include.. */

filesystem::rrmdir('/var/log');
filesystem::rrmdir('./cache');

?>
Nkc
źródło
0

Wygląda na to, że wszystkie inne odpowiedzi zakładają, że ścieżka podana do funkcji jest zawsze katalogiem. Ten wariant działa w celu usunięcia katalogów, a także pojedynczych plików:

/**
 * Recursively delete a file or directory.  Use with care!
 *
 * @param string $path
 */
function recursiveRemove($path) {
    if (is_dir($path)) {
        foreach (scandir($path) as $entry) {
            if (!in_array($entry, ['.', '..'])) {
                recursiveRemove($path . DIRECTORY_SEPARATOR . $entry);
            }
        }
        rmdir($path);
    } else {
        unlink($path);
    }
}
jlh
źródło
0

Prawidłowe używanie DirectoryIterator i rekurencji:

function deleteFilesThenSelf($folder) {
    foreach(new DirectoryIterator($folder) as $f) {
        if($f->isDot()) continue; // skip . and ..
        if ($f->isFile()) {
            unlink($f->getPathname());
        } else if($f->isDir()) {
            deleteFilesThenSelf($f->getPathname());
        }
    }
    rmdir($folder);
}
AvaLanCS
źródło
-1

Właśnie stworzyłem ten kod na podstawie niektórych dyskusji dotyczących StackOverflow. Nie testowałem jeszcze w środowisku Linux. Ma na celu całkowite usunięcie pliku lub katalogu:

function splRm(SplFileInfo $i)
{
    $path = $i->getRealPath();

    if ($i->isDir()) {
        echo 'D - ' . $path . '<br />';
        rmdir($path);
    } elseif($i->isFile()) {
        echo 'F - ' . $path . '<br />';
        unlink($path);
    }
}

function splRrm(SplFileInfo $j)
{
    $path = $j->getRealPath();

    if ($j->isDir()) {
        $rdi = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS);
        $rii = new RecursiveIteratorIterator($rdi, RecursiveIteratorIterator::CHILD_FIRST);
        foreach ($rii as $i) {
            splRm($i);
        }
    }
    splRm($j);

}

splRrm(new SplFileInfo(__DIR__.'/../dirOrFileName'));
Chicna
źródło
-1
function rmdir_recursive( $dirname ) {

    /**
     * FilesystemIterator and SKIP_DOTS
     */

    if ( class_exists( 'FilesystemIterator' ) && defined( 'FilesystemIterator::SKIP_DOTS' ) ) {

        if ( !is_dir( $dirname ) ) {
            return false;
        }

        foreach( new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $dirname, FilesystemIterator::SKIP_DOTS ), RecursiveIteratorIterator::CHILD_FIRST ) as $path ) {
            $path->isDir() ? rmdir( $path->getPathname() ) : unlink( $path->getRealPath() );
        }

        return rmdir( $dirname );

    }

    /**
     * RecursiveDirectoryIterator and SKIP_DOTS
     */

    if ( class_exists( 'RecursiveDirectoryIterator' ) && defined( 'RecursiveDirectoryIterator::SKIP_DOTS' ) ) {

        if ( !is_dir( $dirname ) ) {
            return false;
        }

        foreach( new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $dirname, RecursiveDirectoryIterator::SKIP_DOTS ), RecursiveIteratorIterator::CHILD_FIRST ) as $path ) {
            $path->isDir() ? rmdir( $path->getPathname() ) : unlink( $path->getRealPath() );
        }

        return rmdir( $dirname );

    }

    /**
     * RecursiveIteratorIterator and RecursiveDirectoryIterator
     */

    if ( class_exists( 'RecursiveIteratorIterator' ) && class_exists( 'RecursiveDirectoryIterator' ) ) {

        if ( !is_dir( $dirname ) ) {
            return false;
        }

        foreach( new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $dirname ), RecursiveIteratorIterator::CHILD_FIRST ) as $path ) {
            if ( in_array( $path->getFilename(), array( '.', '..' ) ) ) {
                continue;
            }
            $path->isDir() ? rmdir( $path->getPathname() ) : unlink( $path->getRealPath() );
        }

        return rmdir( $dirname );

    }

    /**
     * Scandir Recursive
     */

    if ( !is_dir( $dirname ) ) {
        return false;
    }

    $objects = scandir( $dirname );

    foreach ( $objects as $object ) {
        if ( $object === '.' || $object === '..' ) {
            continue;
        }
        filetype( $dirname . DIRECTORY_SEPARATOR . $object ) === 'dir' ? rmdir_recursive( $dirname . DIRECTORY_SEPARATOR . $object ) : unlink( $dirname . DIRECTORY_SEPARATOR . $object );
    }

    reset( $objects );
    rmdir( $dirname );

    return !is_dir( $dirname );

}
D3F4ULT
źródło
-1

Zmodyfikowany wariant rozwiązania @XzaR. Usuwa puste foldery, gdy wszystkie pliki są z nich usuwane i zgłasza wyjątki zamiast zwracać false w przypadku błędów.

function recursivelyRemoveDirectory($source, $removeOnlyChildren = true)
{
    if (empty($source) || file_exists($source) === false) {
        throw new Exception("File does not exist: '$source'");
    }

    if (is_file($source) || is_link($source)) {
        if (false === unlink($source)) {
            throw new Exception("Cannot delete file '$source'");
        }
    }

    $files = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::CHILD_FIRST
    );

    foreach ($files as $fileInfo) {
        /** @var SplFileInfo $fileInfo */
        if ($fileInfo->isDir()) {
            if ($this->recursivelyRemoveDirectory($fileInfo->getRealPath()) === false) {
                throw new Exception("Failed to remove directory '{$fileInfo->getRealPath()}'");
            }
            if (false === rmdir($fileInfo->getRealPath())) {
                throw new Exception("Failed to remove empty directory '{$fileInfo->getRealPath()}'");
            }
        } else {
            if (unlink($fileInfo->getRealPath()) === false) {
                throw new Exception("Failed to remove file '{$fileInfo->getRealPath()}'");
            }
        }
    }

    if ($removeOnlyChildren === false) {
        if (false === rmdir($source)) {
            throw new Exception("Cannot remove directory '$source'");
        }
    }
}
Vladislav Rastrusny
źródło
-1
function deltree_cat($folder)
{
    if (is_dir($folder))
    {
             $handle = opendir($folder);
             while ($subfile = readdir($handle))
             {
                     if ($subfile == '.' or $subfile == '..') continue;
                     if (is_file($subfile)) unlink("{$folder}/{$subfile}");
                     else deltree_cat("{$folder}/{$subfile}");
             }
             closedir($handle);
             rmdir ($folder);
     }
     else
     {
        unlink($folder);
     }
}
Ilya Yaremchuk
źródło
1
Jeśli odpowiadasz na stare pytanie, które ma już wiele odpowiedzi, w tym zaakceptowaną, musisz zamieścić wyjaśnienie, jaką wartość dodaje twoja odpowiedź, a nie tylko kod. Odpowiedzi zawierające tylko kod są generalnie źle widziane, ale szczególnie w tym przypadku.
Jared Smith
Głosowałem za tą odpowiedzią i zaakceptowałem odpowiedź. To nie jest złe, z mojego czeku benchmarku (bez unlink, rmdir) opendir+ readdirpraca szybciej, że scandiri RecursiveDirectoryIteratorto jest także zużywają mniej pamięci niż wszystkie. Aby usunąć folder, muszę closedirnajpierw, utknąłem na tym. Dzięki tej odpowiedzi.
vee