用 scandir() 获取文件列表并按扩展名分组是批量分类第一步,需排除 . 和 ..、过滤子目录、统一小写扩展名;移动前须用 mkdir(..., 0755, true) 创建目标目录并检查可写性;语义分类宜硬编码扩展名映射;大目录应改用 directoryiterator 流式处理防超时和内存溢出。

用 scandir() 获取文件列表再按扩展名分组
直接遍历目录拿到所有文件,是批量分类的第一步。别用 glob()——它不返回子目录信息,也不方便过滤掉 . 和 ..;scandir() 更可控,返回数组后可以 array_filter() 清洗。
注意点:
- 必须显式排除
.和..,否则后续pathinfo()会报错或返回空扩展名 - 用
is_file()过滤掉子目录,避免把文件夹当成待分类目标 - 扩展名统一转小写处理,防止
.JPG和.jpg被分到不同组
$files = scandir('/path/to/dir');
$files = array_filter($files, function($f) {
return $f !== '.' && $f !== '..' && is_file("/path/to/dir/$f");
});
$groups = [];
foreach ($files as $file) {
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
$groups[$ext][] = $file;
}
按扩展名移动文件前先检查目标目录是否存在
批量移动时最常踩的坑:目标分类目录没创建就直接 rename(),结果全部失败。PHP 不会自动建父级目录,mkdir() 必须加 recursive 参数(即第三个参数设为 true),且要检查返回值。
实操建议:
- 每个扩展名对应的目标路径用
sprintf()或字符串拼接生成,例如/path/to/dir/images/、/path/to/dir/docs/ - 调用
mkdir($target_dir, 0755, true)后,务必用is_writable($target_dir)再确认一次权限,尤其在 Linux 上容易因用户组问题失败 - 移动用
rename(),不要用copy()+unlink()——前者原子性强,后者中断会导致文件残留
区分「图片」「文档」「压缩包」这类语义类型需手动映射扩展名
PHP 没有内置的 MIME 类型到用途的映射表,finfo_file() 可以识别真实类型,但太重,对批量场景属于杀鸡用牛刀。简单分类直接硬编码映射更稳。
常见映射参考:
- 图片类:
['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp']
- 文档类:
['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'md'] - 压缩类:
['zip', 'rar', '7z', 'tar', 'gz', 'bz2'] - 代码类:
['php', 'js', 'py', 'java', 'cpp', 'html', 'css']
把上面数组做成配置,再用 in_array($ext, $image_exts) 判断归属,比反复写 <code>if ($ext === 'jpg' || $ext === 'png' ...) 更易维护。
大目录下执行要防超时和内存溢出
如果待分类目录有几千个文件,脚本很可能被 max_execution_time 中断,或者 scandir() 返回的大数组撑爆内存。不能靠调高 set_time_limit(0) 和 memory_limit 硬扛。
更实际的做法:
- 改用
DirectoryIterator流式遍历,边读边分组,不一次性加载全部文件名 - 每处理 100 个文件后调用
sleep(1),缓解服务器压力(适合后台任务) - 记录当前进度到临时文件,崩溃后可从断点继续,而不是全量重跑
真正麻烦的不是逻辑,是没人提醒你:Windows 下长路径(>260 字符)会导致 scandir() 静默失败,得用 \? 前缀绕过——这点几乎总被忽略。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











