The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

PowerShell 執行非 .NET 程式在輸出資料時要注意編碼問題

我今天發現 PowerShell 有個非常需要小心運用的地方,就是在執行非 .NET 應用程式時,當利用 Out-File 或 pipe 運算子 ( > ) 輸出至文字檔時很容易會有編碼錯亂的問題,如果一不小心設定,你輸出的所有文字全部都會變成亂碼,而且是無法復原的亂碼!

我今天利用 PowerShell 執行 mysqldump 指令將 MySQL 資料庫匯出成檔案,執行的指令如下:

&mysqldump -u $strUserName "-p$strUserPassword" $strDBName > "$strDBName-$Today.sql"

看起來是多麼的理所當然阿,但魔鬼總在細節裡

首先,當你執行 mysqldump 指令時,所預設輸出字集是 UTF-8 編碼,你自然也會很理所當然的認為輸出的 "$strDBName-$Today.sql" 檔案也是 UTF-8 編碼,而且結果也不例外,輸出的檔案用 Notepad++ ���啟後的確也是 UTF-8 編碼。

在 DOS 模式下執行 mysqldump 指令匯出資料的時候中文正常的,而且是 UTF-8 編碼

在 DOS 模式下執行 mysqldump 指令,再匯出資料的時候中文是正常的,而且是 UTF-8 編碼

但是在 PowerShell 模式下執行 mysqldump 指令匯出資料的時候中文卻是亂碼,但文件還是 UTF-8 編碼!

在 PowerShell 模式下執行 mysqldump 指令匯出資料的時候中文卻是亂碼,但文件還是 UTF-8 編碼!

如果開啟 mysqldump 匯出的資料檔,在編輯器第一頁可能看不到任何中文字,所以你也許會誤以為檔案匯出成功,而放心的度假去了。

由於 PowerShell 核心就是 .NET 執行環境,當你執行非 .NET 應用程式時,PowerShell 卻會以系統預設字集當成所有文字派送管道(pipeline),所以就算 mysqldump 輸出的文字編碼是 UTF-8,PowerShell 依然會將他認為是 Big5 (繁體作業系統下的預設編碼),但是 PowerShell 雖然是以 Big5 作為 mysqldump 的輸入編碼,但透過大於符號 ( > ) 輸出的檔案卻是以 UTF-16 編碼 ( 因為 PowerShell 就等於 .NET,所有 .NET 的文字處理預設用 UTF-16 進行編碼 )。

為了驗證上述觀念,我利用 Jeffery Lee 所開發的 中文編碼解析 Ver 1.31 版 分析被弄成亂碼的文字:

利用 Jeffery Lee 所開發的 中文編碼解析 Ver 1.31 版 分析被弄成亂碼的文字 

你可以看到,原本的 [平裝] 透過 mysqldump 輸出時的確是 UTF-8 編碼,但是 PowerShell 卻以 Big5 碼進行處理,我試著用內碼輸入法依序輸入 E5 B9 B3 E8 A3 9D 等十六進制位元,所得到的文字正好就是變亂碼的 [撟唾?]。其中 E5 B9 正是 Big5 編碼的 [],B3 E8 正是 Big5 編碼的 [],而最後的 A3 9D 並不是有效的 Big5 文字,所以才會出現問號 ( ? )。

我試著用內碼輸入法依序輸入 E5 B9 B3 E8 A3 9D 等十六進制位元

這就是典型的文字編碼錯亂事件,而且由於這種文字錯亂的現象是先從 UTF-8 轉成 Big5 編碼,此時已經有些編碼轉不過去了,所以變成問號,也就是說原本的 A3 9D 這兩個位元(byte)只會變成一個問號位元 ( 3F ),文字已經不完整了。之後再從一個不完整的 Big5 字元再轉成 UTF-16 編碼,這時的文字已經殘破不堪,而且幾乎無法逆轉。

這種問題我遇到不止一次了,老外寫軟體總是不仔細考慮非英語系使用者的困難,而且像這種問題微軟根本不應該再允許發生,透過程式比對非 .NET 應用程式的輸出是不是 UTF-8 應該很容易阿,為什麼不多做一點檢查呢?!

若要解決 mysqldump 輸出文字混亂的情況,可以指定輸出的字集為 Big5 就能輸出正常的編碼,例如:

&mysqldump -u $strUserName "-p$strUserPassword" --default-character-set=big5 $strDBName > 
    "$strDBName-$Today.sql"

不過,也只有 DML (資料操作語言) 的部分才會正常 [資料部分],原本在 DDL (資料定義語言) 的部分 [結構部分] 依然會輸出 UTF-8 編碼,所以輸出後的結果依然會出問題。在此建議的作法是套用 mysqldump 的 -r 選項,直接在 mysqldump 執行時直接指定輸出檔名與路徑,例如:

&mysqldump -u $strUserName "-p$strUserPassword" -r "$strDBName-$Today.sql" $strDBName

最後給PowerShell使用者的由衷建議

當執行非 .NET 應用程式時,不要用 PowerShell 執行文字資料匯流排(pipe)功能,否則你會欲哭無淚

給微軟開發 PowerShell 團隊的建議

別再害我的資料毀損了,PowerShell 又不是什麼需要超高效能的運算能力,不就是一些腳本嘛,多花點 CPU 時間判斷我原本程式輸出的文字編碼吧,總比把客戶的資料搞亂掉的好些。(希望有人可以幫我回報)