DEV Community

Cover image for PowerShell 的文字編碼
codemee
codemee

Posted on

PowerShell 的文字編碼

輸出到檔案的編碼

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
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

在 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 來決定使用哪一種文字編碼,預設是 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
Enter fullscreen mode Exit fullscreen mode

舉例來說, 我撰寫了一個可以將標準輸入的資料以 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;
}
Enter fullscreen mode Exit fullscreen mode

直接執行並且輸入 "測試":

❯ ./getch
測試
\xE6\xB8\xAC\xE8\xA9\xA6\x0A
Enter fullscreen mode Exit fullscreen mode

可以看到取得的是 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
Enter fullscreen mode Exit fullscreen mode

現在把終端機輸入的編碼改成和輸出一樣:

❯ [Console]::InputEncoding = [console]::OutputEncoding
# ./getch
測試
\xB4\xFA\xB8\xD5\x0A
Enter fullscreen mode Exit fullscreen mode

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

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

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

我們一樣可以透過剛剛的 getch 程式來觀察透過 PowerShell 的資料通道實際送來的內容:

❯ echo 測試 | .\getch
\xE6\xB8\xAC\xE8\xA9\xA6\x0A
Enter fullscreen mode Exit fullscreen mode

可以看到 getch 收到的是 UTF8 編碼的文字。如果我們將 $OutputEncoding 修改成其他編碼, 例如這裡我們把 $OutputEncoding 設定成和終端機輸出的編碼一樣,再進行同樣的測試:

❯ $OutputEncoding = [Console]::OutputEncoding
❯ echo '測試' | ./getch
\xB4\xFA\xB8\xD5\x0A
Enter fullscreen mode Exit fullscreen mode

就可以看到 getch 收到的變成是 big5 編碼的文字了。

Top comments (0)