直接覆盖原文件高危,因file_put_contents非原子操作,崩溃或磁盘满会导致原文件清空且无法回滚;正确做法是临时文件法:写新、校验、rename原子替换、失败清理。

为什么不能直接覆盖原文件
直接用 file_put_contents($path, $new_content) 覆盖原文件,看似简单,实则高危:进程崩溃、磁盘满、权限丢失时,原文件会瞬间变空或截断,且无回滚路径。PHP 不提供原子写入语义,file_put_contents 本质是 unlink + fopen + fwrite,中间任意一步失败就留残局。
临时文件法的正确写法
核心是“写新、校验、替换、清理”四步闭环,不是单纯换个文件名。
- 用
tempnam(sys_get_temp_dir(), 'php_replace_')生成唯一临时路径,避免并发冲突 - 写入后必须调用
fflush($fp)+fsync($fp)(PHP 8.5.5 支持),确保内容落盘,而非仅在内核缓冲区 - 替换前用
sha1_file($temp_path) === sha1_file($original_path)校验内容一致性(可选但推荐) - 用
rename($temp_path, $original_path)原子替换——这是 POSIX 系统唯一真正原子的操作 - 失败时手动清理临时文件:
is_file($temp_path) && unlink($temp_path)
跨文件系统时 rename() 失败怎么办
当 rename() 报错 Invalid cross-device link,说明源和目标不在同一挂载点(如 /tmp 和 /var/www 分属不同磁盘)。此时无法原子替换,必须退化为“复制+删除”:
- 先
copy($original_path, $temp_path),检查返回值是否为true - 再
unlink($original_path),失败则立即中止,保留临时文件供人工恢复 - 最后
rename($temp_path, $original_path)只用于同设备场景;跨设备时,copy后直接unlink即可,无需再 rename
批量处理时临时文件命名易踩的坑
批量遍历目录做替换时,很多人用固定前缀(如 temp.txt)导致多进程覆盖、冲突或残留。
立即学习“PHP免费学习笔记(深入)”;
- 每个文件必须独立生成临时路径:
$temp = tempnam(dirname($file), basename($file) . '_') - 不要复用同一个临时句柄或路径,尤其在
foreach循环里 - 临时文件写入失败时,
tempnam()返回false,需立刻throw new RuntimeException,而不是静默跳过 - Windows 下注意
tempnam()可能返回反斜杠路径,统一用str_replace('\', '/', $temp)或直接realpath($temp)规范化
copy 和 unlink 这两个非原子操作,一旦出错,靠日志也救不回原始文件。



















