RecursiveDirectoryIterator + RecursiveIteratorIterator 是最稳方案,自动跳过 . 和 ..、支持过滤与深度控制、不依赖 shell 命令;需 try/catch 捕获 UnexpectedValueException,软链接需显式启用 FOLLOW_SYMLINKS 并设最大深度。

用 RecursiveDirectoryIterator 遍历目录最稳
PHP 原生提供了一套基于 SPL 的递归迭代器,RecursiveDirectoryIterator + RecursiveIteratorIterator 组合是目前最可靠、最符合语义的方案。它自动跳过 . 和 ..,支持过滤、深度控制,且不依赖 shell 命令或手动拼接路径。
常见错误是直接用 scandir() 手写递归——容易漏掉权限拒绝目录(如 /proc 下某些子目录)、路径拼接出错(/a//b)、或无限递归软链接(默认不检测)。
- 必须用
try/catch包裹迭代过程,因为遇到无权限目录会抛出UnexpectedValueException - 若需排除软链接,构造时传入标志
RecursiveDirectoryIterator::SKIP_DOTS | RecursiveDirectoryIterator::FOLLOW_SYMLINKS(默认不跟随) - 遍历时调用
$item->getPathname()得到完整路径,$item->getFilename()得到当前项名,避免自己basename()
用 scandir() 手写递归要注意路径和异常
如果项目还停留在 PHP 5.2 或必须兼容极旧环境,scandir() 是唯一选择,但必须自己处理边界和错误。
典型报错是 Warning: scandir(/path/to/dir): failed to open dir,说明权限不足或路径不存在;更隐蔽的问题是 Windows 下反斜杠导致 is_dir() 返回 false(因路径含 被误判为转义)。
立即学习“PHP免费学习笔记(深入)”;
- 统一用
str_replace('\', '/', $path)或realpath($path)规范路径分隔符 -
scandir()返回结果包含.和..,必须显式array_diff($files, ['.', '..'])过滤 - 每次进入子目录前,先用
is_readable()判断可读,再用is_dir()判断是否为目录,顺序不能颠倒(否则对不可读目录调用is_dir()可能静默失败)
glob() 适合简单模式匹配,不适合深层遍历
glob('path/**/*', GLOB_BRACE | GLOB_ONLYDIR) 看起来简洁,但实际限制很多:PHP 5.6+ 才支持 **,且 GLOB_ONLYDIR 不过滤符号链接,GLOB_BRACE 在某些系统上不可用。
更关键的是,glob() 一次性返回所有匹配路径,内存占用随目录深度/文件数线性增长,遍历一个含 10 万文件的目录可能直接 OOM。
- 仅推荐用于单层或已知浅层结构(如
config/*.php) - 若坚持用
**,务必加set_error_handler()捕获glob()失败,它不抛异常,只触发 warning - Linux 下可用
shell_exec('find ' . escapeshellarg($path) . ' -type f')替代,但需确保escapeshellarg严格包裹路径,否则有命令注入风险
递归遍历时软链接和权限问题最容易被忽略
绝大多数示例代码默认把软链接当普通目录处理,导致无限循环(如 /var/www/html -> /var/www/);而权限问题常表现为“部分目录没遍历到”,却查不出原因。
真实生产环境里,/sys、/proc、Docker 容器挂载点等位置经常出现无权限子目录,或软链接指向自身层级。
-
RecursiveDirectoryIterator默认不跟随软链接,安全;如需跟随,必须显式加RecursiveDirectoryIterator::FOLLOW_SYMLINKS,并配合setMaxDepth()防止失控 - 手写递归中,用
is_link($path)提前判断,再决定跳过、报错或记录 - 权限异常不要静默吞掉——至少
error_log("skipped {$path}: " . $e->getMessage(), 4)记到日志,否则排查时完全无迹可寻



















