DEV Community

Cover image for PowerShell 的文字編碼
codemee
codemee

Posted on • Edited on

PowerShell 的文字編碼

輸出到檔案的編碼

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
Enter fullscreen mode Exit fullscreen mode

若不指定 -encoding 選項, 預設就是 UTF-8 編碼, 因此以下兩次使用 Get-Content 讀取剛剛存好的檔案, 在讀取 big5 編碼的檔案時就會解譯錯誤:

❯ Get-Content .\out_utf8.txt
測試
❯ Get-Content .\out_big5.txt
����
Enter fullscreen mode Exit fullscreen mode

如果指定使用 big5 編碼, 就會正確:

❯ Get-Content -Encoding big5 .\out_big5.txt
測試
Enter fullscreen mode Exit fullscreen mode

你也可以使用 $PSDefaultParameterValues['Get-Content:encoding'] 來設定 Get-Content 預設的 -encoding 選項值:

❯ $PSDefaultParameterValues['Get-Content:encoding']='big5'
❯ Get-Content .\out_big5.txt
測試
Enter fullscreen mode Exit fullscreen mode

如果是具有 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));
}
Enter fullscreen mode Exit fullscreen mode

繁體中文 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
Enter fullscreen mode Exit fullscreen mode

所以執行 print.c 程式只有 big5 編碼的位元組序列才能正確顯示『測試』:

❯ .\print
UTF8:皜祈岫
big5:測試
Enter fullscreen mode Exit fullscreen mode

UTF-8 位元組序列所顯示的『皜祈岫』是把 "\xE6\xB8\xAC\xE8\xA9\xA6\x0A" 當成 big5 解譯, 所以把 "\xE6\xB8" 當一個字, 變成『』;"\xAC\xE8" 當一個字, 變成『』;"\xA9\xA6" 也當一個字, 變成『』了。

如果修改設定, 改採 UTF-8 編碼:

❯ [console]::OutputEncoding=[text.encoding]::UTF8
Enter fullscreen mode Exit fullscreen mode

就會變成以 UTF-8 編碼的位元組序列可以正常顯示『測試』, 而 big5 編碼的位元組序列因為不符合 UTF-8 編碼規則, 顯示成 3 個『�』:

❯ .\print
UTF8:測試
big5:���
Enter fullscreen mode Exit fullscreen mode

只要設定回原本的編碼方式, 就又恢復原樣了。要以編碼頁取得特定編碼, 可以使用[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:測試
Enter fullscreen mode Exit fullscreen mode

從終端機輸入文字到外部程式時的文字編碼

要從終端機輸入資料到外部程式時,是由 [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
Enter fullscreen mode Exit fullscreen mode

舉例來說, 仍然以前面使用過的 xxd 工具為例,若不指定輸入檔,就會從終端機讀取輸入:

❯ xxd -g1 -u
測試
^Z
00000000: B4 FA B8 D5 0D 0A                                ......
Enter fullscreen mode Exit fullscreen mode

其中 ^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
Enter fullscreen mode Exit fullscreen mode

重新測試:

❯ xxd -g1 -u
測試
^Z
00000000: E6 B8 AC E8 A9 A6 0D 0A                          ........
Enter fullscreen mode Exit fullscreen mode

你可以看到現在收到的是 utf-8 編碼的結果了。

透過資料通道輸出給外部程式時的編碼

$OutputEncoding 變數是 PowerShell 透過資料通道輸出給外部程式時的文字編碼, 但不會影響 >、>>、Out-File 採用的編碼,預設也是 utf-8。

我們一樣可以透過剛剛的 xxd 工具來觀察從 PowerShell 的資料通道實際送來的內容:

❯ echo 測試 | xxd -g1 -u
00000000: E6 B8 AC E8 A9 A6 0D 0A                          ........
Enter fullscreen mode Exit fullscreen mode

可以看到 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                                ......
Enter fullscreen mode Exit fullscreen mode

就可以看到 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
Enter fullscreen mode Exit fullscreen mode

因此如果傳送中文都會變亂碼:

> echo 測試| xxd -g1 -u
00000000: 3F 3F 0D 0A                                      ??..
Enter fullscreen mode Exit fullscreen mode

修改編碼為預設的 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                                ......
Enter fullscreen mode Exit fullscreen mode

外部程式命令列參數的文字編碼

在 PowerShell 中執行外部程式時命令列的參數會用系統預設的編碼,你可以在這裡找到自碼頁的識別碼:

> [System.Globalization.CultureInfo]::CurrentCulture.TextInfo.ANSICodePage
950
Enter fullscreen mode Exit fullscreen mode

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]);
  }
}
Enter fullscreen mode Exit fullscreen mode

它會把第一個命令列參數實際的字碼顯示出來,然後在下一行顯示顯示參數內容:

> .\arg 測試
\xB4\xFA\xB8\xD5
測試
Enter fullscreen mode Exit fullscreen mode

你可以看到第一行顯示的就是 "測試" 的 big-5 編碼,而第二行就顯示了正確的內容。由於是以系統的設定為準,在繁體中文 Windows 環境下如果傳入日文,就會出錯,例如:

> .\arg "おはよう"
\x74\x65\x73\x74
test
Enter fullscreen mode Exit fullscreen mode

Top comments (0)