# 分享幾個在 Windows 與 Linux 常見的編碼問題與解決方案

我每隔幾年我就會遇到一次 non-Unicode 的編碼問題，真的不常見，但這些年來也處理過無數次了，每次都被搞的很煩。最近在 Linux 環境又遇到棘手的編碼問題，檔案內容是從 `ISO-8859-1` (Latin-1) 字集的 Sybase ASE 資料庫轉出，所以編碼是 `ISO-8859-1` 字集，但內容其實是 `BIG5` 字集，而我用 Windows Terminal + WSL 2 又只支援顯示 `Unicode` 字集的文字，所以文字無法正常在螢幕上顯示或複製。幾經嘗試後，我決定把這幾年累積的心得都寫下來，以免日後又要再花時間研究一次。

### 準備一個有文字編碼問題的環境

我準備了一個以 `BIG5` 編碼的文字檔案，請先在任意 Windows 與 Linux 環境下載：

-   Linux
    
    ```bash
    curl https://blog.miniasp.com/big5-example.txt -o big5-example.txt
    ```
    
    我這邊的 Linux 版本為：
    
    ```
    $ lsb_release -a
    No LSB modules are available.
    Distributor ID: Ubuntu
    Description:    Ubuntu 20.04.2 LTS
    Release:        20.04
    Codename:       focal
    ```
    
-   Windows PowerShell
    
    ```bash
    Invoke-WebRequest -Uri https://blog.miniasp.com/big5-example.txt -OutFile big5-example.txt
    ```
    
    ```
    ❯ $PSVersionTable
    
    Name                           Value
    ----                           -----
    PSVersion                      5.1.19041.1023
    PSEdition                      Desktop
    PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
    BuildVersion                   10.0.19041.1023
    CLRVersion                     4.0.30319.42000
    WSManStackVersion              3.0
    PSRemotingProtocolVersion      2.3
    SerializationVersion           1.1.0.1
    ```
    
-   PowerShell Core
    
    ```
    ❯ $PSVersionTable
    
    Name                           Value
    ----                           -----
    PSVersion                      7.1.3
    PSEdition                      Core
    GitCommitId                    7.1.3
    OS                             Microsoft Windows 10.0.19043
    Platform                       Win32NT
    PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
    PSRemotingProtocolVersion      2.3
    SerializationVersion           1.1.0.1
    WSManStackVersion              3.0
    ```
    

### Windows 命令提示字元 (Command Prompt)

基本上，在 Windows 命令提示字元環境下，編碼問題可以很輕易的透過 `CHCP` 命令 (`C:\Windows\System32\chcp.com`) 快速切換，只要你設定的字型有支援這個字集，就可以正常顯示文字在畫面上。

我的 Windows 10 **控制台**的 **地區設定** (Region settings) 指定的 **non-Unicode** 字集為 `Chinese (Traditional, Taiwan)`，這個設定會讓你的**命令提示字元**預設 CodePage 為 `950`，也就是俗稱的 `Big5` 編碼。

在 Command Prompt 環境下，預設可以正常輸出 Big5 編碼的文字：

![預設可以正常輸出 Big5 編碼的文字](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/b55b1d25a8ab3364be51-128214295-9ba83a12-b1fc-41f9-95f3-35ae664a53ee.png)

你可以透過以下命令快速切換為使用 Unicode 作為預設編碼：

```batch
chcp 65001
```

或是透過以下命令快速切換回預設的 `CP950` (Big5) 編碼：

```batch
chcp 950
```

Windows 命令提示字元在編碼處理上一致性很高，而且切換字集很容易，不太有什麼地雷。

### Windows PowerShell 與 PowerShell Core 的預設編碼差異

基本上 PowerShell 可以區分 **5.1 以前的版本**與 **6.0 之後的版本**，兩個版本對於**預設字集**的處理方式不同，必須認真看待，否則執行同一個 PowerShell Script 並不會得到相同的結果。

-   使用 BOM (byte-order-mark) 字元的差異
    
    PowerShell Script (`.ps1`) 本身的內容，其實也影響著 PowerShell 的執行結果：
    
    | 比較項目 | Windows PowerShell 5.1 以前的版本 | PowerShell Core 6.0 之後的版本 |
    | --- | --- | --- |
    | 檔案內容使用 `ANSI` 編碼 (ASCII) | 會直接視為 ANSI 字集 | 會直接視為 UTF8 字集 |
    | 檔案內容使用 `UTF8` 編碼 (No BOM) | 會直接視為 ANSI 字集 | 會直接視為 UTF8 字集 |
    | 檔案內容使用 `UTF8` 編碼 (BOM) | 會直接視為 UTF-8 字集 | 會直接視為 UTF8 字集 |
    
    如果你要在 PowerShell Script 中使用到 **non-ascii** 字元 (例如：中文字)，請務必加上 BOM 字元，因為 Windows **有時候**會誤判檔案字集，如果誤判為 ANSI 字集的話，這些文字就會亂掉，導致 PowerShell Script 無法正確執行。相反的，在 Linux 作業系統中，如果你加入 BOM 字元的話，可能會導致一些未知的問題，所以建議不要加上 BOM 字元。
    
    > 總之，你真的想用 PowerShell Script 來撰寫跨平台的自動化命令腳本，不要寫中文在腳本中。我是完全不考慮在 Linux 使用 PowerShell 了啦！ XD
    
-   字元編碼處理的差異
    
    | 比較項目 | Windows PowerShell 5.1 以前的版本 | PowerShell Core 6.0 之後的版本 |
    | --- | --- | --- |
    | 預設輸出使用的編碼 | `Unicode UTF-16LE` | `utf8NoBOM` |
    
    > 注意：基本上 UTF-16 以上的文字編碼，都會強制要求一定要有 BOM 字元，只有 UTF-8 可以選擇要不要加上 BOM 字元。
    
-   可以指定 Encoding 的 Cmdlets
    
    以下是包含 `-Encoding` 參數的 Cmdlets 清單，可以讓你用指定的 Encoding 處理文字：
    
    -   [Microsoft.PowerShell.Management](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/?view=powershell-5.1)
        -   Add-Content
        -   Get-Content
        -   Set-Content
    -   [Microsoft.PowerShell.Utility](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/?view=powershell-5.1)
        -   Export-Clixml
        -   Export-Csv
        -   Export-PSSession
        -   Format-Hex
        -   Import-Csv
        -   Out-File
        -   Select-String
        -   Send-MailMessage
-   這個 `-Encoding` 參數支援的編碼對照
    
    | 字集  | Windows PowerShell 5.1 以前的版本 | PowerShell Core 6.0 之後的版本 |
    | --- | --- | --- |
    | Default (ANSI) | ✅ (`Default`) | ❌   |
    | OEM (作業系統預設) | ✅ (`Oem`) | ✅ (`oem`) |
    | ASCII (7-bit) 字集 | ✅ (`Ascii`) | ✅ (`ascii`) |
    | UTF7 | ✅ (`UTF7`) | ✅ (`utf7`) |
    | UTF8 (No BOM) | ❌   | ✅ (`utf8`) |
    | UTF8 (No BOM) | ❌   | ✅ (`utf8NoBOM`) |
    | UTF8 BOM | ✅ (`UTF8`) | ✅ (`utf8BOM`) |
    | UTF-16 BE BOM | ✅ (`BigEndianUnicode`) | ✅ (`bigendianunicode`) |
    | UTF-16 LE BOM | ✅ (`String`) | ❌   |
    | UTF-16 LE BOM | ✅ (`Unicode`) | ✅ (`unicode`) |
    | UTF-16 LE BOM | ❌ (官網有寫但不能用) (`Unknown`) | ❌   |
    | UTF-32 BE BOM | ❌ (官網有寫但不能用) (`BigEndianUTF32`) | ✅ (官網沒寫這項但實際上有) (`bigendianutf32`) |
    | UTF-32 LE BOM | ✅ (`UTF32`) | ✅ (`utf32`) |
    | Bytes | ❌ (官網有寫但不能用) (`Byte`) | ❌   |
    
    看到上述這麼多支援的編碼不同，而跙 Windows PowerShell 5.1 以前的版本竟然沒辦法指定 UTF8 (No BOM) 的輸出，實在母湯！👎
    
    PowerShell Core 6.0 之後的版本，可用的編碼比文件上寫的多出很多，你可以用以下命令查詢出當前系統可用的編碼清單：
    
    ```powershell
    ❯ [System.Text.Encoding]::GetEncodings()
    
    CodePage Name                    DisplayName
    -------- ----                    -----------
         932 shift_jis               shift_jis
         860 IBM860                  IBM860
         861 ibm861                  ibm861
       20880 IBM880                  IBM880
         862 DOS-862                 DOS-862
         863 IBM863                  IBM863
         936 gb2312                  gb2312
         864 IBM864                  IBM864
         865 IBM865                  IBM865
         866 cp866                   cp866
       21866 koi8-u                  koi8-u
          37 IBM037                  IBM037
         869 ibm869                  ibm869
         500 IBM500                  IBM500
       10079 x-mac-icelandic         x-mac-icelandic
        1140 IBM01140                IBM01140
        1141 IBM01141                IBM01141
        1142 IBM01142                IBM01142
       20273 IBM273                  IBM273
        1143 IBM01143                IBM01143
        1144 IBM01144                IBM01144
        1145 IBM01145                IBM01145
        1250 windows-1250            windows-1250
        1146 IBM01146                IBM01146
        1251 windows-1251            windows-1251
        1147 IBM01147                IBM01147
       10000 macintosh               macintosh
        1252 windows-1252            windows-1252
         720 DOS-720                 DOS-720
       20277 IBM277                  IBM277
        1148 IBM01148                IBM01148
       10001 x-mac-japanese          x-mac-japanese
        1253 windows-1253            windows-1253
         437 IBM437                  IBM437
       20278 IBM278                  IBM278
        1149 IBM01149                IBM01149
       10002 x-mac-chinesetrad       x-mac-chinesetrad
        1254 windows-1254            windows-1254
        1255 windows-1255            windows-1255
        1361 Johab                   Johab
        1256 windows-1256            windows-1256
       10004 x-mac-arabic            x-mac-arabic
        1257 windows-1257            windows-1257
       10005 x-mac-hebrew            x-mac-hebrew
        1258 windows-1258            windows-1258
       10006 x-mac-greek             x-mac-greek
       10007 x-mac-cyrillic          x-mac-cyrillic
       20924 IBM00924                IBM00924
       28592 iso-8859-2              iso-8859-2
       28593 iso-8859-3              iso-8859-3
       28594 iso-8859-4              iso-8859-4
       28595 iso-8859-5              iso-8859-5
       28596 iso-8859-6              iso-8859-6
         870 IBM870                  IBM870
       28597 iso-8859-7              iso-8859-7
       28598 iso-8859-8              iso-8859-8
       28599 iso-8859-9              iso-8859-9
       10081 x-mac-turkish           x-mac-turkish
       10082 x-mac-croatian          x-mac-croatian
         874 windows-874             windows-874
         875 cp875                   cp875
       20420 IBM420                  IBM420
         949 ks_c_5601-1987          ks_c_5601-1987
       20423 IBM423                  IBM423
       20424 IBM424                  IBM424
       20280 IBM280                  IBM280
        1047 IBM01047                IBM01047
       20284 IBM284                  IBM284
       20285 IBM285                  IBM285
       10010 x-mac-romanian          x-mac-romanian
       20932 EUC-JP                  EUC-JP
       10017 x-mac-ukrainian         x-mac-ukrainian
       29001 x-Europa                x-Europa
         737 ibm737                  ibm737
       20105 x-IA5                   x-IA5
         950 big5                    big5
       20936 x-cp20936               x-cp20936
       20106 x-IA5-German            x-IA5-German
       20107 x-IA5-Swedish           x-IA5-Swedish
       20108 x-IA5-Norwegian         x-IA5-Norwegian
       20866 koi8-r                  koi8-r
         775 ibm775                  ibm775
       28603 iso-8859-13             iso-8859-13
       20290 IBM290                  IBM290
       28605 iso-8859-15             iso-8859-15
       20000 x-Chinese-CNS           x-Chinese-CNS
         708 ASMO-708                ASMO-708
       20297 IBM297                  IBM297
       10021 x-mac-thai              x-mac-thai
       20001 x-cp20001               x-cp20001
       20905 IBM905                  IBM905
       20002 x-Chinese-Eten          x-Chinese-Eten
       20833 x-ebcdic-koreanextended x-ebcdic-koreanextended
       20003 x-cp20003               x-cp20003
       20004 x-cp20004               x-cp20004
       20005 x-cp20005               x-cp20005
         850 ibm850                  ibm850
       20838 IBM-Thai                IBM-Thai
         852 ibm852                  ibm852
       20871 IBM871                  IBM871
       10029 x-mac-ce                x-mac-ce
         855 IBM855                  IBM855
       21025 cp1025                  cp1025
       20949 x-cp20949               x-cp20949
         857 ibm857                  ibm857
         858 IBM00858                IBM00858
       20261 x-cp20261               x-cp20261
        1026 IBM1026                 IBM1026
       20269 x-cp20269               x-cp20269
        1200 utf-16                  Unicode
        1201 utf-16BE                Unicode (Big-Endian)
       12000 utf-32                  Unicode (UTF-32)
       12001 utf-32BE                Unicode (UTF-32 Big-Endian)
       20127 us-ascii                US-ASCII
       28591 iso-8859-1              Western European (ISO)
       65001 utf-8                   Unicode (UTF-8)
    ```
    
    不過 Windows PowerShell 5.1 以前的版本，地雷的地方還不只這些，不同的 Cmdlet 實作上對「預設編碼」的選用並不一致，並非所有預設都以 `UTF-16 LE BOM` 編碼為主。
    
-   關於 **重新導向運算子** (Redirection operators) ( `>` 、 `>>` ) 的預設編碼
    
    從 Windows PowerShell 5.1 開始，預設所有 `>` 與 `>>` 導向都會委由 [Out-File](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/out-file?view=powershell-5.1) Cmdlet 處理，這也意味著預設輸出編碼是可以調整的，這部分在本文後續會提及。但在這之前的 Windows PowerShell 版本，只能輸出 `UTF-16 LE BOM` 編碼。
    
    在 Windows PowerShell 5.1 以前的版本，使用**重新導向運算子**(Redirection operators)預設會採用 `UTF-16 LE BOM` 字集：
    
    | 字集  | Windows PowerShell 5.1 以前的版本 | PowerShell Core 6.0 之後的版本 |
    | --- | --- | --- |
    | Default (ANSI) | ✅ (`Default`) | ❌   |
    | OEM (作業系統預設) | ✅ (`Oem`) | ✅ (`oem`) |
    | ASCII (7-bit) 字集 | ✅ (`Ascii`) | ✅ (`ascii`) |
    | UTF7 | ✅ (`UTF7`) | ✅ (`utf7`) |
    | UTF8 (No BOM) | ❌   | ✅ (`utf8`) |
    | UTF8 (No BOM) | ❌   | ✅ (`utf8NoBOM`) (**預設值**) |
    | UTF8 BOM | ✅ (`UTF8`) | ✅ (`utf8BOM`) |
    | UTF-16 BE BOM | ✅ (`BigEndianUnicode`) | ✅ (`bigendianunicode`) |
    | UTF-16 LE BOM | ✅ (`String`) | ❌   |
    | UTF-16 LE BOM | ✅ (`Unicode`) (**預設值**) | ✅ (`unicode`) |
    | UTF-16 LE BOM | ❌ (`Unknown`) | ❌   |
    | UTF-32 BE BOM | ❌ (`BigEndianUTF32`) | ✅ (`bigendianutf32`) |
    | UTF-32 LE BOM | ✅ (`UTF32`) | ✅ (`utf32`) |
    | Bytes | ❌ (`Byte`) | ❌   |
    
-   關於 [Get-Content](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-content?view=powershell-7.1) (讀取內容), [Add-Content](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/add-content?view=powershell-7.1) (添增內容), [Set-Content](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/set-content?view=powershell-7.1) (取代內容) 的預設編碼
    
    | 字集  | Windows PowerShell 5.1 以前的版本 | PowerShell Core 6.0 之後的版本 |
    | --- | --- | --- |
    | Default (ANSI) | ✅ (`Default`) (**預設值**) | ❌   |
    | OEM (作業系統預設) | ✅ (`Oem`) | ✅ (`oem`) |
    | ASCII (7-bit) 字集 | ✅ (`Ascii`) | ✅ (`ascii`) |
    | UTF7 | ✅ (`UTF7`) | ✅ (`utf7`) |
    | UTF8 (No BOM) | ❌   | ✅ (`utf8`) |
    | UTF8 (No BOM) | ❌   | ✅ (`utf8NoBOM`) (**預設值**) |
    | UTF8 BOM | ✅ (`UTF8`) | ✅ (`utf8BOM`) |
    | UTF-16 BE BOM | ✅ (`BigEndianUnicode`) | ✅ (`bigendianunicode`) |
    | UTF-16 LE BOM | ✅ (`String`) | ❌   |
    | UTF-16 LE BOM | ✅ (`Unicode`) | ✅ (`unicode`) |
    | UTF-16 LE BOM | ❌ (`Unknown`) | ❌   |
    | UTF-32 BE BOM | ❌ (`BigEndianUTF32`) | ✅ (`bigendianutf32`) |
    | UTF-32 LE BOM | ✅ (`UTF32`) | ✅ (`utf32`) |
    | Bytes | ❌ (`Byte`) | ❌   |
    
    不同 Cmdlets 之間，預設編碼不一樣，真的蠻讓人崩潰的。
    
    但事實上，[官網文件](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_character_encoding?view=powershell-7.1#character-encoding-in-windows-powershell)在針對 [Add-Content](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/add-content?view=powershell-7.1) 與 [Set-Content](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/set-content?view=powershell-7.1) (取代內容) 的說明也是有錯誤的。
    
    當你在使用 [Add-Content](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/add-content?view=powershell-7.1) Cmdlet 的時候，他會判斷目標檔案的編碼，添加檔案時會以目標檔案的編碼為主，並不會使用 `Default` (ANSI) 編碼。這裡所指的 `ANSI` 編碼，其實就是你控制台中地區設定的 non-Unicode 字集設定。
    
    當你在使用 [Set-Content](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/set-content?view=powershell-7.1) Cmdlet 的時候，他並不會判斷目標檔案的編碼（但文件說會），所以這也是非常的雷！
    
    當你在使用 [Get-Content](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-content?view=powershell-7.1) Cmdlet 的時候，預設使用 `Default` 編碼讀入。事實上，PowerShell Engine 在讀入 PowerShell Script 的時候，也是使用一樣的預設值。
    
-   關於 [Export-Csv](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/export-csv?view=powershell-7.1) 的預設編碼
    
    | 字集  | Windows PowerShell 5.1 以前的版本 | PowerShell Core 6.0 之後的版本 |
    | --- | --- | --- |
    | Default (ANSI) | ✅ (`Default`) | ❌   |
    | OEM (作業系統預設) | ✅ (`Oem`) | ✅ (`oem`) |
    | ASCII (7-bit) 字集 | ✅ (`Ascii`) (**預設值**) | ✅ (`ascii`) |
    | UTF7 | ✅ (`UTF7`) | ✅ (`utf7`) |
    | UTF8 (No BOM) | ❌   | ✅ (`utf8`) |
    | UTF8 (No BOM) | ❌   | ✅ (`utf8NoBOM`) (**預設值**) |
    | UTF8 BOM | ✅ (`UTF8`) | ✅ (`utf8BOM`) |
    | UTF-16 BE BOM | ✅ (`BigEndianUnicode`) | ✅ (`bigendianunicode`) |
    | UTF-16 LE BOM | ✅ (`String`) | ❌   |
    | UTF-16 LE BOM | ✅ (`Unicode`) | ✅ (`unicode`) |
    | UTF-16 LE BOM | ❌ (`Unknown`) | ❌   |
    | UTF-32 BE BOM | ❌ (`BigEndianUTF32`) | ✅ (`bigendianutf32`) |
    | UTF-32 LE BOM | ✅ (`UTF32`) | ✅ (`utf32`) |
    | Bytes | ❌ (`Byte`) | ❌   |
    
    上表 Windows PowerShell 5.1 以前的版本預設值，當你在設定 `-Append` (添加) 參數的時候，他就會改先判斷**目標檔案**的字集，以**目標檔案**的字集為主。
    
    > 不過 `Out-File` 也有個 `-Append` 參數，這個參數不會判斷**目標檔案**的字集。（為什麼設計的這麼不一致啊？）
    
-   關於 [Export-PSSession](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/export-pssession?view=powershell-7.1) 的預設編碼
    
    | 字集  | Windows PowerShell 5.1 以前的版本 | PowerShell Core 6.0 之後的版本 |
    | --- | --- | --- |
    | Default (ANSI) | ✅ (`Default`) | ❌   |
    | OEM (作業系統預設) | ✅ (`Oem`) | ✅ (`oem`) |
    | ASCII (7-bit) 字集 | ✅ (`Ascii`) | ✅ (`ascii`) |
    | UTF7 | ✅ (`UTF7`) | ✅ (`utf7`) |
    | UTF8 (No BOM) | ❌   | ✅ (`utf8`) |
    | UTF8 (No BOM) | ❌   | ✅ (`utf8NoBOM`) (**預設值**) |
    | UTF8 BOM | ✅ (`UTF8`) (**預設值**) | ✅ (`utf8BOM`) |
    | UTF-16 BE BOM | ✅ (`BigEndianUnicode`) | ✅ (`bigendianunicode`) |
    | UTF-16 LE BOM | ✅ (`String`) | ❌   |
    | UTF-16 LE BOM | ✅ (`Unicode`) | ✅ (`unicode`) |
    | UTF-16 LE BOM | ❌ (`Unknown`) | ❌   |
    | UTF-32 BE BOM | ❌ (`BigEndianUTF32`) | ✅ (`bigendianutf32`) |
    | UTF-32 LE BOM | ✅ (`UTF32`) | ✅ (`utf32`) |
    | Bytes | ❌ (`Byte`) | ❌   |
    
-   關於 [New-Item -Type File -Value](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/new-item?view=powershell-7.1) 的預設編碼
    
    事實上 [New-Item](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/new-item?view=powershell-7.1) Cmdlet 並沒有 `-Encoding` 參數可用，但因為當你使用 `-Type File` 建立新檔案時，必須選用一個文字編碼，但他又不能指定 `-Encoding` 參數，預設值被設定成 `UTF8 (No BOM)` 編碼。真的是非常的匪夷所思！
    
    | 字集  | Windows PowerShell 5.1 以前的版本 | PowerShell Core 6.0 之後的版本 |
    | --- | --- | --- |
    | Default (ANSI) | ✅ (`Default`) | ❌   |
    | OEM (作業系統預設) | ✅ (`Oem`) | ✅ (`oem`) |
    | ASCII (7-bit) 字集 | ✅ (`Ascii`) | ✅ (`ascii`) |
    | UTF7 | ✅ (`UTF7`) | ✅ (`utf7`) |
    | UTF8 (No BOM) | ❌   | ✅ (`utf8`) |
    | UTF8 (No BOM) | ❌ (**預設值**) | ✅ (`utf8NoBOM`) (**預設值**) |
    | UTF8 BOM | ✅ (`UTF8`) | ✅ (`utf8BOM`) |
    | UTF-16 BE BOM | ✅ (`BigEndianUnicode`) | ✅ (`bigendianunicode`) |
    | UTF-16 LE BOM | ✅ (`String`) | ❌   |
    | UTF-16 LE BOM | ✅ (`Unicode`) | ✅ (`unicode`) |
    | UTF-16 LE BOM | ❌ (`Unknown`) | ❌   |
    | UTF-32 BE BOM | ❌ (`BigEndianUTF32`) | ✅ (`bigendianutf32`) |
    | UTF-32 LE BOM | ✅ (`UTF32`) | ✅ (`utf32`) |
    | Bytes | ❌ (`Byte`) | ❌   |
    
-   關於 [Send-MailMessage](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/send-mailmessage?view=powershell-7.1) 的預設編碼
    
    | 字集  | Windows PowerShell 5.1 以前的版本 | PowerShell Core 6.0 之後的版本 |
    | --- | --- | --- |
    | Default (ANSI) | ✅ (`Default`) (**預設值**) | ❌   |
    | OEM (作業系統預設) | ✅ (`Oem`) | ✅ (`oem`) |
    | ASCII (7-bit) 字集 | ✅ (`Ascii`) | ✅ (`ascii`) |
    | UTF7 | ✅ (`UTF7`) | ✅ (`utf7`) |
    | UTF8 (No BOM) | ❌   | ✅ (`utf8`) |
    | UTF8 (No BOM) | ❌   | ✅ (`utf8NoBOM`) (**預設值**) |
    | UTF8 BOM | ✅ (`UTF8`) | ✅ (`utf8BOM`) |
    | UTF-16 BE BOM | ✅ (`BigEndianUnicode`) | ✅ (`bigendianunicode`) |
    | UTF-16 LE BOM | ✅ (`String`) | ❌   |
    | UTF-16 LE BOM | ✅ (`Unicode`) | ✅ (`unicode`) |
    | UTF-16 LE BOM | ❌ (`Unknown`) | ❌   |
    | UTF-32 BE BOM | ❌ (`BigEndianUTF32`) | ✅ (`bigendianutf32`) |
    | UTF-32 LE BOM | ✅ (`UTF32`) | ✅ (`utf32`) |
    | Bytes | ❌ (`Byte`) | ❌   |
    
-   關於 [Start-Transcript](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.host/start-transcript?view=powershell-5.1) 的預設編碼
    
    | 字集  | Windows PowerShell 5.1 以前的版本 | PowerShell Core 6.0 之後的版本 |
    | --- | --- | --- |
    | Default (ANSI) | ✅ (`Default`) | ❌   |
    | OEM (作業系統預設) | ✅ (`Oem`) | ✅ (`oem`) |
    | ASCII (7-bit) 字集 | ✅ (`Ascii`) | ✅ (`ascii`) |
    | UTF7 | ✅ (`UTF7`) | ✅ (`utf7`) |
    | UTF8 (No BOM) | ❌   | ✅ (`utf8`) |
    | UTF8 (No BOM) | ❌   | ✅ (`utf8NoBOM`) (**預設值**) |
    | UTF8 BOM | ✅ (`UTF8`) (**預設值**) | ✅ (`utf8BOM`) |
    | UTF-16 BE BOM | ✅ (`BigEndianUnicode`) | ✅ (`bigendianunicode`) |
    | UTF-16 LE BOM | ✅ (`String`) | ❌   |
    | UTF-16 LE BOM | ✅ (`Unicode`) | ✅ (`unicode`) |
    | UTF-16 LE BOM | ❌ (`Unknown`) | ❌   |
    | UTF-32 BE BOM | ❌ (`BigEndianUTF32`) | ✅ (`bigendianutf32`) |
    | UTF-32 LE BOM | ✅ (`UTF32`) | ✅ (`utf32`) |
    | Bytes | ❌ (`Byte`) | ❌   |
    
    上表 Windows PowerShell 5.1 以前的版本預設值，當你在設定 `-Append` (添加) 參數的時候，他就會改先判斷**目標檔案**是否有 `BOM` 字元，如果有，就會以**目標檔案**的字集為主。如果沒有，就會選用 `Ascii` 編碼。
    

### 變更 PowerShell 的預設編碼

你可以從上述的整理發現 PowerShell Core 6.0 之後的版本，一律使用 `utf8NoBOM` 為預設編碼，一致性相當高！

但是 Windows PowerShell 5.1 以前的版本，預設編碼就非常混亂，很容易踩雷。

PowerShell 有兩個[預先定義好的變數](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables?view=powershell-7.1)，用來變更預設的編碼設定：

-   `$PSDefaultParameterValues`
    
    如果你想改變 `>` 或 `>>` 重新導向的預設編碼，或是使用 [Out-File](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/out-file?view=powershell-5.1) Cmdlet 時想要改變預設編碼為 `UTF8 BOM`，可以這樣設定：
    
    ```powershell
    $PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'
    ```
    
    如果想要改變所有 Cmdlets 的預設編碼，可以這樣寫：
    
    ```powershell
    $PSDefaultParameterValues['*:Encoding'] = 'utf8'
    ```
    
    你可以將設定加入到 `$PROFILE` 檔案中，這樣就可以統一所有 Cmdlet 的預設編碼。
    
-   `$OutputEncoding`
    
    這個 `$OutputEncoding` 主要用來讓 PowerShell Cmdlet 與 **外部程式** (external programs) 通訊的時候，指定的文字編碼，其預設值定義如下：
    
    | 字集  | Windows PowerShell 5.1 以前的版本 | PowerShell Core 6.0 之後的版本 |
    | --- | --- | --- |
    | Default (ANSI) | ✅ (`Default`) | ❌   |
    | OEM (作業系統預設) | ✅ (`Oem`) | ✅ (`oem`) |
    | ASCII (7-bit) 字集 | ✅ (`Ascii`) (**預設值**) | ✅ (`ascii`) |
    | UTF7 | ✅ (`UTF7`) | ✅ (`utf7`) |
    | UTF8 (No BOM) | ❌   | ✅ (`utf8`) |
    | UTF8 (No BOM) | ❌   | ✅ (`utf8NoBOM`) (**預設值**) |
    | UTF8 BOM | ✅ (`UTF8`) | ✅ (`utf8BOM`) |
    | UTF-16 BE BOM | ✅ (`BigEndianUnicode`) | ✅ (`bigendianunicode`) |
    | UTF-16 LE BOM | ✅ (`String`) | ❌   |
    | UTF-16 LE BOM | ✅ (`Unicode`) | ✅ (`unicode`) |
    | UTF-16 LE BOM | ❌ (`Unknown`) | ❌   |
    | UTF-32 BE BOM | ❌ (`BigEndianUTF32`) | ✅ (`bigendianutf32`) |
    | UTF-32 LE BOM | ✅ (`UTF32`) | ✅ (`utf32`) |
    | Bytes | ❌ (`Byte`) | ❌   |
    
    Windows PowerShell 5.1 以前的版本，這個 `$OutputEncoding` 預設變數定義的是 `Ascii` 編碼，這意味著所有從 PowerShell 導向到外面的資料，預設都會轉換成這個編碼：
    
    ```powershell
    ❯ $OutputEncoding
    
    IsSingleByte      : True
    BodyName          : us-ascii
    EncodingName      : US-ASCII
    HeaderName        : us-ascii
    WebName           : us-ascii
    WindowsCodePage   : 1252
    IsBrowserDisplay  : False
    IsBrowserSave     : False
    IsMailNewsDisplay : True
    IsMailNewsSave    : True
    EncoderFallback   : System.Text.EncoderReplacementFallback
    DecoderFallback   : System.Text.DecoderReplacementFallback
    IsReadOnly        : True
    CodePage          : 20127
    ```
    
    你可以透過 `[Console]::OutputEncoding` 取得目前 Console 可以使用的編碼，就會發現通常都與 `$OutputEncoding` 不一樣，這也意味著你從 PowerShell Cmdlet 傳資料給傳統 Windows 外部程式的話，文字編碼通常都會出問題：
    
    ```powershell
    ❯ [Console]::OutputEncoding
    
    EncodingName      : Chinese Traditional (Big5)
    WebName           : big5
    HeaderName        : big5
    BodyName          : big5
    Preamble          :
    WindowsCodePage   :
    IsBrowserDisplay  :
    IsBrowserSave     :
    IsMailNewsDisplay :
    IsMailNewsSave    :
    IsSingleByte      : False
    EncoderFallback   : System.Text.InternalEncoderBestFitFallback
    DecoderFallback   : System.Text.InternalDecoderBestFitFallback
    IsReadOnly        : False
    CodePage          : 950
    ```
    
    我舉的簡單的例子，在 Windows 有個 `findstr.exe` 工具，類似 Linux 下的 `grep` 工具，但是陽春很多。
    
    假設我們在 Windows PowerShell 這樣用，你會發現 `finstr` 什麼都搜尋不到，那是因為編碼不一致造成的：
    
    ```powershell
    Get-Content .\big5-example.txt | findstr 測
    ```
    
    但是如果我們把 `$OutputEncoding` 重新宣告成 `[Console]::OutputEncoding`，PowerShell Cmdlet 所有輸出就會與 Console 的預設輸出編碼一致：
    
    ```powershell
    $OutputEncoding = [Console]::OutputEncoding
    Get-Content .\big5-example.txt | findstr 測
    ```
    
    ![PowerShell](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/3c5b6bca67b64b8b6a22-128304467-b17526b9-c45f-4714-9475-d479d0c38dc6.png)
    
    > 很可惜，PowerShell 並沒有 `$InputEncoding` 可用，這意味著一個重大的資訊：**你不能從其他應用程式傳入 non-Unicode 的字元給 PowerShell 使用！**
    

### WSL 2

基本上 Windows 10 內建所有的 Terminal 都是共用同一個超過 30 年沒有改進的核心，但是麻煩的地方在於 WSL 2 開啟的時候，沒有 `chcp` 命令可用，而且預設 Terminal 採用 UTF-8 編碼，任何 non-Unicode 的編碼，通通無法正常顯示！

如下圖示，明明有 4 個 bytes (兩個中文字)，但卻一個字也吐不出來：

![在 WSL 使用 cat 輸出 big5-example.txt 檔案內容](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/99ec9ff5495d901698b2-128213706-00265f84-c960-48a5-a774-26ae723aaa06.png)

基本上，**無解**！你只能在 WSL 2 處理 Unicode 編碼的文字！

### Windows Terminal + WSL 2

使用 Microsoft 最新的 Windows Terminal 來執行的話，有比較好一點，可以出現四個 `�` 字元，而且你依然無法調整預設字集！ 😅

![在 WSL + Windows Terminal 使用 cat 輸出 big5-example.txt 檔案內容](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/aad57aa466015e898200-128215391-2a74cf44-4300-46d9-bf5d-b0276d6ae7a9.png)

基本上，一樣**無解**！你只能在 WSL 2 處理 Unicode 編碼的文字！

### PuTTY

使用 [PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/) 進行遠端連接，可以有效的解決各種編碼問題。這是因為 PuTTY 內建一個叫做 **Character set translation** 的功能，他可以讓你在建立連線之前，就先設定好**遠端虛擬終端機**想要使用**預設字集**，搭配著可以顯示該字集的**字型**，就可以正確顯示。

PuTTY 預設採用 `UTF-8` 編碼，如果你的遠端 Linux 想以 `BIG5` 為預設字集，那麼你必須調整設定如下：

![PuTTY](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/c1b4fe4c68927cd0ce25-128218410-b1badf35-7458-4899-9bda-983d0baaf240.png)

並選擇一個支援 `BIG5` 字集的字型：

![PuTTY](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/8223c6afa44dd7ff2bb2-128218864-b1dc17a8-a899-4445-888a-90c2ff4dd77b.png)

連入之後，就可以正常顯示 `BIG5` 編碼的文字了

![PuTTY](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/467edef22a658dc6eeaf-128219068-9031e918-f4fd-475e-a8ce-f86ed16f9105.png)

不過，當你想用 WSL 的時候，怎麼可能還特別安裝個 OpenSSH Server 然後再用 PuTTY 連進去，這雖然是在 Linux 下看到 `BIG5` 字集文字的「唯一」解決方案，但就是沒那麼漂亮！

> 我覺得要完美解決這個問題，大概只能依賴 Windows Terminal 實現 **Character set translation** 功能了，我特別到 Windows Terminal 的 [GitHub Repo](https://github.com/microsoft/terminal) 提出一個功能建議 [Feature Request: Character Set translation #10870](https://github.com/microsoft/terminal/issues/10870)，希望日後有機會可以實現！

### 如何不透過 PuTTY 連線到 Linux 又能正確看到 BIG5 編碼的文字

我後來有研究出兩個方法，可以讓我在 WSL 下也能正確顯示 BIG5 的檔案內容：

1.  使用 `cat` 搭配 `iconv` 轉換字集
    
    反正文字只要能轉成 UTF8 就能正常顯示，所以這招可行！
    
    ```bash
    cat big5-example.txt | iconv -f big5 -t utf8
    ```
    
    不過，對於含有互動的命令介面下，這招就沒效了！ Orz
    
2.  使用 `pwsh` 搭配 `$PSDefaultParameterValues['*:Encoding'] = 'big5'` 設定
    
    這個方式則是利用 PowerShell Core (`pwsh`) 的特性，將 `Get-Content` 指定編碼為 `big5` (CodePage `950`)，然後利用 PowerShell Core 預設 `$OutputEncoding` 為 UTF8 的特性，將文字輸出成 UTF8 編碼，如此以來就可以順利顯示在畫面上了！😃
    
    ```powershell
    pwsh -Command 'Get-Content -Encoding big5 ./big5-example.txt'
    pwsh -Command 'Get-Content -Encoding 950 ./big5-example.txt'
    ```
    
    我可以寫成一個 Alias 專門來做這件事：
    
    ```bash
    alias catb5='pwsh -Command '"'"'Get-Content -Encoding big5'"'"''
    
    catb5 ./big5-example.txt
    ```
    
    > 請注意：不要在 PowerShell Core on Linux 使用 `cat` 命令，因為他會執行 `/usr/bin/cat` 命令，而非 `Get-Content` Cmdlet 喔！
    

### 總結

反正遇到編碼問題就是有著數不盡的地雷要踩，還好這幾年累積了無數踩雷經驗，今天終於將我所知道的編碼問題全部整理起來，心中頓時覺得踏實許多！ 😄

本文有點長，我特別節錄幾個重點知識：

-   Windows 命令提示字元 (Command Prompt) 可以用 `chcp` 切換預設編碼
    
    ```batch
    chcp 65001
    ```
    
-   PowerShell 5.1+ 可以透過修改 `$PSDefaultParameterValues['*:Encoding']` 改變重新導向運算子的預設編碼
    
    ```powershell
    $PSDefaultParameterValues['*:Encoding'] = 'utf8'
    
    Get-Content ./big5-example.txt
    ```
    
-   PowerShell 5.1+ 預設所有 `>` 與 `>>` 導向都會委由 [Out-File](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/out-file?view=powershell-5.1) Cmdlet 處理
    
    ```powershell
    $PSDefaultParameterValues['Out-File:Encoding'] = 'utf8bom'
    
    Get-Content ./big5-example.txt > abc.txt
    ```
    
-   PowerShell 5.1+ 可以透過修改 `$OutputEncoding` 改變 Pipe 給外部程式的預設編碼
    
    ```powershell
    $OutputEncoding = [Console]::OutputEncoding
    
    Get-Content ./big5-example.txt | findstr /i 測
    ```
    
-   PowerShell Core 的編碼可以透過 `[System.Text.Encoding]::GetEncodings()` 取得完整清單，設定 `-Encoding` 的時候，可以指定 **CodePage 的數字**，也可以指定**編碼名稱**
    
    ```powershell
    [System.Text.Encoding]::GetEncodings()
    
    Get-Content -Encoding 950 ./big5-example.txt
    Get-Content -Encoding 'big5' ./big5-example.txt
    ```
    
-   [PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/) 內建一個叫做 **Character set translation** 的功能，他可以讓你在建立連線之前，就先設定好**遠端虛擬終端機**想要使用**預設字集**，搭配著可以顯示該字集的**字型**，就可以正確顯示。
    

### 相關連結

-   [\[Console\]::OutputEncoding unable switch to cp950 encoding in Linux #15865](https://github.com/PowerShell/PowerShell/issues/15865)
-   [$OutputEncoding to the rescue | PowerShell Team](https://devblogs.microsoft.com/powershell/outputencoding-to-the-rescue/?WT.mc_id=DT-MVP-4015686)
-   [Understanding default encoding and Change the same in PowerShell](https://mohitgoyal.co/2017/03/03/understanding-default-encoding-and-change-the-same-in-powershell/)
-   [PowerShell 輸出結果編碼問題](https://blog.darkthread.net/blog/ps-encoding/)
-   [How to read pipeline with iso-8859-1 encoding characters? #15871](https://github.com/PowerShell/PowerShell/issues/15871)
-   [How to change default codepage in Windows Terminal using WSL? - Super User](https://superuser.com/questions/1667486/how-to-change-default-codepage-in-windows-terminal-using-wsl)
-   [Putty 中文顯示 @ 廖董不懂的部落格 :: 痞客邦 ::](https://liaobart.pixnet.net/blog/post/97057242)
