scandir() 更适合递归删目录,因其稳定返回 . 和 .. 以准确识别子目录边界,无模式匹配开销且兼容特殊字符路径;而 glob() 在空目录或权限不足时可能漏项,且不递归、忽略隐藏文件。

为什么 scandir() 比 glob() 更适合递归删目录
因为 scandir() 能稳定返回 . 和 ..,这是判断子目录边界的可靠依据;而 glob("*/") 在空目录或权限不足时可能直接返回空数组,导致跳过子目录甚至误删父级。另外 scandir() 不做模式匹配,开销更低,对路径中含特殊字符(如空格、方括号)的目录更鲁棒。
实操建议:
- 始终用
array_diff(scandir($dir), ['.', '..'])过滤出真实子项,避免误入上级目录 - 删除前加
is_writable($path)判断,否则unlink()或rmdir()会静默失败(尤其在 Linux 的 sticky bit 目录下) - 不要用
foreach (glob($dir."/*") as $f)—— 它不递归,且遇隐藏文件(如.git)默认被忽略
unlink() 和 rmdir() 的调用顺序不能颠倒
必须先递归清理子项,再删当前目录本身。如果先调用 rmdir($dir),哪怕目录非空,PHP 也会抛出 Warning: rmdir(): Directory not empty 并中断执行,后续文件不会被处理。
典型错误写法:
if (is_dir($dir)) rmdir($dir); // 错!没清里面就删目录
正确逻辑链:
立即学习“PHP免费学习笔记(深入)”;
- 遍历当前目录所有子项(过滤掉
.和..) - 对每个子项:是目录就递归调用自身;是文件就
unlink() - 子项全部清理完后,再
rmdir($dir) - 最后一步必须单独做,不能塞进循环里
遇到权限问题时,chmod() 要谨慎加在 unlink() 前
Windows 下通常无此问题,但 Linux/macOS 中,文件可能有 0444(只读)权限,导致 unlink() 失败并报 Permission denied。此时需先改权限再删。
注意点:
- 只对文件调用
chmod($file, 0777),别对目录也 chmod —— 否则可能破坏继承权限或触发 SELinux 限制 - 改权限后立即
unlink(),不要中间穿插其他操作,防止竞态 - 用
@unlink($file)抑制警告即可,不必 try/catch —— 删除失败本就是预期状态之一,关键是要继续遍历其他项 - 若需强一致性,可先
clearstatcache(true, $file)避免 PHP 缓存旧的权限信息
大目录下递归删除容易超时或爆内存
PHP 默认 max_execution_time=30,删几千个文件就可能超时;而深度嵌套目录反复 scandir() 会产生大量小数组,内存增长明显。
缓解方式:
- 开头加
set_time_limit(0),但仅限 CLI 环境;Web 环境应改用分批异步(如队列或 AJAX 分页轮询) - 避免在递归函数内拼接长路径字符串,改用
realpath($dir)一次算清,后续用相对路径拼接减少重复解析 - 不用
array_map('basename', ...),直接在循环里用basename($full_path)—— 减少临时数组生成 - 极端场景(如 >10 万文件)建议改用 shell 命令:
exec("rm -rf " . escapeshellarg($dir), $output, $return),但务必确认$dir已校验且不含用户输入
最易被忽略的是符号链接:默认 is_dir() 对软链返回 false,所以软链指向的目录不会被递归进入;如果业务需要跟随软链,得显式用 is_link() + readlink() 处理,但多数情况应跳过软链以保安全。



















