PHP 递归扫描目录

15

我希望我的脚本能够递归扫描目录,

$files = scandir('/dir');
foreach($files as $file){
if(is_dir($file)){
    echo '<li><label class="tree-toggler nav-header"><i class="fa fa-folder-o"></i>'.$file.'</label>';
    $subfiles = scandir($rooturl.'/'.$file);
        foreach($subfiles as $subfile){
            // and so on and on and on
        }
        echo '<li>';
    } else {
        echo $file.'<br />';
    }
}
我希望以循环的方式处理这个问题,对于每个由scandir找到的目录,在该目录中找到的文件夹上再运行一次scandir,
所以dir'A'包含dir1/2/3,现在应该scandir(1), scandir(2), scandir(3),对于找到的每个dir都要这样做。
我如何能够轻松实现这一点,而不必在每个foreach中重复复制粘贴代码?
编辑:由于答案几乎与我已经尝试过的答案相同,所以我将稍微更新一下问题。
使用此脚本需要创建一个treeview列表。 当前发布的脚本会输出以下内容:
/images/dir1/file1.png
/images/dir1/file2.png
/images/dir1/file3.png
/images/anotherfile.php
/data/uploads/avatar.jpg
/data/config.php
index.php

我需要的实际上是:

<li><label>images</label>
    <ul>
        <li><label>dir1</label>
            <ul>
                <li>file1.png</li>
                <li>file2.png</li>
                <li>file3.png</li>
            </ul>
        </li>
        <li>anotherfile.php</li>
    </ul>
</li>
<li><label>data</label>
    <ul>
        <li><label>uploads</label>
            <ul>
                <li>avatar.jpg</li>
            </ul>
        </li>
        <li>config.php</li>
    </ul>
</li>
<li>index.php</li>

等等,感谢已经发布的答案!


除了递归编程基础之外,is_dir($file) 应该是 is_dir("/dir/$file")。 - Jack
1
请查看recursiveIterator类系列 - http://php.net/manual/en/class.recursivedirectoryiterator.php - Professor Abronsius
7个回答

27

我知道这是一个老问题,但我写了一个更加功能性的版本。它不使用全局状态,并使用纯函数来解决问题:

function scanAllDir($dir) {
  $result = [];
  foreach(scandir($dir) as $filename) {
    if ($filename[0] === '.') continue;
    $filePath = $dir . '/' . $filename;
    if (is_dir($filePath)) {
      foreach (scanAllDir($filePath) as $childFilename) {
        $result[] = $filename . '/' . $childFilename;
      }
    } else {
      $result[] = $filename;
    }
  }
  return $result;
}

2
为了确保代码可以在Linux和Windows系统上安全使用,您可能希望使用常量DIRECTORY_SEPARATOR而不是硬编码'/'。 - Select0r
由于某种原因,它只选择一个子目录并对其进行递归扫描。 - JackTheKnife
做得很好:开箱即用,如广告所述。 - Eric P
将当前扫描结果存储为$thisDirectory = scandir($dir)不是更好吗? 以避免在每次迭代中执行scandir($dir)函数。 或者它不会在每次迭代中执行吗? - algo
scandir每个目录只会被调用一次。将结果放入变量中不会减少其被调用的次数。 - Allain Lalonde

6
您可以以递归方式扫描目录,如下所示,其中目标是您的最高级目录:
function scanDir($target) {

        if(is_dir($target)){

            $files = glob( $target . '*', GLOB_MARK ); //GLOB_MARK adds a slash to directories returned

            foreach( $files as $file )
            {
                scanDir( $file );
            }


        } 
    }

你可以轻松地根据自己的需求调整此函数。 例如,如果要使用此函数删除目录及其内容,则可以执行以下操作:
function delete_files($target) {

        if(is_dir($target)){

            $files = glob( $target . '*', GLOB_MARK ); //GLOB_MARK adds a slash to directories returned

            foreach( $files as $file )
            {
                delete_files( $file );
            }

            rmdir( $target );

        } elseif(is_file($target)) {

            unlink( $target );
    }

你现在的做法行不通。 以下函数可以递归获取所有目录、子目录以及它们的内容:
function assetsMap($source_dir, $directory_depth = 0, $hidden = FALSE)
    {
        if ($fp = @opendir($source_dir))
        {
            $filedata   = array();
            $new_depth  = $directory_depth - 1;
            $source_dir = rtrim($source_dir, '/').'/';

            while (FALSE !== ($file = readdir($fp)))
            {
                // Remove '.', '..', and hidden files [optional]
                if ( ! trim($file, '.') OR ($hidden == FALSE && $file[0] == '.'))
                {
                    continue;
                }

                if (($directory_depth < 1 OR $new_depth > 0) && @is_dir($source_dir.$file))
                {
                    $filedata[$file] = assetsMap($source_dir.$file.'/', $new_depth, $hidden);
                }
                else
                {
                    $filedata[] = $file;
                }
            }

            closedir($fp);
            return $filedata;
        }
        echo 'can not open dir';
        return FALSE;
    }

将您的路径传递给函数:

$path = 'elements/images/';
$filedata = assetsMap($path, $directory_depth = 5, $hidden = FALSE);
$filedata是一个包含所有被发现的目录及其内容的数组。该函数可让您扫描目录结构($directory_depth),深度可以随意设置,同时还可摆脱所有无聊的隐藏文件(例如'.', '..')。
现在您只需使用返回的数组,在视图中按照自己的喜好排列数据即可。
实际上,您正在尝试创建一种文件管理器,正如您所知,这种管理器在互联网上开源且免费的很多。
我希望这可以帮到您,祝您圣诞快乐。

谢谢您的回复,但是我无法按照我需要的方式使其工作,我已经更新了我的初始问题以获得更好的解释 :) - GRX
我一定会尝试这个,因为大多数情况下,你必须剥离它们70%的构建才能适应自己的脚本/模板/页面,所以我还没有检查过这个开源脚本。感谢您的回复,祝您圣诞快乐! :) - GRX
你的扫描目录函数没有返回任何值,并且你不能使用这个名称调用该函数,因为它是一个保留名称。 - Froggiz

6
一个与之前函数变体,使用三目运算符使函数更短,并使用展开运算符来为array_push拆包的数组。
function getFiles(string $directory): array
{
    $files = array_diff(scandir($directory), ['.', '..']);
    $allFiles = [];

    foreach ($files as $file) {
        $fullPath = $directory. DIRECTORY_SEPARATOR .$file;
        is_dir($fullPath) ? array_push($allFiles, ...getFiles($fullPath)) : array_push($allFiles, $file);
    }

    return $allFiles;
}

旧的

function getFiles(string $directory, array $allFiles = []): array
{
    $files = array_diff(scandir($directory), ['.', '..']);

    foreach ($files as $file) {
        $fullPath = $directory. DIRECTORY_SEPARATOR .$file;

        if( is_dir($fullPath) )
            $allFiles += getFiles($fullPath, $allFiles);
        else
            $allFiles[] = $file;
    }

    return $allFiles;
}

我知道这是旧的,但我想展示另一种稍微不同的答案版本。使用array_diff来丢弃“。”和“..”文件夹。还使用+运算符来组合两个数组(我很少看到人们使用这个,所以对某些人可能有用)


1
使用 array_diff 真的很好,因为它相比于 array_filter 更加简洁。 - Alexander Kucheryuk
我甚至不知道可以使用+=来合并数组。我所知道的最接近的方法是array_merge(),但它是无用的,因为它会用相同键的值替换掉原有的值。这个解决方案是否保留了所有的值? - algo

4

现代方式:

use RecursiveIteratorIterator;
use RecursiveDirectoryIterator;

$path = '/var/www/path/to/your/files/';

foreach ( new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $path, RecursiveDirectoryIterator::SKIP_DOTS ) ) as $file ) {
    
    // TODO: Uncomment to see additional information. Do your manipulations with the files here.
    // var_dump( $file->getPathname() ); // Or simple way: echo $file;
    // var_dump( $file->isFile() );
    // var_dump( $file->isDir() );
    // var_dump( $file->getFileInfo() );
    // var_dump( $file->getExtension() );
    // var_dump( $file->getDepth() );

}

2

虽然这个问题很老,但我的答案可以帮助那些访问这个问题的人。

这个函数会递归扫描目录和子目录,并将输出存储在一个全局变量中。

global $file_info; // All the file paths will be pushed here
$file_info = array();

/**
 * 
 * @function recursive_scan
 * @description Recursively scans a folder and its child folders
 * @param $path :: Path of the folder/file
 * 
 * */
function recursive_scan($path){
    global $file_info;
    $path = rtrim($path, '/');
    if(!is_dir($path)) $file_info[] = $path;
        else {
            $files = scandir($path);
            foreach($files as $file) if($file != '.' && $file != '..') recursive_scan($path . '/' . $file);
        }
}

recursive_scan('/var/www/html/wp-4.7.2/wp-content/plugins/site-backup');
print_r($file_info);

1
创建一个扫描函数并调用它递归地...

e.g:

   <?php

    function scandir_rec($root)
    {
        echo $root . PHP_EOL;
        // When it's a file or not a valid dir name
        // Print it out and stop recusion 
        if (is_file($root) || !is_dir($root)) {
            return;
        }

        // starts the scan
        $dirs = scandir($root);
        foreach ($dirs as $dir) {
            if ($dir == '.' || $dir == '..') {
                continue; // skip . and ..
            }

            $path = $root . '/' . $dir;
            scandir_rec($path); // <--- CALL THE FUNCTION ITSELF TO DO THE SAME THING WITH SUB DIRS OR FILES.
        }
    }

    // run it when needed
    scandir_rec('./rootDir');

你可以对这个函数进行很多变化。例如,打印一个'li'标签而不是PHP_EOL,以创建树形视图。

[编辑]

 <?php

function scandir_rec($root)
{
    // if root is a file
    if (is_file($root)) {
        echo '<li>' . basename($root) . '</li>';
        return;
    }

    if (!is_dir($root)) {
        return;
    }

    $dirs = scandir($root);
    foreach ($dirs as $dir) {
        if ($dir == '.' || $dir == '..') {
            continue;
        }

        $path = $root . '/' . $dir;
        if (is_file($path)) {
            // if file, create list item tag, and done.
            echo '<li>' . $dir . '</li>';
        } else if (is_dir($path)) {
            // if dir, create list item with sub ul tag
            echo '<li>';
            echo '<label>' . $dir . '</label>';
            echo '<ul>';
            scandir_rec($path); // <--- then recursion
            echo '</ul>';
            echo '</li>';
        }
    }
}

// init call
$rootDir = 'rootDir';
echo '<ul>';
scandir_rec($rootDir);
echo '</ul>';

1
谢谢您的回复,但是我无法按照我需要的方式使其工作,我已经更新了我的初始问题以获得更好的解释 :) - GRX

0
function deepScan($dir = __DIR__){
  static $allFiles = [];
  $allFiles[$dir] = [];

   $directories = array_values(array_diff(scandir($dir), ['.', '..']));
   foreach($directories as $directory){
     if(is_dir("$dir\\$directory")){
       foreach(deepScan("$dir\\$directory") as $key => $value) $allFiles[$key] = $value;
     }
     else{
      $allFiles[$dir][] = "$directory";
     }
   }
   return $allFiles;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接