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

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more