powershellでBOMなしUTF8で出力しようとしてencodingに躓いたメモ

powershellでsqlite3に読み込ませるようにデータを加工していたときにEncodingに苦労したときのメモ。
どうもsqlite3は"BOMなしのUTF-8"でないとうまく読み込んでくれないようだったので、powershellからの出力結果を"BOMなしのUTF-8"にしようとしたが、なかなか情報を見つけられなかった。
手作業ならばメモ帳で開いて"BOMなしのUTF-8"で保存すればよいのだが、なるべくコードに落とし込みたかったので。
Windows10でpowershellのバージョンは以下の通り。

PS >$psversiontable

Name                           Value
----                           -----
PSVersion                      5.1.19041.1682
PSEdition                      Desktop
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}
BuildVersion                   10.0.19041.1682
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

encodingをUTF8にして、「Set-Content -Encoding UTF8」で保存するとBOMがついてきてしまう。UTF8NoBOMはサポートされていないようだし、C#のコードを書くという解もあるようだったが腰が引けてしまう。
諦めかけていたときに以下のページを見つけた。
maywork.net
助かりますね。

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

$f = join-path  $pwd  data.tsv
$f
.\aaa.ps1 | Out-FileNoBOM -filepath $f

sqlite3でファイル"data.tsv"をテーブル"book"にインポート。".mode tabs"でタブ区切りに指定。

PS >sqlite3 .\test.sqlite
SQLite version 3.30.1 2019-10-10 20:19:45
Enter ".help" for usage hints.
sqlite> .mode tabs
sqlite> .import data.tsv  book

PowerShell (v6 以上) は、すべてのテキスト出力に utf8NoBOM 対して既定で に設定されます。」というから将来はデフォルトになるのだろう。
お恥ずかしながら、私は-Join '`t'とシングルクォートを使ってうまくいかないことにも躓いた。最初はシングルクォートで括っていてしまい意図した通りに動かないことの原因に気付かなかった。たぶんシングルクォートで括ってしまうと特殊な意味を失いエスケープシーケンスでなくなってしまうのね。変数を展開しないだけではないのね。

....
% { ($_.ToString().Split(',')[1,3]) -Join "`t" }
...

どうも私は体で覚えないといけないタイプのようだ。

【備考】
その後、日付のフォーマットを変更したり、両端のダブルクォーテーションをトリミングしたりと手を加えた。
まずは primary keyが重複しないように現在の最大値を取得し、'max.txt'に出力するSQLファイルを用意。

PS >cat .\sql.txt
.output ./max.txt
select max(id) from book;

.readでSQLファイルを読み込ませて実行させる。

$tmp = 'tmp.txt'
sqlite3  .\publib.sqlite ".read sql.txt"
$mf = 'max.txt'
$s = get-content($mf)
if ( [string]::IsNullOrEmpty($s) ) {
    $cnt = 1
}
else {
    $cnt = [int]::parse($s) + 1
}

$d = 'D:\Document\PublicLibrary'

get-childitem $d -filter RENT*.csv | sort-object |`
% { get-content $_.fullname } | select-string '貸出資料一覧' -notmatch |`
 select-string 'タイトル' -notmatch |`
 select-string '貸出状況照会' -notmatch |`
 select-string '^$' -notmatch |`
% { $a =($_.ToString().Split(',')[1,3]); $a[0].trim('"') + "`t" + ([DateTime]($a[1] -replace('"',''))).ToString('yyyy-MM-dd') } |`
sort-object -unique  > $tmp

$f = ( get-content $tmp )

for ( $i=0; $i -lt $f.length; $i++ ){
    $cnt.tostring() + "`t" + $f[$i]
    $cnt++
}

sqlite3はimportするとき両端の"をトリミングしてくれているようなので気づいていなかったが、MySQLにそのままimportすると両端のダブルクォーテーションが残ってしまうのであった。encodingだけでなく結構、データを加工しないといけないとは・・・。