輸出到檔案的編碼
PowerShell 的 > 與 >> 其實就是 Out-File 這個 cmdlet, 它在輸出文字到檔案時可以使用 -encoding 選項指定檔案的文字編碼, 你也可以透過 $PSDefaultParameterValues[Out-File:Encoding] 設定預設採用的 -encoding 選項值。
若沒有設定, PowerShell 7 預設是 UTF8、PowerShell 5 預設是具有 BOM(Byte order mark) 的 UTF16LE。例如:
-
在 PowerShell 5 中:
> echo 測試 > out_ps5.txt > ls .\out_ps5.txt 目錄: D:\code\test_ampy Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 2021/7/17 下午 01:20 10 out_ps5.txt
檔案內容就是 2 個位元組的 BOM、各 2 個位元組的 2 個中文字、以及各 2 個位元組表示結尾的 \x0D\x00 與 \x0A\x00:
FF FE 2C 6E 66 8A 0D 00 0A 00
其中開頭的 \xFF\xFE 表示這是 little endian 位元組順序的 UTF16 檔案, 因為是 little endian 位元組順序, 所以 \x2C\x6E 就是將低位元組排到前面後的 Unicode \x6E\x2C, 也就是『測』、\x66\x8A 是 Unicode 編碼 \x8A\x66 的『試』。由於是 UTF16, 所以換行字元也都各是 2 個位元組。
如果在 PowerShell 5 中設定為使用 utf8 , 輸出檔案就會採用具有 BOM 的 UTF8 編碼:
> $PSDefaultParameterValues['Out-File:Encoding']='utf8' > echo 測試 > out_ps5.txt > ls .\out_ps5.txt 目錄: D:\code\test_ampy Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 2021/7/17 下午 01:32 11 out_ps5.txt
實際內容如下:
EF BB BF E6 B8 AC E8 A9 A6 0D 0A
開頭的 \xEF\xBB\xBF 就是 BOM, 表示此檔案是以 UTF-8 編碼。接著的內容是各 3 個位元組的 2 個中文字, 加上換行的 \x0D\x0A 共 8 個位元組, 其中 \xE6\xB8\xAC 是『測』、\xE8\xA9\xA6 是『試』。
PowerShell 5 中 -encoding 允許的編碼有以下這幾種, 預設是 Unicode:
參數值 編碼 ASCII 7 位元的 ASCII 編碼 BigEndianUnicode big endian 位元組順序的 UTF-16 Default 系統設定的編碼頁 OEM 系統 OEM 設定的編碼頁 String 同 Unicode Unicode little endian 位元組順序的 UTF-16 Unknown 同 Unicode UTF7 UTF-7 UTF8 UTF-8 UTF32 little endian 位元組順序的 UTF-32 -
在 PowerShell 7 中:
❯ echo 測試 > out_ps7.txt ❯ ls out_ps7.txt Directory: D:\code\test_ampy Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2021/7/17 下午 01:17 8 out_ps7.txt
檔案內容沒有 BOM, 並且以 UTF-8 編碼, 為各 3 個位元組的 2 個中文字, 加上換行的 \x0D\x0A 共 8 個位元組:
E6 B8 AC E8 A9 A6 0D 0A
其中 \xE6\xB8\xAC 是『測』、\xE8\xA9\xA6 是『試』。
在 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 ❯ $PSDefaultParameterValues['Out-File:Encoding']='big5' ❯ echo 測試 > out_ps7.txt ❯ ls out_ps7.txt Directory: D:\code\test_ampy Mode LastWriteTime Length Name ---- ------------- ------ ---- -a--- 2021/7/17 下午 01:55 6 out_ps7.txt
檔案長度就變成 2 個各佔 2 個位元組的中文字以及換行符號的 2 個位元組, 共 6 個位元組了。
請特別留意: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));
}
在 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 來決定使用哪一種文字編碼,預設是 utf-8:
❯ [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 : True
CodePage : 65001
舉例來說, 我撰寫了一個可以將標準輸入的資料以 16 進位顯示的 getch.c 程式:
#include <stdio.h>
#include <unistd.h>
int main( ) {
unsigned char c;
int count;
while((count=read(0, &c, 1))>0) {
printf("\\x%02X", c);
if(c==0x0a) {
return 0;
}
}
return 0;
}
直接執行並且輸入 "測試":
❯ ./getch
測試
\xE6\xB8\xAC\xE8\xA9\xA6\x0A
可以看到取得的是 utf-8 編碼的結果。如果更改編碼為 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
現在把終端機輸入的編碼改成和輸出一樣:
❯ [Console]::InputEncoding = [console]::OutputEncoding
# ./getch
測試
\xB4\xFA\xB8\xD5\x0A
你可以看到現在收到的是 big5 編碼的結果了。
透過資料通道輸出給外部程式時的編碼
$OutputEncoding 變數是 PowerShell 透過資料通道輸出給外部程式時的文字編碼, 但不會影響 >、>>、Out-File 採用的編碼,預設也是 utf-8。
我們一樣可以透過剛剛的 getch 程式來觀察透過 PowerShell 的資料通道實際送來的內容:
❯ echo 測試 | .\getch
\xE6\xB8\xAC\xE8\xA9\xA6\x0A
可以看到 getch 收到的是 UTF8 編碼的文字。如果我們將 $OutputEncoding 修改成其他編碼, 例如這裡我們把 $OutputEncoding 設定成和終端機輸出的編碼一樣,再進行同樣的測試:
❯ $OutputEncoding = [Console]::OutputEncoding
❯ echo '測試' | ./getch
\xB4\xFA\xB8\xD5\x0A
就可以看到 getch 收到的變成是 big5 編碼的文字了。
Top comments (0)