最稳妥方式是用 glob() 配合 unlink() 清理日志:glob('logs/*.log') 获取文件列表,is_file() 判断并 filemtime() 检查是否超7天,再 unlink() 删除;避免 scandir() 忘拼路径、exec() 命令注入、file_put_contents() 截断写入中文件等风险。

PHP 脚本里用 glob() 找日志文件再删最稳妥
直接用 unlink() 配合 glob() 是清理 logs/ 下旧日志最轻量、兼容性最好的方式。不用依赖外部命令,不挑 PHP 版本(5.3+ 都行),也不怕 shell 权限问题。
常见错误是写成 foreach (scandir() as $f) { unlink($f); } —— 忘了拼路径,结果删的是当前目录下的同名文件,甚至误删脚本自己。
- 务必用
glob('logs/*.log')或glob('logs/access_*.log')明确匹配,避免误伤 - 加
is_file()判断,跳过目录或符号链接(有些日志轮转会留空目录) - 建议加时间判断:用
filemtime()看是否超过 7 天,别一上来全清
$logFiles = glob('logs/*.log');
foreach ($logFiles as $file) {
if (is_file($file) && filemtime($file) < time() - 7 * 86400) {
unlink($file);
}
}用 exec() 调 find 更快但有风险
当 logs/ 下文件超 10 万,PHP 的 glob() 会明显变慢,这时调系统 find 是合理选择。但必须注意权限和路径安全。
典型翻车点:变量没过滤就拼进 exec("find $path ..."),导致命令注入;或者 find 返回空时 PHP 报 Warning: exec(): Unable to fork 却被忽略。
立即学习“PHP免费学习笔记(深入)”;
- 绝对路径写死:
/usr/bin/find /var/www/myapp/logs/ -name "*.log" -mtime +7 -delete - 不要用变量拼接命令,尤其不能让用户输入影响
$path或-name - 加
@抑制 warning,但要用exec($cmd, $output, $returnCode)检查$returnCode === 0
rotate.php 里别用 file_put_contents($log, '', FILE_APPEND) 清空单个文件
有人想“清空”而不是“删除”,于是用 file_put_contents($f, '')。这看似安全,实则埋雷:如果日志正被其他进程(如 nginx、supervisor、另一个 PHP worker)以 FILE_APPEND 方式打开写入,清空操作会截断文件,但对方仍往原 offset 写,导致日志错乱或丢失。
- 清空 ≠ 安全,除非你 100% 确认该文件此刻无任何写入者
- 更稳妥的做法是重命名后删,比如
rename($f, $f . '.old');再unlink() - 生产环境建议用 logrotate 工具配
copytruncate,PHP 脚本只负责触发或归档
crontab 里跑 PHP 清理脚本要注意用户和工作目录
直接写 * 2 * * * php /path/to/clean_logs.php 很容易失败——因为 cron 默认工作目录是 root 用户的 home,logs/ 相对路径会找错;而且 web 用户(如 www-data)写的日志,root 可能没权限删。
- 必须用绝对路径:
cd /var/www/myapp && php ./clean_logs.php - cron 用户要和日志所有者一致,或至少有读写权限(
chown -R www-data:www-data logs/) - 加日志输出:
2>&1 >> /var/log/clean_logs.log,不然失败了你也看不到
真正麻烦的不是语法,而是日志文件可能被其他进程 hold 住、磁盘 inodes 耗尽、或者清理逻辑和 logrotate 冲突。上线前最好在测试环境用 lsof +D logs/ 看有没有进程锁着文件。











