Contents

[PHP]basename採雷記

PHP 的 basename() 函式用來從路徑字串中取出檔名部分,第二個參數可以去掉指定的副檔名。這個函式看起來簡單,但實際使用時有幾個地方容易踩雷。

基本用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$path = '/var/www/html/images/photo.jpg';

// 取得完整檔名(含副檔名)
echo basename($path);           // photo.jpg

// 取得不含 .jpg 的檔名
echo basename($path, '.jpg');   // photo

// 注意:第二個參數只是「去掉後綴字串」,不是「去掉副檔名」
echo basename($path, '.php');   // photo.jpg(沒有匹配,不會去掉)

第二個參數的行為容易誤解:它只是做字串比對,如果路徑的結尾不匹配指定的字串,就完全不做任何處理,而不是智慧判斷副檔名。

Linux 上的中文路徑問題

basename() 在 Linux 環境下處理含有多字節字符(如中文)的路徑時,可能會出現截斷或回傳空字串的問題。原因是 PHP 的 basename() 底層依賴 C 函式庫的 locale 設定,預設 locale 為 C(即 POSIX),不認識 UTF-8 多字節字符。

1
2
3
// 在 C locale 下,可能回傳空字串或截斷
$path = '/var/www/html/圖片/照片.jpg';
echo basename($path);  // 可能輸出錯誤結果

解決方法一:設定 Locale

在使用 basename() 之前,將 locale 設定為支援 UTF-8 的語系:

1
2
3
4
5
6
setlocale(LC_ALL, 'zh_TW.UTF-8');
// 或
setlocale(LC_ALL, 'en_US.UTF-8');

$path = '/var/www/html/圖片/照片.jpg';
echo basename($path);  // 照片.jpg(正確)

注意:這個設定需要伺服器上已安裝對應的 locale。可用 locale -a 確認可用的 locale 列表。

解決方法二:使用 mb_substr 自訂實作

如果無法修改伺服器 locale,可以用 Multibyte String 函式自訂一個 mb_basename

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function mb_basename(string $path, string $suffix = ''): string {
    // 統一將路徑分隔符轉為 /
    $path = str_replace('\\', '/', $path);
    
    // 取最後一個 / 之後的部分
    $basename = mb_substr($path, mb_strrpos($path, '/') + 1);
    
    // 如果指定了 suffix,去掉結尾的 suffix
    if ($suffix !== '' && mb_substr($basename, -mb_strlen($suffix)) === $suffix) {
        $basename = mb_substr($basename, 0, mb_strlen($basename) - mb_strlen($suffix));
    }
    
    return $basename;
}

// 測試
echo mb_basename('/var/www/html/圖片/照片.jpg');         // 照片.jpg
echo mb_basename('/var/www/html/圖片/照片.jpg', '.jpg'); // 照片

跨平台路徑分隔符

Windows 使用反斜線 \,Linux 使用正斜線 /basename() 在不同平台的行為不同,若程式可能跨平台執行,建議在處理路徑前先統一分隔符:

1
2
3
// 統一為正斜線
$path = str_replace('\\', '/', $path);
echo basename($path);

參考資料