批量重命名需确保路径绝对化、目标目录存在且可写、中文编码正确;跨磁盘需用copy+unlink;保留时间戳须stat+touch或touch -r。

PHP里用 rename() 批量改文件名要小心路径和编码
直接用 rename() 改名本身没问题,但批量操作时容易出错——尤其是中文文件名、相对路径没写全、目标目录不存在,或者 Windows 和 Linux 下的大小写敏感差异。别指望一次循环就跑通,得先确保路径绝对化、目标目录可写、文件名不冲突。
-
rename()第一个参数是旧路径,第二个是新路径,二者都必须是完整路径(建议用realpath()或__DIR__ . '/xxx'拼) - 中文文件名在 PHP 7.4+ 的 Linux 环境下一般没问题,但 Windows 上若系统 locale 不是 GBK,
scandir()可能返回乱码,此时应避免直接用文件名拼接,改用mb_convert_encoding()转码 - 务必提前
mkdir()目标父目录,否则rename()会失败并返回false,且不报错
用 glob() + foreach 实现安全批量重命名
比 scandir() 更可控:能按扩展名过滤,天然跳过 . 和 ..,且返回的是带路径的完整字符串,减少拼接错误。
foreach (glob(__DIR__ . '/old/*.jpg') as $old) {
$new = str_replace('old/', 'new/', $old); // 简单前缀替换
if (!is_dir(dirname($new))) {
mkdir(dirname($new), 0755, true);
}
if (rename($old, $new)) {
echo "OK: " . basename($old) . " → " . basename($new) . "/n";
} else {
echo "FAIL: " . basename($old) . "/n";
}
}
- 上面例子把
/old/xxx.jpg改成/new/xxx.jpg,注意str_replace()是字符串替换,不是正则;如需更灵活匹配,请用preg_replace() - 每次
rename()后检查返回值,不要只靠echo或静默跳过 - 如果原目录和目标目录跨磁盘(比如从
/home移到/mnt/usb),rename()会失败,此时得用copy()+unlink()
命令行替代方案:find + mv 更快更稳
PHP 脚本适合小规模或需业务逻辑介入的场景;纯文件名替换,Shell 效率高、原子性强,而且绕开了 PHP 的编码和权限陷阱。
find ./photos -name "IMG_*.jpg" -exec bash -c 'mv "$1" "${1/IMG_/NEW_}"' _ {} /;
- 这个命令把当前目录下
photos/中所有IMG_xxx.jpg改成NEW_xxx.jpg,${1/IMG_/NEW_}是 Bash 参数扩展,比sed更轻量 - Windows 用户可用 Git Bash 或 WSL 运行;PowerShell 也能做,但语法更啰嗦,容易因空格、括号出错
- 执行前加
echo预览:把mv换成echo mv,确认输出无误再删掉echo
重命名时保留原始时间戳是个隐藏坑
rename() 和 mv 都会更新文件的 mtime 和 ctime,如果你依赖文件修改时间做同步或备份判断,这会导致误判。PHP 里没法用 rename() 保留时间戳;必须用 touch() 手动恢复:
立即学习“PHP免费学习笔记(深入)”;
$stat = stat($old); rename($old, $new); touch($new, $stat['mtime'], $stat['atime']);
-
stat()返回数组里mtime是修改时间,atime是访问时间,ctime(inode change time)无法通过touch()设置,PHP 没有对应函数 - Linux 下可用
touch -r oldfile newfile命令复制时间戳,比 PHP 更准 - 这个细节几乎没人提,但一旦你用
rsync -t或基于 mtime 的增量备份,就会发现同步异常
