php删除文件前必须检查file_exists()和is_writable(),直接unlink()可能触发警告或静默失败;用户输入路径需realpath()校验防遍历;windows/linux行为差异要求兼容处理;批量删除应先获取完整列表再遍历并检查返回值;unlink()不抛异常,须用error_get_last()捕获错误。

PHP删除文件前必须检查file_exists()和is_writable()
直接调用unlink()删不存在的文件会触发Warning: unlink(): No such file or Directory,不是致命错误但干扰日志、掩盖真实问题。更危险的是对非可写文件执行unlink()——在某些系统(如Windows或严格权限的Linux容器)下可能静默失败,你以为删了,其实还在。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 先用
file_exists(<code>$path)确认路径存在,再用is_writable(<code>$path)确认有写权限(注意:对目录,is_writable()判断的是能否在其内创建/删除文件,不是目录本身的写位) - 避免用
@unlink()压制警告——它同时屏蔽了真正的权限拒绝或NFS挂载异常等关键信号 - 如果
$path来自用户输入(比如URL参数或表单),务必先用realpath()解析并校验是否落在允许范围内,防止路径遍历(如../../etc/passwd)
unlink()在Windows和Linux下的行为差异
Linux下unlink()只要进程有目标文件的写权限就可删除,即使文件正被其他进程读取;Windows则要求文件完全未被占用,否则报Permission denied。这意味着同一段代码在开发环境(Windows)跑不过,在生产(Linux)却正常,容易漏测。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 遇到
unlink(): Permission denied,先用fopen(<code>$path, ‘r’)测试是否被占用,而不是立刻重试 - 不要依赖
sleep()等待释放——Windows上文件句柄可能卡住数秒,且无法预测 - 若需兼容双平台,考虑改用临时重命名再删:
rename(<code>$path,$temp_path)成功后再unlink(<code>$temp_path),重命名在Windows下比直接unlink()更健壮
批量删除时别用foreach直接unlink()
循环里反复调用unlink()本身没问题,但常见错误是边遍历目录边删文件,导致scandir()或glob()结果错乱,漏删或报Invalid argument。另一个坑是没做错误处理,一个文件删失败,整个流程就中断。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 先用
glob(<code>$pattern)或array_diff(scandir(<code>$dir), [‘.’, ‘..’])拿到完整文件列表,再遍历删除 - 每个
unlink()后检查返回值:if (unlink(<code>$file) === false) { error_log(“Failed to delete $file: ” . error_get_last()[‘message’]); } - 大量小文件场景(如日志清理),注意PHP的
max_execution_time限制,可配合set_time_limit(0),但更稳妥的是分批处理(每次50个)加usleep(10000)防IO打满
删除失败时error_get_last()比try/catch更有用
unlink()不抛出Exception,try/catch完全捕获不到。很多人误以为加了try就安全了,结果错误被吞掉,日志里只剩空行。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 删完立刻调
error_get_last(),只在unlink()返回false时才查——避免干扰其他函数的错误状态 - 注意
error_get_last()返回数组,['message']字段才是具体错误,如"No such file or directory"或"Permission denied",可据此做分支处理 - 线上环境别把完整
error_get_last()内容直接输出给前端,至少过滤掉路径等敏感信息
真正麻烦的从来不是“怎么删”,而是删之前没想清“该不该删”和“删了之后谁还在用”。特别是web服务器进程、CLI脚本、crontab任务之间共享文件时,删完立刻被另一个进程重建,或者删的是正在被fopen('a')追写的日志——这种竞态不会报错,但数据就丢了。