【问题标题】:PHP get path to every file in folder/subfolder into array? [duplicate]PHP将文件夹/子文件夹中每个文件的路径放入数组中? [复制]
【发布时间】:2012-08-27 08:44:49
【问题描述】:

可能重复:
PHP SPL RecursiveDirectoryIterator RecursiveIteratorIterator retrieving the full tree

我不知道从哪里开始。但是我必须获取文件夹中所有文件的路径以及路径中子文件夹的所有内容。例如,如果我有 1 个文件夹,其中有 5 个文件夹,每个文件夹中有 10 个 mp3,等等……这意味着我的数组必须找到这些文件的 50 个路径。

稍后假设我添加了一个文件夹,其中有 3 个文件夹,每个文件夹有 10 张图像。

我的代码现在需要找到 80 个路径并将它们存储在一个数组中。

我的问题有意义吗?

更新:

我想要的输出是将所有这些路径存储在一个数组中。

但我会“喜欢”动态代码,这意味着如果我以后再添加 10 个文件夹,每个文件夹有 17 个子文件夹,每个文件夹都有大量不同的内容。我希望数组保存所有文件的文件路径。我觉得这是有道理的。

【问题讨论】:

  • 我了解您的文件夹结构。现在你想要你的输出是什么。在您的问题中更新您的所需输出! :)
  • 你为什么要这么做。我对 php 几乎没有经验,但我认为这会杀死你糟糕的服务器。想象一下有 5000 人在读取您的目录结构!!!
  • 我为什么要这样做是因为我在 Flash as3 中有一个脚本,可以一次下载一个文件。 Flash 无法下载文件夹及其内容,因此我希望 php 创建文件夹/子文件夹的所有内容的字符串并将其发送回 Flash,它可以开始在应用程序中下载内容。 :)
  • 我们可能应该为它创建一个规范问题,对吧。

标签: php arrays multidimensional-array path directory


【解决方案1】:

您要查找的也称为递归目录遍历。这意味着,您将浏览所有目录并列出其中的子目录和文件。如果有一个子目录,它也会被遍历等等 - 所以它是递归的。

正如您想象的那样,这是您编写软件时所需要的一种常见的东西,而 PHP 支持您这样做。它提供了一个RecursiveDirectoryIterator,以便目录可以递归迭代,标准RecursiveIteratorIterator 进行遍历。然后,您可以通过简单的迭代轻松访问所有文件和目录,例如通过foreach

$rootpath = '.';
$fileinfos = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($rootpath)
);
foreach($fileinfos as $pathname => $fileinfo) {
    if (!$fileinfo->isFile()) continue;
    var_dump($pathname);
}

这个例子首先指定了你要遍历的目录。我一直在服用当前的:

$rootpath = '.';

下一行代码有点长,它先实例化the directory iterator,然后再实例化the iterator-iterator,这样树状结构就可以在单/扁平循环中遍历:

$fileinfos = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($rootpath)
);

这些$fileinfos 然后用一个简单的foreach 进行迭代:

foreach($fileinfos as $pathname => $fileinfo) {

在其中,有一个测试可以跳过所有目录的输出。这是通过使用迭代的SplFileInfo 对象来完成的。它由递归目录迭代器提供,在处理文件时包含许多有用的属性和方法。例如,您还可以返回文件扩展名、有关大小和时间的基本名称信息等等。

if (!$fileinfo->isFile()) continue;

最后我只输出 pathname 即文件的完整路径:

var_dump($pathname);

示例输出如下所示(此处为 Windows 操作系统):

string(12) ".\.buildpath"
string(11) ".\.htaccess"
string(33) ".\dom\xml-attacks\attacks-xml.php"
string(38) ".\dom\xml-attacks\billion-laughs-2.xml"
string(36) ".\dom\xml-attacks\billion-laughs.xml"
string(40) ".\dom\xml-attacks\quadratic-blowup-2.xml"
string(40) ".\dom\xml-attacks\quadratic-blowup-3.xml"
string(38) ".\dom\xml-attacks\quadratic-blowup.xml"
string(22) ".\dom\xmltree-dump.php"
string(25) ".\dom\xpath-list-tags.php"
string(22) ".\dom\xpath-search.php"
string(27) ".\dom\xpath-text-search.php"
string(29) ".\encrypt-decrypt\decrypt.php"
string(29) ".\encrypt-decrypt\encrypt.php"
string(26) ".\encrypt-decrypt\test.php"
string(13) ".\favicon.ico"

如果存在不可访问的子目录,以下将抛出异常。在实例化RecursiveIteratorIterator 时,可以使用一些标志来控制此行为:

$fileinfos = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator('.'),
    RecursiveIteratorIterator::LEAVES_ONLY,
    RecursiveIteratorIterator::CATCH_GET_CHILD
);

我希望这能提供信息。您也可以将其封装到您自己的类中,还可以提供FilterIterator 以将是否应列出文件的决定移出foreach 循环。


RecursiveDirectoryIteratorRecursiveIteratorIterator 组合的强大之处在于其灵活性。上面没有提到的是所谓的FilterIterators。我想我添加另一个示例,即利用两个自写的它们,将它们相互放置以组合它们。

  • 一种方法是过滤掉所有以点开头的文件和目录(在 UNIX 系统上这些文件和目录被视为隐藏文件,因此您不应将这些信息提供给外部)和
  • 另一个将列表过滤为文件。那是以前在foreach 内部进行的检查。

此用法示例的另一个更改是使用 getSubPathname() function,它返回从迭代的根路径开始的子路径,因此是您要查找的路径。

我还明确添加了 SKIP_DOTS flag 以防止遍历 ... (技术上不是真的必要的,因为过滤器会过滤它们以及它们是目录,但我认为它更正确)并作为UNIX_PATHS 的路径返回,因此无论底层操作系统如何,路径字符串始终是类似unix的路径,如果稍后通过HTTP请求这些值,通常是一个好主意,就像你的情况一样:

$rootpath = '.';

$fileinfos = new RecursiveIteratorIterator(
    new FilesOnlyFilter(
        new VisibleOnlyFilter(
            new RecursiveDirectoryIterator(
                $rootpath,
                FilesystemIterator::SKIP_DOTS
                    | FilesystemIterator::UNIX_PATHS
            )
        )
    ),
    RecursiveIteratorIterator::LEAVES_ONLY,
    RecursiveIteratorIterator::CATCH_GET_CHILD
);

foreach ($fileinfos as $pathname => $fileinfo) {
    echo $fileinfos->getSubPathname(), "\n";
}

此示例与上一个示例类似,尽管 $fileinfos 的构建方式略有不同。特别是关于过滤器的部分是新的:

    new FilesOnlyFilter(
        new VisibleOnlyFilter(
            new RecursiveDirectoryIterator($rootpath, ...)
        )
    ),

所以目录迭代器被放入一个过滤器,过滤器本身被放入另一个过滤器。其余的没有变化。

这些过滤器的代码非常简单,它们与accept 函数一起使用,即truefalse,用于获取或过滤掉:

class VisibleOnlyFilter extends RecursiveFilterIterator
{
    public function accept()
    {
        $fileName = $this->getInnerIterator()->current()->getFileName();
        $firstChar = $fileName[0];
        return $firstChar !== '.';
    }
}

class FilesOnlyFilter extends RecursiveFilterIterator
{
    public function accept()
    {
        $iterator = $this->getInnerIterator();

        // allow traversal
        if ($iterator->hasChildren()) {
            return true;
        }

        // filter entries, only allow true files
        return $iterator->current()->isFile();
    }
}

又是这样。当然,您也可以将这些过滤器用于其他情况。例如。如果您有另一种目录列表。

还有另一个示例性输出,其中 $rootpath 被删除:

test.html
test.rss
tests/test-pad-2.php
tests/test-pad-3.php
tests/test-pad-4.php
tests/test-pad-5.php
tests/test-pad-6.php
tests/test-pad.php
TLD/PSL/C/dkim-regdom.c
TLD/PSL/C/dkim-regdom.h
TLD/PSL/C/Makefile
TLD/PSL/C/punycode.pl
TLD/PSL/C/test-dkim-regdom.c
TLD/PSL/C/test-dkim-regdom.sh
TLD/PSL/C/tld-canon.h
TLD/PSL/generateEffectiveTLDs.php

不再有.git.svn 目录遍历或.builtpath.project 等文件列表。


FilesOnlyFilterLEAVES_ONLY 的注意事项: 过滤器基于SplFileInfo 对象(only regular files that do exist)明确拒绝使用目录链接。所以这是一个真正的基于文件系统的过滤。
另一种仅获取非目录条目的方法随 RecursiveIteratorIterator 一起提供,因为默认的 LEAVES_ONLY flag(此处也用于示例)。此标志不能用作过滤器,并且独立于底层迭代器。它只是指定迭代不应该返回分支(here: 目录迭代器的情况下)。

【讨论】:

  • 太棒了!哇!好的,我怎样才能得到没有字符串(33)的路径名等等。我怎样才能得到文件名呢? :)
  • 还有路径名减去前面的 ./ 吗? :)
  • 有多种方法可以实现这一目标。您可以通过foreach 中的一些后处理“扩展”它,SplFileInfo 对象在这里很有帮助,您在$fileinfo 中有这些对象。删除 basedir 实际上是微不足道的,因为它是 $rootpath 加上文件系统目录分隔符,其长度通常正好是一个字符。所以你在这里有substr($pathname, 2)。我将用另一个过滤器示例扩展答案,并将添加该子字符串操作示例。
  • 非常酷!还有名字?我猜这也应该很容易?哇,伙计。非常感谢。你摇滚。
  • 名字,你是说文件名吗?好吧,正如所写,SplFileInfo,该函数称为getFilename(),在您的情况下,只有文件名是$fileinfo->getFilename()。对于仅扩展使用$fileinfo->getExtension() 等等。您可以使用 SplFileInfo 对象的每个功能。这就是为什么它优于 readdir 的原因,因为你得到的是这些对象而不是哑字符串。
【解决方案2】:

如果你在linux上并且不介意执行shell命令,你可以在一行中完成所有这些

$path = '/etc/php5/*'; // file filter, you could specify a extension using *.ext
$files = explode("\n", trim(`find -L $path`)); // -L follows symlinks

print_r($files);

输出:

Array (
       [0] => /etc/php5/apache2
       [1] => /etc/php5/apache2/php.ini
       [2] => /etc/php5/apache2/conf.d
       [3] => /etc/php5/apache2/conf.d/gd.ini
       [4] => /etc/php5/apache2/conf.d/curl.ini
       [5] => /etc/php5/apache2/conf.d/mcrypt.ini
       etc...
      )

仅使用 PHP 的下一个最短选择是 glob-,但它不会像您想要的那样扫描子目录。 (你必须遍历结果,使用 is_dir() 然后再次调用你的函数

http://us3.php.net/glob

$files = dir_scan('/etc/php5/*'); 
print_r($files);

function dir_scan($folder) {
    $files = glob($folder);
    foreach ($files as $f) {
        if (is_dir($f)) {
            $files = array_merge($files, dir_scan($f .'/*')); // scan subfolder
        }
    }
    return $files;
}

所有其他方式都需要更多的代码来完成如此简单的事情

【讨论】:

  • :-) 没问题。关于 glob 的另一个很酷的事情是您可以指定一个不同的过滤器(例如 *.txt)并同时进行所有文件过滤(您不仅可以避免解析每个文件名来检查扩展名,而且您不需要甚至必须遍历它们,因为它们已经被过滤了)
  • 有没有办法在每个数组中减去 ./ 之前的值?我还可以获得一个没有路径和 ho 扩展名的文件名数组吗?顺便说一句,这太棒了。
  • $path 包含./ && rm -r / 时?也许我只是对在服务器上进行炮击有点偏执,但运行任何带有变量的命令都会出现问题 - 你知道在某些时候,一些新的开发人员会出现并想“如果我们能让这条路径由用户提供,我们就会得到 [Blah Benefit]”。否 -1,因为它仍然是一个有效的答案,但我永远不会这样做
  • 如果你想去掉'./',你必须遍历数组并清理它(或使用 array_walk)——或者你可以将完整路径传递给 glob/find(它是给出 './' 因为您正在搜索相对路径)@Basic - 使用 escapeshellarg() 将用户提供的路径传递给 shell,这还不错 - php.net/manual/en/function.escapeshellarg.php
  • @RobertMays​​Jr Sometimes it is
【解决方案3】:

步骤如下:

opendir 会打开目录结构

$dh = opendir($dir)

你接下来要做的是阅读$dh中的任何内容

$file = readdir($dh)

你可以在php手册中找到opendir对应的所有信息

谷歌搜索读取结构返回了这个

http://www.codingforums.com/showthread.php?t=71882

【讨论】:

  • 太棒了。谢谢你。看起来很棒。立即测试 :) 会尽快回复您。
  • 对于如此简单的事情过于复杂,您可以在 5 行代码中完成此操作(7 行带有括号以提高可读性)请参阅下面的答案以获取您需要的确切代码
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-03
  • 2017-10-28
  • 2021-02-17
  • 1970-01-01
  • 1970-01-01
  • 2015-05-23
相关资源
最近更新 更多