scandir()配合递归是最直接、兼容性最好的php多层目录遍历方案,需过滤’.’和’..’、用is_dir()判目录、directory_separator拼路径、is_readable()预检权限,并避免内存溢出。

用 scandir() + 递归是最直接的写法
PHP 没有内置的“一键遍历多层目录”函数,scandir() 是最轻量、兼容性最好(5.0+ 都支持)的选择。它返回当前目录下所有文件和子目录名数组,不含路径,需要手动拼接。
递归时必须过滤掉 '.' 和 '..',否则会无限循环——这是新手踩得最多的一坑。
常见错误现象:Maximum function nesting level of '256' reached,基本就是没过滤这两个特殊目录项。
- 每次进入子目录前,先用
is_dir()判断是否为真实目录(避免符号链接或权限问题导致的意外中断) - 路径拼接统一用
DIRECTORY_SEPARATOR,别硬写'/'或'',windows 下会出错 - 如果只关心文件,进到某一层后发现是文件就直接处理,不必再递归;是目录才继续调用自身
遇到权限不足或 open_basedir 限制怎么办
不是所有目录都能读——尤其是 Web 服务用户(如 www-data)对系统级路径(/etc、/root)天然无权访问;open_basedir 更会直接抛出 Warning: scandir(): open_basedir restriction in effect。
不能靠 try/catch 捕获 warning(PHP 的 warning 默认不抛异常),得提前防御:
立即学习“PHP免费学习笔记(深入)”;
- 调用
scandir()前先用is_readable()检查目录可读性,不可读就跳过或记录日志 - 用
ini_get('open_basedir')查当前限制路径,判断目标目录是否在其范围内 - 若需绕过限制,只能改 PHP 配置(
php.ini或虚拟主机配置),代码层无法突破
RecursiveDirectoryIterator 看起来高级,但要注意迭代器陷阱
这个 SPL 类确实能自动处理嵌套,也支持过滤(比如跳过 . 和 ..),但默认不递归子目录——必须配合 RecursiveIteratorIterator 才行,而且容易漏掉关键参数。
典型误用:new RecursiveDirectoryIterator($path) 直接 foreach,结果只列出第一层,根本没进子目录。
- 正确组合是:
new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS)) -
FilesystemIterator::SKIP_DOTS必须显式传入,否则仍会包含.和..(哪怕文档说“默认跳过”,实际版本行为不一致) - 性能上,迭代器比手写递归略重,尤其在超大目录中,内存占用更明显;简单脚本没必要上
扫描结果要按需处理,别一股脑全存进数组
深层嵌套+大量小文件时,把所有路径都 array_push() 进一个大数组,很容易 OOM。特别是 CLI 脚本没设 memory_limit,一跑就崩。
真实场景里,你往往只需要:匹配某类文件、统计数量、找最大文件、生成清单……没必要缓存全部路径。
- 边遍历边处理:比如找到
*.log就立即file_get_contents()分析,不用存路径 - 用生成器(
yield)返回单个路径,调用方按需消费,内存恒定 - 如果真要汇总,考虑写入临时文件或数据库,而不是全放内存
递归深度、路径合法性、权限边界、内存水位——这四个点任何一个没卡住,都可能让脚本在生产环境静默失败。别信“本地跑通就行”。