しばたテックブログ

気分で書いている技術ブログです。

PowerShell 6.0からファイル出力に関わるエンコーディングが変わります

現在開発中のPowerShell 6.0ではクロスプラットフォーム化することを主な契機としてファイル出力のエンコーディングに大きな破壊的変更が入ります。

現在最新のPowerShell 6.0 Beta.9でその変更が一部反映されたため本エントリで紹介します。

1. -Encodingパラメーターの型が変わります

これまでのPowerShellでは、例えばOut-Fileなどのファイル出力に係わるコマンドレットの-Encodingパラメーターはstring*1であり、主に以下の内容を指定可能でした。

手元のWindows PowerShell 5.1環境ではざっとこんな感じです。

コマンドレット -Encodingパラメーターの型
Add-Content Microsoft.PowerShell.Commands.FileSystemCmdletProviderEncoding
Export-Clixml System.String
Export-Csv System.String
Export-PSSession System.String
Get-Content Microsoft.PowerShell.Commands.FileSystemCmdletProviderEncoding
Import-Csv System.String
Out-File System.String
Select-String System.String
Set-Content Microsoft.PowerShell.Commands.FileSystemCmdletProviderEncoding

-Encodingパラメータの値 対応する.NET Frameworkのエンコーディング
unknown System.Text.Encoding.Unicode
string System.Text.Encoding.Default
unicode System.Text.Encoding.Unicode
bigendianunicode System.Text.Encoding.BigEndianUnicode
utf8 System.Text.Encoding.UTF8
utf7 System.Text.Encoding.UTF7
utf32 System.Text.Encoding.UTF32
ascii System.Text.Encoding.ASCII
default System.Text.Encoding.Default
(PowerShell Core 6.0だとUTF8)
oem GetOEMCP()関数で得られるコードページのエンコーディング

PowerShell 6.0 Beta.9からは-Encodingパラメーターは全てSystem.Text.Encoding型に統一されます。

一応これまでのパラメーターも全てではないもののstringSystem.Text.Encodingへの変換をしてくれるのでそのまま使えます。

この変更により、Out-Fileなどのコマンドレットで使用可能なエンコーディングの制限がなくなるので利用範囲がより増えることになります。

# 例) たとえばOut-FileでEUC出力するなんてことも可能に
$encoding = [Text.Encoding]::GetEncoding(51932)
"鉄はホットなうちにビートダウン!" | Out-File -FilePath .\sample.txt -Encoding $encoding

非互換のパラメーターについて

今回の変更によりGet-ContentSet-Content-EncodingではByteを指定することが出来なくなりました。
代わりにAsByteStreamパラメーターを使う必要があります。

# PowerShell 5.1まで
Get-Content -Path .\Sample.bin -Encoding Byte

# PowerShell 6.0からは -AsByteStream を使う
Get-Content -Path .\Sample.bin -AsByteStream

2. デフォルトのファイル出力エンコーディングがBOM無しUTF-8になります

こちらは多くの人が望んでいた変更かと思います。
PowerShellがクロスプラットフォーム化したことでより多くの環境にマッチする動作をするための変更となります。

この変更については、もともとRFCで提案されていたものが承認され、前項の変更に合わせてPowershell 6.0 Beta.9で"部分的に"導入されました。

github.com

例として以下の様なリダイレクト演算子やエンコーディング指定のないOut-Fileコマンドレットを実行した場合、これまではBOM付きのUTF-16で出力されましたが、PowerShell 6.0ではBOM無しUTF-8になります。

# PowerShell 5.1まではBOM付きUTF-16、PowerShell 6.0からはBOM無しUTF-8のファイルが作成される
"鉄はホットなうちにビートダウン!" > .\sample.txt

"鉄はホットなうちにビートダウン!" | Out-File .\sample.txt

この変更に合わせて前項の-Encodingパラメーターに新たに

  • utf8BOM
  • utf8NoBOM

と、BOMの有無を明示したものが追加されました。

f:id:stknohg:20171104214636p:plain

RFCの未実装部分について

RFCを最後まで読むとわかるのですが、このRFCではデフォルトのエンコーディングを変える他に、これまでのWindows PowerShellとの互換を図るため$PSDefaultEncoding変数を追加し、従来の挙動と今回変更された挙動を切り替えることができる様にすると記載されています。

この機能については現在のところ未実装でPowerShell 6.0のリリース後要望があれば実装するとの事です。*2

その要望を集めるためのIssueが

github.com

に上げられているので互換性が必要だと思う方はこちらにコメントすると良いでしょう。

注意事項

ちょっとややこしいのですが、今回の変更はファイル出力に係わるエンコーディングのはなしであるため、$OutputEncoding変数や[Console]::OutputEncodingについては変更がありませんので注意してください。

【2017/12/25追記】

本件とは別件でPowerShell 6.0 RC1から$OutputEncodingの既定値がBOM無しUTF8に変更されました。

細かい経緯は

github.com

をご覧ください。
(余談ですが、はじめは既定値をISO-8859-1(いわゆるLatin-1)にする方針で思わずコメントしてしまいました...)

最後に

とりあえずこんな感じです。

今回の変更はPowerShell 6.0で発生する破壊的変更の中でも最大級のものですが、否定的な反応はさほど出ないのではと個人的には思っています。

*1:またはMicrosoft.PowerShell.Commands.FileSystemCmdletProviderEncoding型

*2:細かい話は https://github.com/PowerShell/PowerShell/pull/5080#discussion_r144723400 あたりのコメントを読んでください