輸出到檔案的編碼
PowerShell 的 >
與 >>
其實就是 Out-File
這個 cmdlet, 它在輸出文字到檔案時可以使用 -encoding
選項指定檔案的文字編碼, 你也可以透過 $PSDefaultParameterValues
[Out-File:Encoding
] 設定預設採用的 -encoding
選項值。
若沒有設定, PowerShell 7 預設是 utf-8、PowerShell 5 預設是具有 BOM(Byte order mark) 的 UTF16LE。例如:
-
在 PowerShell 5 中:
> echo 測試 > out_ps5.txt
我們可以使用隨 vim 安裝的跨平台 xxd 工具以 16 進位數值來觀察檔案內容:
> xxd -u -g1 .\out_ps5.txt 00000000: FF FE 2C 6E 66 8A 0D 00 0A 00 ..,nf.....
檔案內容就是 2 個位元組的 BOM、各 2 個位元組的 2 個中文字、以及各 2 個位元組的歸位(CR)與換行(LF)字元,其中開頭的 FF FE 表示這是 little endian 位元組順序的 UTF16 檔案, 因為是 little endian 位元組順序, 所以 2C 6E 就是將低位元組排到前面後的 Unicode 6E 2C, 也就是『測』、66 8A 是 Unicode 編碼 8A 66 的『試』。由於是 UTF16, 所以歸位(0D 00)與換行(0A 00)字元也都各是 2 個位元組。
如果在 PowerShell 5 中設定為使用 utf-8 , 輸出檔案就會採用具有 BOM 的 utf-8 編碼:
> $PSDefaultParameterValues['Out-File:Encoding']='utf8' > echo 測試 > out_ps5.txt > xxd -u -g1 .\out_ps5.txt 00000000: EF BB BF E6 B8 AC E8 A9 A6 0D 0A ...........
開頭的 EF BB BF 就是 BOM, 表示此檔案是以 UTF-8 編碼。接著的內容是各 3 個位元組的 2 個中文字, 加上換行的 0D 0A 共 8 個位元組, 其中 E6 B8 AC 是『測』、E8 A9 A6 是『試』。
PowerShell 5 中
-encoding
允許的編碼有以下這幾種, 預設是 Unicode:參數值 編碼 ASCII 7 位元的 ASCII 編碼 BigEndianUnicode big endian 位元組順序的 UTF-16 Default 系統設定的編碼頁 Unicode little endian 位元組順序的 UTF-16 UTF7 UTF-7 UTF8 UTF-8 UTF32 little endian 位元組順序的 UTF-32 -
在 PowerShell 7 中:
❯ echo 測試 > out_ps7.txt ❯ xxd -u -g1 .\out_ps7.txt 00000000: E6 B8 AC E8 A9 A6 0D 0A ........
檔案內容沒有 BOM, 並且以 UTF-8 編碼, 為各 3 個位元組的 2 個中文字, 加上歸位與換行的 0D 0A 共 8 個位元組。其中 E6 B8 AC 是『測』、E8 A9 A6 是『試』。
在 PowerShell 7 中
-encoding
可接受的參數值如下, 預設為 utf8NoBOM:參數值 編碼 ASCII 7 位元的 ASCII 編碼 BigEndianUnicode big endian 位元組順序的 UTF-16 BigEndianUTF32 big endian 位元組順序的 UTF-32 OEM MS-DOS 與終端機的字元編碼 Unicode little endian 位元組順序的 UTF-16 UTF7 UTF-7(已不建議使用) UTF8 UTF-8(同 UTF8NoBOM) UTF8BOM 具有 BOM 的 UTF-8 UTF8NoBOM 沒有 BOM 的 UTF-8 UTF32 little endian 位元組順序的 UTF-32 編碼頁或其名稱 例如 950 是 big5 default 回復預設值,同 UTF8NoBOM 以下把預設的輸出編碼改為 big5:
❯ $PSDefaultParameterValues['Out-File:Encoding']='big5' ❯ echo 測試 > out_ps7.txt xxd -u -g1 .\out_ps7.txt 00000000: B4 FA B8 D5 0D 0A ......
檔案長度就變成 2 個各佔 2 個位元組的中文字以及歸位與換行 2 個位元組, 共 6 個位元組了。
測試完後請記得把剛剛更改的設定回復成預設值,以免影像之後的測試結果:
❯ $PSDefaultParameterValues['Out-File:Encoding']='default'
請特別留意:PowerShell 在 7.4 之前,外部程式在轉向資料到檔案時也是透過 Out-File
,都會受到 $PSDefaultParameterValues['Out-File:Encoding']
的影響。不過從 PowerShell 7.4 開始,外部程式輸出轉向到檔案已經不再透過 Out-File, 會保留原始輸出內容不動直接存檔, 所以如果外部程式輸出內容為 big5 編碼的文字, 轉向存檔內容就一樣是 big5 編碼。
從檔案讀取文字時的編碼
從檔案讀取文字的 Get-Content
和輸出文字到檔案的 Out-File
都有 -encoding
選項可以設定文字編碼。例如以下將『測試』兩字分別以不同編碼方式存檔:
❯ echo 測試 | Out-File out_utf8.txt
❯ echo 測試 | Out-File -Encoding big5 out_big5.txt
若不指定 -encoding
選項, 預設就是 UTF-8 編碼, 因此以下兩次使用 Get-Content
讀取剛剛存好的檔案, 在讀取 big5 編碼的檔案時就會解譯錯誤:
❯ Get-Content .\out_utf8.txt
測試
❯ Get-Content .\out_big5.txt
����
如果指定使用 big5 編碼, 就會正確:
❯ Get-Content -Encoding big5 .\out_big5.txt
測試
你也可以使用 $PSDefaultParameterValues['Get-Content:encoding']
來設定 Get-Content
預設的 -encoding
選項值:
❯ $PSDefaultParameterValues['Get-Content:encoding']='big5'
❯ Get-Content .\out_big5.txt
測試
如果是具有 BOM 的檔案, 那麼即使不指定編碼, 甚至指定錯誤的編碼, Get-Content 都還是可以從 BOM 得到正確的編碼, 解讀檔案中的文字。
外部程式輸出到終端機時的編碼
PowerShell 本質是一個 .net 的 console 程式, 外部程式如果要顯示文字到畫面上, 就是由 [console]::OutputEncoding
來決定要如何解譯外部程式輸出的文字。例如以下這個 print.c 程式, 同時會輸出『測試』兩字以 UTF-8 及 Big5 編碼的位元組序列:
#include<stdio.h>
#include <unistd.h>
#include <string.h>
int main(){
char strUTF8[] = "UTF8:\xE6\xB8\xAC\xE8\xA9\xA6\x0A";
char strbig5[] = "big5:\xB4\xFA\xB8\xD5\x0A";
write(1, strUTF8, strlen(strUTF8));
write(1, strbig5, strlen(strbig5));
}
繁體中文 Windows 在 PowerShell 預設的情況下, [console]::OutputEncoding 是 big5:
❯ [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
所以執行 print.c 程式只有 big5 編碼的位元組序列才能正確顯示『測試』:
❯ .\print
UTF8:皜祈岫
big5:測試
UTF-8 位元組序列所顯示的『皜祈岫』是把 "\xE6\xB8\xAC\xE8\xA9\xA6\x0A" 當成 big5 解譯, 所以把 "\xE6\xB8" 當一個字, 變成『皜』;"\xAC\xE8" 當一個字, 變成『祈』;"\xA9\xA6" 也當一個字, 變成『岫』了。
如果修改設定, 改採 UTF-8 編碼:
❯ [console]::OutputEncoding=[text.encoding]::UTF8
就會變成以 UTF-8 編碼的位元組序列可以正常顯示『測試』, 而 big5 編碼的位元組序列因為不符合 UTF-8 編碼規則, 顯示成 3 個『�』:
❯ .\print
UTF8:測試
big5:���
只要設定回原本的編碼方式, 就又恢復原樣了。要以編碼頁取得特定編碼, 可以使用[text.encoding]::GetEncoding()
:
❯ [console]::OutputEncoding=[text.encoding]::GetEncoding(950)
❯ [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
❯ .\print
UTF8:皜祈岫
big5:測試
從終端機輸入文字到外部程式時的文字編碼
要從終端機輸入資料到外部程式時,是由 [Console]::InputEncoding
來決定使用哪一種文字編碼,繁體中文的 PowerShell 預設也是 big5:
❯ [Console]::OutputEncoding
EncodingName : Chinese Traditional (Big5)
WebName : big5
HeaderName : big5
BodyName : big5
Preamble :
WindowsCodePage :
IsBrowserDisplay :
IsBrowserSave :
IsMailNewsDisplay :
IsMailNewsSave :
IsSingleByte : False
EncoderFallback : System.Text.InternalEncoderBestFitFallba
ck
DecoderFallback : System.Text.InternalDecoderBestFitFallba
ck
IsReadOnly : False
CodePage : 950
舉例來說, 仍然以前面使用過的 xxd 工具為例,若不指定輸入檔,就會從終端機讀取輸入:
❯ xxd -g1 -u
測試
^Z
00000000: B4 FA B8 D5 0D 0A ......
其中 ^Z
是按 Ctrl+Z 輸入 Windows 上代表檔案結束的標記,可以看到取得的是 big5 編碼總共 6 個位元組的結果。如果將終端機輸入的編碼改為 utf-8:
❯ [Console]::InputEncoding = [text.encoding]::UTF8
❯ [Console]::InputEncoding
Preamble :
BodyName : utf-8
EncodingName : Unicode (UTF-8)
HeaderName : utf-8
WebName : utf-8
WindowsCodePage : 1200
IsBrowserDisplay : True
IsBrowserSave : True
IsMailNewsDisplay : True
IsMailNewsSave : True
IsSingleByte : False
EncoderFallback : System.Text.EncoderReplacementFallback
DecoderFallback : System.Text.DecoderReplacementFallback
IsReadOnly : False
CodePage : 65001
重新測試:
❯ xxd -g1 -u
測試
^Z
00000000: E6 B8 AC E8 A9 A6 0D 0A ........
你可以看到現在收到的是 utf-8 編碼的結果了。
透過資料通道輸出給外部程式時的編碼
$OutputEncoding
變數是 PowerShell 透過資料通道輸出給外部程式時的文字編碼, 但不會影響 >、>>、Out-File 採用的編碼,預設也是 utf-8。
我們一樣可以透過剛剛的 xxd 工具來觀察從 PowerShell 的資料通道實際送來的內容:
❯ echo 測試 | xxd -g1 -u
00000000: E6 B8 AC E8 A9 A6 0D 0A ........
可以看到 getch 收到的是 utf-8 編碼的文字。如果我們將 $OutputEncoding
修改成其他編碼, 例如這裡我們把 $OutputEncoding 設定成和終端機輸出的編碼一樣,再進行同樣的測試:
❯ $OutputEncoding = [Console]::OutputEncoding
❯ $OutputEncoding
EncodingName : Chinese Traditional (Big5)
WebName : big5
HeaderName : big5
BodyName : big5
Preamble :
WindowsCodePage :
IsBrowserDisplay :
IsBrowserSave :
IsMailNewsDisplay :
IsMailNewsSave :
IsSingleByte : False
EncoderFallback : System.Text.InternalEncoderBestFitFallba
ck
DecoderFallback : System.Text.InternalDecoderBestFitFallba
ck
IsReadOnly : False
CodePage : 950
❯ echo 測試 | xxd -g1 -u
00000000: B4 FA B8 D5 0D 0A ......
就可以看到 getch 收到的變成是 big5 編碼的文字了。
PowerShell 5
在 PowerSehll 5 中,預設為識別碼為 1252 的 ASCII 字碼頁,
> $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
因此如果傳送中文都會變亂碼:
> echo 測試| xxd -g1 -u
00000000: 3F 3F 0D 0A ??..
修改編碼為預設的 big5 後就可以得到正確的結果:
> [text.encoding]::Default
BodyName : big5
EncodingName : 繁體中文 (Big5)
HeaderName : big5
WebName : big5
WindowsCodePage : 950
IsBrowserDisplay : True
IsBrowserSave : True
IsMailNewsDisplay : True
IsMailNewsSave : True
IsSingleByte : False
EncoderFallback : System.Text.InternalEncoderBestFitFallba
ck
DecoderFallback : System.Text.InternalDecoderBestFitFallba
ck
IsReadOnly : True
CodePage : 950
> $OutputEncoding = [text.encoding]::Default
> echo 測試| xxd -g1 -u
00000000: B4 FA B8 D5 0D 0A ......
外部程式命令列參數的文字編碼
在 PowerShell 中執行外部程式時命令列的參數會用系統預設的編碼,你可以在這裡找到自碼頁的識別碼:
> [System.Globalization.CultureInfo]::CurrentCulture.TextInfo.ANSICodePage
950
950 就是繁體中文,像是以下的這個簡單的 C 程式:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
char* c;
int main(int argc, char *argv[]) {
if(argc > 1) {
c = argv[1];
while(*c) {
printf("\\x%02X", (unsigned char)*c);
c++;
}
printf("\n%s", argv[1]);
}
}
它會把第一個命令列參數實際的字碼顯示出來,然後在下一行顯示顯示參數內容:
> .\arg 測試
\xB4\xFA\xB8\xD5
測試
你可以看到第一行顯示的就是 "測試" 的 big-5 編碼,而第二行就顯示了正確的內容。由於是以系統的設定為準,在繁體中文 Windows 環境下如果傳入日文,就會出錯,例如:
> .\arg "おはよう"
\x74\x65\x73\x74
test
Top comments (0)