php中删除文件必须先校验file_exists()、is_writable()和realpath()白名单,否则易导致静默失败、路径遍历或跨平台兼容问题;unlink()不抛异常且不返回错误原因,须检查返回值并用error_get_last()捕获细节。

直接调用 unlink() 删文件,大概率会出问题——不是删不掉,就是删错了,还可能被利用路径遍历攻击。
为什么 unlink() 不能裸用
它不检查文件是否存在、是否可写、路径是否合法,失败时只返回 false,连错误原因都不告诉你。更麻烦的是:unlink() 在 Windows 下要求文件完全未被占用,Linux 下却允许“边读边删”,同一段代码在开发和生产环境行为不一致。
- 对不存在的路径调用
unlink()→ 触发Warning: unlink(): No such file or directory,干扰日志 - 对只读文件或权限不足的文件调用 → 在某些系统(如 Windows 容器)下静默失败,你以为删了,其实还在
- 路径来自用户输入(如
$_GET['file'])→ 若没过滤,../../etc/passwd这类构造可直达系统关键文件
必须做的三步预检:存在性、可写性、路径合法性
别跳过任何一步,它们各自解决不同层面的风险:
-
file_exists($path):确认路径真实存在(注意:对符号链接,它检查的是目标是否存在) -
is_writable($path):确认当前 PHP 进程有权限删除该文件(注意:对目录,它判断的是能否在其内创建/删除文件,不是目录本身的w位) -
realpath($path)+ 白名单校验:解析绝对路径后,检查是否落在允许范围内,例如只允许操作/var/www/uploads/下的文件
示例片段:
$user_input = $_GET['file'] ?? '';
$base_dir = '/var/www/uploads/';
$full_path = realpath($base_dir . $user_input);
if ($full_path === false || strpos($full_path, $base_dir) !== 0 || !is_file($full_path)) {
die('非法文件请求');
}
if (!file_exists($full_path) || !is_writable($full_path)) {
die('文件不可访问或无删除权限');
}
Windows 兼容性问题:删不掉时别瞎重试
遇到 unlink(): Permission denied,大概率是文件正被其他进程(如 Excel、记事本、杀毒软件)锁住。这时候:
- 不要用
sleep()等几秒再试——无法预测释放时间,且可能卡住整个请求 - 先用
fopen($path, 'r')测试是否可读,若失败,基本可判定被占用 - 更健壮的做法是“先重命名再删”:
rename($path, $temp_path)成功后再unlink($temp_path);重命名在 Windows 下比直接unlink()更可靠
批量删除时最容易漏掉的两个坑
不是语法错,而是逻辑陷阱:
- 边遍历边删:用
scandir()或glob()获取列表后,如果在循环里直接unlink(),可能导致后续readdir()返回异常或漏项(尤其当目录结构动态变化时) - 忽略返回值:一个文件删失败,
foreach不会中断,但你也不知道哪几个没删成。务必检查unlink()返回值,并用error_get_last()捕获底层错误信息用于排查
正确做法是先完整收集所有待删路径,再统一处理:
$files = glob('/var/www/uploads/*.tmp');
foreach ($files as $file) {
if (is_file($file) && is_writable($file)) {
if (!unlink($file)) {
$err = error_get_last();
error_log("unlink failed on {$file}: " . ($err['message'] ?? 'unknown error'));
}
}
}
真正危险的从来不是函数不会用,而是删之前没想清楚“这个路径到底是谁给的、它指向哪里、我有没有资格动它”。路径校验和权限预检不是可选项,是每次调用 unlink() 前必须执行的硬性步骤。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











