03.27
昨天在 PTT 看到一個問題關於讀取 Unicode 檔案名稱的問題…
作者 danny0838 (道可道非常道) 看板 PHP
標題 [請益] Windows 上的中文檔案
時間 Thu Mar 25 00:59:32 2010
──────────────────────────────────────因為某些因素,小的有用 php 存取 windows 系統上中文檔名檔案的需求..
我知道繁中版 windows 的檔名是用 big5 編碼,
假設一個檔案名為 “測試.txt”,而 php 程式是 UTF-8 編碼,
用 iconv(“UTF-8″,”big5//IGNORE”,”測試.txt”) 即可讀到該檔案。但如果檔名包含 unicode 呢?
比如 “消化酶.txt”(酉每是 unicode 字元),它會被編為什麼碼?第二個問題是,若用 scandir 之類的方式掃資料夾,
純 big5 檔名會以 big5 格式傳回,可做進一步應用;
但夾有 unicode 的檔案傳回值一部分會變成 ? (UTF-8 無法對應到 big5 者),
造成路徑錯誤,掃到檔案卻無法進一步處理。所以,要如何用 php 存取 windows 系統下,檔名夾有 unicode 字元的檔案呢?
先講解答,答案是沒辦法,但如果是用 Linux + Unicode 檔名,同樣的條件則是完全沒問題。
可以寫一段簡單的測試程式來驗證
<?php
$dir = opendir("./");
while (($file = readdir($dir)) !== false)
{
echo "filename: " . $file . "\n";
}
closedir($dir);
php?>
執行結果:

但執行環境如果換到 Window 下面的話,一樣的程式碼,則會帶來不同的結果:

實際開啟檔案,裡面的「酶」被換成了「?」這個字元,這是因為 PHP 在 Windows 讀檔名的時候,沒辦法讀出 Unicode 字元。因為 PHP 在 Windows 對 Unicode 支援並不完整。如果用 .NET Framework (原生 Unucode) 就不會有這樣的問題了。我寫了一個簡單的程式來測試,原始碼如下:
using System;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string myDir = "C:\\Users\\roga\\Documents\\php";
byte[] myBytes;
string myTmpStr;
string myString;
string[] files = null;
files = System.IO.Directory.GetFiles(myDir);
foreach (string file in files)
{
try
{
System.IO.FileInfo fi = new System.IO.FileInfo(file);
Console.WriteLine("\n檔案資訊:");
Console.WriteLine("{0}: {1}, {2}", fi.Name, fi.Length, fi.CreationTime);
Console.WriteLine("\n檔案內容:");
System.IO.StreamReader myFile = new System.IO.StreamReader(myDir + "\\" + fi.Name);
myString = myFile.ReadToEnd();
myFile.Close();
Console.WriteLine(myString);
Console.WriteLine("\n改用 Unicode 編碼讀取:");
myBytes = System.Text.Encoding.Unicode.GetBytes(fi.Name);
myTmpStr = System.Text.Encoding.Unicode.GetString(myBytes);
Console.WriteLine(myTmpStr);
string myHex = BitConverter.ToString(myBytes);
Console.WriteLine("\n轉成 HEX 輸出確認是否正確:");
Console.WriteLine(myHex);
}
catch (System.IO.FileNotFoundException e)
{
Console.WriteLine(e.Message);
continue;
}
}
int pauseTime = 20000;
System.Threading.Thread.Sleep(pauseTime);
}
}
}
- 開一個新目錄,然後建立一個檔案叫做「消化酶.txt」
- 用上面這隻程式讀取目錄下所有檔案,並且顯示內容
- 雖然在畫面上一樣顯示不出來「酶」這個字,是因為在 Console 顯示不出 CP950 以外的字,但可以確定 C# 操作的 resource 底層可以正確存取這個檔案物件。
- 驗證方法是把取回的檔名轉成 Unicode (UTF-16) ,然後以 HEX 碼印出,再對照實際檔名字串(以 Unicode 編碼)的 HEX Code 這樣就可以確定沒錯了。
所以說,用 PHP 要讀取 Windows 含有 Unicode 字元的檔案,目前是有困難。


既然 C# 沒問題,那就不該是「Windows 自己的相容性都做不好。」若底層 OS 做不好,上層的應用程式開發工具又怎有辦法解決?MBCS 版 API 因為要向前相容而仍然使用 CP950,「酶」這個字的確是無解了。但我確定 Unicode 版 API 沒問題,可以用 Unicode 檔名(UCS2-LE)。問題應該這麼說,是 PHP 的 win32 版有問題,沒用 Unicode 版 win32 API 實作,當然匯出問題。
抱歉是我一開始沒有弄清楚這之間的關聯,後來我有修改了一下文章,PHP 目前不支援 unicode 是最大的問題,目前只能等到 PHP6 解決了…orz
在 php.net 上面有看到幾個類似的問題 http://bugs.php.net/bug.php?id=46990 和 http://bugs.php.net/bug.php?id=47096 ,但結論是 Can’t be fixed in 5.2 neither in 5.3. It will work smoothly in 6.x (unicode support).