我每隔幾年我就會遇到一次 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 編碼的文字:
你可以透過以下命令快速切換為使用 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-Content 與 Set-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 並沒有 $InputEncoding
可用,這意味著一個重大的資訊:你不能從其他應用程式傳入 non-Unicode 的字元給 PowerShell 使用!
WSL 2
基本上 Windows 10 內建所有的 Terminal 都是共用同一個超過 30 年沒有改進的核心,但是麻煩的地方在於 WSL 2 開啟的時候,沒有 chcp
命令可用,而且預設 Terminal 採用 UTF-8 編碼,任何 non-Unicode 的編碼,通通無法正常顯示!
如下圖示,明明有 4 個 bytes (兩個中文字),但卻一個字也吐不出來:
基本上,無解!你只能在 WSL 2 處理 Unicode 編碼的文字!
Windows Terminal + WSL 2
使用 Microsoft 最新的 Windows Terminal 來執行的話,有比較好一點,可以出現四個 �
字元,而且你依然無法調整預設字集! 😅
基本上,一樣無解!你只能在 WSL 2 處理 Unicode 編碼的文字!
PuTTY
使用 PuTTY 進行遠端連接,可以有效的解決各種編碼問題。這是因為 PuTTY 內建一個叫做 Character set translation 的功能,他可以讓你在建立連線之前,就先設定好遠端虛擬終端機想要使用預設字集,搭配著可以顯示該字集的字型,就可以正確顯示。
PuTTY 預設採用 UTF-8
編碼,如果你的遠端 Linux 想以 BIG5
為預設字集,那麼你必須調整設定如下:
並選擇一個支援 BIG5
字集的字型:
連入之後,就可以正常顯示 BIG5
編碼的文字了
不過,當你想用 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 的檔案內容:
-
使用 cat
搭配 iconv
轉換字集
反正文字只要能轉成 UTF8 就能正常顯示,所以這招可行!
cat big5-example.txt | iconv -f big5 -t utf8
不過,對於含有互動的命令介面下,這招就沒效了! Orz
-
使用 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 的功能,他可以讓你在建立連線之前,就先設定好遠端虛擬終端機想要使用預設字集,搭配著可以顯示該字集的字型,就可以正確顯示。
相關連結