﻿# 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 編碼](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/93422241dd072f01cb54-image_8.png "在 DOS 模式下執行 mysqldump 指令，再匯出資料的時候中文是正常的，而且是 UTF-8 編碼")

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

![在 PowerShell 模式下執行 mysqldump 指令匯出資料的時候中文卻是亂碼，但文件還是 UTF-8 編碼!](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/dc7d90ce561e606c9b13-image_3.png "在 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 版 分析被弄成亂碼的文字](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/652b33a647c4a6c43a87-image_11.png "利用 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 等十六進制位元](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/e423f3d0477a9db59d7f-image_14.png "我試著用內碼輸入法依序輸入 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 時間判斷我原本程式輸出的文字編碼吧，總比把客戶的資料搞亂掉的好些。(希望有人可以幫我回報)
