PowerShellのOut-Fileで出力されるファイルがBOM有Unicodeで驚く

コンピュータ
PowerShellのあれこれ
PowerShellのOut-Fileはパイプラインで繋ぐだけでファイルに出力してくれます。ファイルを開いたり閉じたりの手順が隠蔽されて、とてもPowerShell的で良いのですが、出来上がるテキストファイルがBOM有のUnicodeだったりします。
Unicodeなのは変換で何とかなりそうですがBOM有なのはちょっと面倒です。フラットなテキストファイルを出力する関数を作ってみます。

スクリプト

using namespace System.IO

# BOM無しUTF8でテキストファイルを出力する

function Out-FileWithoutBOM
{
    param(
        [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
        [string[]]$Lines,
        [Parameter(Mandatory=$True, ValueFromPipeline=$False)]
        [string]$FilePath
    )
    begin
    {
        $fs = New-Object StreamWriter($FilePath, $false)
    }
    process
    {
        foreach($line in $Lines)
        {
            $fs.WriteLine($line)
        }
    }
    end
    {
        $fs.Close()
    }
 }

$array = @("abc", "123")

# パイプライン
$path = "H:\ps1\text_file_encode_test\outfile_test1.txt"
$array | Out-FileWithoutBOM -FilePath $path

# 通常呼び出し
$path = "H:\ps1\text_file_encode_test\outfile_test2.txt"
Out-FileWithoutBOM $array -FilePath $path

説明

BOM無しのUTF8のテキストファイルが出力されるように作りました。
このソースでは省略していますが、System.IO.StreamWriterのコンストラクタの3番目引数にエンコーディングを指定するとBOM付きのテキストファイルになります。
パイプラインに対応するように作ってみたつもりですが、パイプラインに引き渡す変数$Linesがstringの配列になっています。
process内でforeachで要素を取り出してWriteLineで一行ごと書き込みしています。
配列でループさせている理由は通常の関数として呼び出した場合の対応です。
多分パイプラインの場合は引数の文字列($_)が要素が一つの配列に変換されて、一回だけforeachしていると思われます。
beginとendはパイプラインでも通常呼び出しでも最初と最後の1回のみ実行されることを期待していますが、例外を考慮すべきでしょう。

感想

Out-FileのデフォルトがBOM無しUTF8だったらいいな。それが無理でも、せめてオプションで選べるようにしてほしい。

UTF8だと文字によってサイズが1~4バイトの可変長だったりするので、メモリーのアドレス管理とか面倒そうなのでUnicode(UTF16)にしているのだと想像します。内部がUnicode(UTF16)でそれをそのままテキストファイルで保存するのでBOMが必要になるのだと思いますが、そこは融通をきかせて欲しい。

それかいっそのこと全部UTF32にすればシンプルになるのではと思います。データ量は増えますがスカスカで圧縮もききそうです。マシンパワーとストレージやメモリ容量を使いますが、人間が楽できそうな気がします。

追記:20221006

こちらの情報はPowerShell5(WindowsPowerShell)の内容です。PowerShell7ではBOM無しのUTF8になります。

コメント