The Will Will Web

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

分享幾個在 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

    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

    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 編碼的文字

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

chcp 65001

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

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 處理文字:

  • 這個 -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 之後的版本,可用的編碼比文件上寫的多出很多,你可以用以下命令查詢出當前系統可用的編碼清單:

    ❯ [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 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 (讀取內容), Add-Content (添增內容), Set-Content (取代內容) 的預設編碼

    字集 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 之間,預設編碼不一樣,真的蠻讓人崩潰的。

    但事實上,官網文件在針對 Add-ContentSet-Content (取代內容) 的說明也是有錯誤的。

    當你在使用 Add-Content Cmdlet 的時候,他會判斷目標檔案的編碼,添加檔案時會以目標檔案的編碼為主,並不會使用 Default (ANSI) 編碼。這裡所指的 ANSI 編碼,其實就是你控制台中地區設定的 non-Unicode 字集設定。

    當你在使用 Set-Content Cmdlet 的時候,他並不會判斷目標檔案的編碼(但文件說會),所以這也是非常的雷!

    當你在使用 Get-Content Cmdlet 的時候,預設使用 Default 編碼讀入。事實上,PowerShell Engine 在讀入 PowerShell Script 的時候,也是使用一樣的預設值。

  • 關於 Export-Csv 的預設編碼

    字集 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 的預設編碼

    字集 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 的預設編碼

    事實上 New-Item 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 的預設編碼

    字集 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 的預設編碼

    字集 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 有兩個預先定義好的變數,用來變更預設的編碼設定:

  • $PSDefaultParameterValues

    如果你想改變 >>> 重新導向的預設編碼,或是使用 Out-File Cmdlet 時想要改變預設編碼為 UTF8 BOM,可以這樣設定:

    $PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'
    

    如果想要改變所有 Cmdlets 的預設編碼,可以這樣寫:

    $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 導向到外面的資料,預設都會轉換成這個編碼:

    ❯ $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 外部程式的話,文字編碼通常都會出問題:

    ❯ [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 什麼都搜尋不到,那是因為編碼不一致造成的:

    Get-Content .\big5-example.txt | findstr 測
    

    但是如果我們把 $OutputEncoding 重新宣告成 [Console]::OutputEncoding,PowerShell Cmdlet 所有輸出就會與 Console 的預設輸出編碼一致:

    $OutputEncoding = [Console]::OutputEncoding
    Get-Content .\big5-example.txt | findstr 測
    

    PowerShell

    很可惜,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 檔案內容

基本上,無解!你只能在 WSL 2 處理 Unicode 編碼的文字!

Windows Terminal + WSL 2

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

在 WSL + Windows Terminal 使用 cat 輸出 big5-example.txt 檔案內容

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

PuTTY

使用 PuTTY 進行遠端連接,可以有效的解決各種編碼問題。這是因為 PuTTY 內建一個叫做 Character set translation 的功能,他可以讓你在建立連線之前,就先設定好遠端虛擬終端機想要使用預設字集,搭配著可以顯示該字集的字型,就可以正確顯示。

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

PuTTY

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

PuTTY

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

PuTTY

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

我覺得要完美解決這個問題,大概只能依賴 Windows Terminal 實現 Character set translation 功能了,我特別到 Windows Terminal 的 GitHub Repo 提出一個功能建議 Feature Request: Character Set translation #10870,希望日後有機會可以實現!

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

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

  1. 使用 cat 搭配 iconv 轉換字集

    反正文字只要能轉成 UTF8 就能正常顯示,所以這招可行!

    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 編碼,如此以來就可以順利顯示在畫面上了!😃

    pwsh -Command 'Get-Content -Encoding big5 ./big5-example.txt'
    pwsh -Command 'Get-Content -Encoding 950 ./big5-example.txt'
    

    我可以寫成一個 Alias 專門來做這件事:

    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 切換預設編碼

    chcp 65001
    
  • PowerShell 5.1+ 可以透過修改 $PSDefaultParameterValues['*:Encoding'] 改變重新導向運算子的預設編碼

    $PSDefaultParameterValues['*:Encoding'] = 'utf8'
    
    Get-Content ./big5-example.txt
    
  • PowerShell 5.1+ 預設所有 >>> 導向都會委由 Out-File Cmdlet 處理

    $PSDefaultParameterValues['Out-File:Encoding'] = 'utf8bom'
    
    Get-Content ./big5-example.txt > abc.txt
    
  • PowerShell 5.1+ 可以透過修改 $OutputEncoding 改變 Pipe 給外部程式的預設編碼

    $OutputEncoding = [Console]::OutputEncoding
    
    Get-Content ./big5-example.txt | findstr /i 測
    
  • PowerShell Core 的編碼可以透過 [System.Text.Encoding]::GetEncodings() 取得完整清單,設定 -Encoding 的時候,可以指定 CodePage 的數字,也可以指定編碼名稱

    [System.Text.Encoding]::GetEncodings()
    
    Get-Content -Encoding 950 ./big5-example.txt
    Get-Content -Encoding 'big5' ./big5-example.txt
    
  • PuTTY 內建一個叫做 Character set translation 的功能,他可以讓你在建立連線之前,就先設定好遠端虛擬終端機想要使用預設字集,搭配著可以顯示該字集的字型,就可以正確顯示。

相關連結

留言評論