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になります。
コメント