应使用复合后缀白名单(如tar.gz)并按长度降序匹配,配合str_ends_with判断文件名结尾,命中即返回;未命中则回退至strrpos提取单后缀,且函数调用前须先用basename()处理路径。

当文件名包含多个点号(如 report.v2.alpha.tar.gz)时,直接用 strrpos($file, '.') + 1 截取会错误地返回 gz 而非真实压缩格式 tar.gz,必须区分「逻辑后缀」与「物理扩展名」。
识别复合后缀:先判断是否为已知多段归档格式
第一步:定义一组公认的复合后缀白名单数组,按长度从长到短排序,避免 tar.gz 被 gz 提前截断:
$compoundSuffixes = ['tar.gz', 'tar.bz2', 'tar.xz', 'tar.zst', 'zip.xz'];
第二步:遍历该数组,对每个后缀检查文件名是否以它结尾——必须用 str_ends_with()(PHP 8.0+)或 substr($file, -strlen($s)) === $s,不能用 strpos 或 strstr,否则会误匹配中间子串。
立即学习“PHP免费学习笔记(深入)”;
第三步:一旦命中,立即返回该后缀并终止循环;若全部未命中,则回落到单点后缀逻辑。
提取纯文件名(不含任何后缀)
方法一:用 pathinfo() 先分离路径与 basename,再对 basename 做复合后缀剥离
$base = basename($file);
$cleanName = $base;
遍历 $compoundSuffixes 数组,每轮执行:if (str_ends_with($cleanName, '.' . $s)) { $cleanName = substr($cleanName, 0, -strlen('.' . $s)); break; }。这一步必须加 break,否则可能连续剥掉两层(比如 .tar.gz 后又误剥 .gz)。
方法二:正则一步到位(仅限 ASCII 文件名)
preg_match('/^(.+?)(?:\.(tar\.(?:gz|bz2|xz|zst)|zip\.xz))$/i', $file, $m);
若匹配成功,$m[1] 即为无后缀文件名;失败则说明不是复合格式,直接用 pathinfo($file, PATHINFO_FILENAME)。
安全剥离后缀的自定义函数
写一个健壮函数,同时支持复合与单段后缀,并拒绝空后缀或非法点开头的文件名:
function getRealExtension($filename) {
if (!is_string($filename) || $filename === '' || $filename[0] === '.') {
return '';
}
$compound = ['tar.gz', 'tar.bz2', 'tar.xz', 'tar.zst', 'zip.xz'];
foreach ($compound as $s) {
if (str_ends_with($filename, ".$s")) {
return $s;
}
}
$lastDot = strrpos($filename, '.');
return $lastDot === false ? '' : substr($filename, $lastDot + 1);
}
注意:【必须在调用前确保 $filename 已经过 basename() 处理】,否则路径中的斜杠和点会干扰判断,例如 /var/log/app.v2.log.gz 传入后会错误识别为 log.gz。
批量重命名时保留复合后缀结构
步骤一:用上一步的 getRealExtension() 提取原始后缀
步骤二:用 pathinfo($file, PATHINFO_FILENAME) 获取不带后缀的主名
步骤三:拼接新名称时,不要简单替换最后的点,而要完整替换整个后缀部分——$newName = $pureName . '.' . $newExt;
步骤四:调用 rename($oldPath, dirname($oldPath) . '/' . $newName);
这一步操作起来很简单,直接把文件拖进去就行。



















