しばたテックブログ

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

PowerShellでBOM無しUTF8を簡単に扱う、デフォルト設定を簡単に変える方法

【2017/11/06追記】

現在開発中のPowerShell 6.0からファイル出力に関わるエンコーディングの扱いが変わり、BOM無しUTF-8をより簡単に扱える様になっています。
詳細は以下のエントリを見てください。

blog.shibata.tech

【追記ここまで】


なんとなく思いついて試したら意外といい感じになったので。

はじめに

PowerShellのUTF8はBOM付きUTF8

PowerShellにはOut-File等といったファイルを簡単に扱うためのコマンドレットがいくつか存在します。

たとえばOut-FileでUTF8のファイルを出力する場合は以下の様に-Encodingパラメーターを指定してやればOKです。

"ファイルの内容" | Out-File ".\UTF8なファイル.txt" -Encoding utf8

ただし、PowerShell(とその基盤である.NET Framework)においてUTF8はBOM付きです。

BOM無しのUTF8を扱う場合はOut-Fileなどのコマンドレットは使用できず、以下の様にBOM無しのSystem.Text.UTF8Encodingクラスを生成してやり.NET FrameworkのIO処理を頑張って書いてやる必要があります。(追記あり)

# BOM無しのUTF8エンコーディングクラスを生成
#   コンストラクタの第一引数 = $false でBOM無し
$UTF8woBOM = New-Object "System.Text.UTF8Encoding" -ArgumentList @($false)
# あとは.NET FrameworkのIO処理を頑張って書く
[System.IO.File]::WriteAllLines((Join-Path $PWD "BOMなしUTF8なファイル.txt"), @("ファイルの内容"), $UTF8woBOM)

-Encodingパラメーターで指定可能なエンコーディング

また、先の-Encodingパラメーターは文字列でエンコーディング名を指定しており、コマンドレットによって指定可能なパラメーターは微妙に異なるのですが、おおむね以下となっています。

-Encodingパラメータの値 対応する.NET Frameworkのエンコーディング
unknown System.Text.Encoding.Unicode
string System.Text.Encoding.Unicode
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だと System.Text.Encoding.GetEncoding(28591)(PowerShell Core 6.0だとUTF8) *1
oem GetOEMCP()関数で得られるコードページのエンコーディング

これら以外のエンコーディングを扱おうとするとBOM無しUTF8の場合と同様に.NET FrameworkのIO処理を頑張って書くしかありません。

せっかく便利なコマンドレットが提供されているにもかかわらず、これはちょっと不便でなりません。

BOM無しUTF8を簡単に扱う、デフォルト設定を簡単に変える方法

で、実際不便をするケースがあったのでInvoke-CustomEncodingBlockというファンクションを作ってみました。

Invoke-CustomEncodingBlock ファンクション

実装は次で説明しますのでまずは使い方から。

Invoke-CustomEncodingBlockでは引数にスクリプトブロックを指定し、そのスクリプトブロック中でのみutf8defaultパラメーターの挙動を変更する様にしています。

BOM無しUTF8を扱いたい場合は、スクリプトブロックと-UseBOMlessUTF8パラメータを使い以下の様にします。

Invoke-CustomEncodingBlock { "BOMなしUTF8なファイルの内容" | Out-File ".\BOMなしUTF8.txt" -Encoding utf8 } -UseBOMlessUTF8

これでOut-FileからBOM無しUTF8のファイルを出力することができます。

また、-DefaultCodePageパラメーターを使うとdefaultを任意のコードページに差し替えることができます。
例えば以下の様にするとEUC(コードページ51932)のファイルを出力することができます。

Invoke-CustomEncodingBlock { "EUCなファイルの内容" | Out-File ".\UEC_CP51932.txt" -Encoding default } -DefaultCodePage 51932

そしてOut-Fileコマンドレット以外にも利用できますので、たとえば、

Invoke-CustomEncodingBlock { Get-Content -Path ".\UEC_CP51932.txt" -Encoding default } -DefaultCodePage 51932

の様にするとGet-ContentでEUCのファイルを読み込みことができたりもします。

いかがしょうか?
既存のコマンドレットを生かしつつ好きなエンコーディングを扱えるので結構便利だと思います。

実装

実装はGistに上げています。
PowerShell 2.0, 4.0, 5.1, 6.0.0.alpha10 *2 で動作確認をしています。

仕組み

仕組みは極めて単純かつ強引ですw
uft8defaultパラメーターは最終的にSystem.Text.Encoding.UTF8System.Text.Encoding.Defaultに変わるため、リフレクションで強引にこれらのエンコーディングの中身を差し替え、スクリプトブロックを呼び終えたら元に戻すということをやっています。

このため、RunSpaceを使って並列処理をしている場合は予期せぬ問題が起きる可能性がありますが、それ以外の場合はおおむね問題ないはずです。
PowerShellのバージョンによって差し替える必要のあるフィールドに違いがあるため、そのあたりは工夫しています。

最後に

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

思いつきで作ったものですが、想像以上に便利で個人的にはかなり気に入っています。

追記 BOM無しUTF8を簡単に扱う別解

【2016/10/03追記】

Add-ContentSet-Contentにバイナリデータを扱う-Encoding Byteパラメーターがあるのを今更ながら知りました...
Byte[]なバイナリデータをそのままファイルに書き込みできるため、BOM無しUTF8は、

"ファイルの内容" `
    | % { [Text.Encoding]::UTF8.GetBytes($_) } `
    | Set-Content -Path ".\BOMなしUTF8なファイル.txt" -Encoding Byte

の様にしてあっさり簡単に書き込むことができます。
自分のPowerShell力の低さがうらめしいです...

【2017/05/18追記】

こちらの方法ですが、状況によってはOut-Stringを使って明示的に改行込みの文字列にしてやらないと改行コードが抜かされて出力される場合があります。

[ファイルの内容を出力するコマンドなど] | `
    | Out-String `
    | % { [Text.Encoding]::UTF8.GetBytes($_) } `
    | Set-Content -Path ".\BOMなしUTF8なファイル.txt" -Encoding Byte

参考)

satob.hatenablog.com

*1:PowerShell Core 6.0 Beta.1よりUTF8を使う様に変更されている 参考 : https://github.com/PowerShell/PowerShell/pull/3467

*2:PowerShell 6.0では-Encodingパラメーターの扱いが変わったのでこの関数を使えない様にしました