PHP批量删除文件须先用is_file()和is_writable()校验,再unlink();推荐glob()配合array_filter()安全过滤,分批处理防超时,严格限定目录白名单并启用DRY_RUN预检。

PHP批量删除文件前必须检查 is_file() 和权限
直接用 unlink() 循环删一堆路径,很容易遇到“Permission denied”或“No such file or directory”警告——尤其当路径里混着目录、符号链接、不存在的条目时。PHP不会自动跳过,unlink() 遇到非普通文件会报 Warning: unlink(): No such file or directory 或直接失败。
正确做法是:先用 is_file() 确认目标是真实存在的普通文件(排除目录、socket、设备文件等),再检查当前进程是否有写权限(is_writable())。不验证就删,轻则报错中断,重则误删失败却以为成功。
-
glob()或scandir()获取路径列表后,务必逐个过滤:if (is_file($path) && is_writable($path)) { unlink($path); } - Windows 下注意路径分隔符,统一用
/或DIRECTORY_SEPARATOR,避免unlink()因反斜杠转义失败 - 如果文件被其他进程占用(如日志正在写入),
unlink()会失败,此时可加@unlink()抑制警告,但更推荐先clearstatcache(true, $path)再试一次
用 glob() 匹配 + array_filter() 安全过滤最常用
想删 logs/*.log 或 tmp/*_temp.php 这类有规律的文件,glob() 比 scandir() 更直接。但它返回的数组可能含 .、.. 或空字符串,且不区分文件/目录——所以不能裸用。
典型安全写法:
立即学习“PHP免费学习笔记(深入)”;
$files = glob('logs/*.log');
$files = array_filter($files, function($f) {
return is_file($f) && is_writable($f);
});
foreach ($files as $file) {
@unlink($file);
}
注意:glob() 默认不递归,要删子目录下匹配的文件得手动遍历目录树;另外它不支持正则,复杂模式得换 RegexIterator 或 RecursiveDirectoryIterator。
删大量文件时避免阻塞,用 set_time_limit(0) 和分批处理
默认 PHP 脚本超时 30 秒,删几百个文件很可能超时中断。虽然加 set_time_limit(0) 能解除限制,但更稳妥的是分批执行——既防超时,也减少单次 I/O 压力。
- 每批处理 50 个文件:
array_chunk($files, 50)拆分,循环中调用sleep(1)(可选)缓解服务器负载 - 命令行运行(
php delete.php)比 Web 请求更稳定,没有 max_execution_time 干扰,也不受输出缓冲影响 - 若需记录结果,用
file_put_contents('delete.log', "$file → " . (unlink($file) ? 'OK' : 'FAIL') . "\n", FILE_APPEND),别用echo输出大量内容
误删无法恢复,必须加白名单或预检机制
批量删文件本质是高危操作,没回滚、没回收站。线上环境绝对不能靠路径字符串拼接来决定删什么——比如 $dir = $_GET['dir']; glob("$dir/*.txt") 就是严重漏洞,可能被利用删系统文件。
实际项目中应强制约束范围:
- 硬编码允许目录:只允许删
ROOT_DIR . '/uploads/temp/'或ROOT_DIR . '/cache/'下的文件 - 路径规范化校验:
realpath($path) === $allowed_dir || strpos(realpath($path), $allowed_dir) === 0 - 上线前加预检开关:
if (defined('DRY_RUN') && DRY_RUN) { echo "Would delete: $file\n"; continue; }
真正执行前漏掉路径合法性校验,或者没做 is_file() 判断,是线上事故最高发的原因。不是代码写得不够短,而是边界没卡严。



















