🐶

PowerShellで文字コードを判別したい

に公開

ちゃんとやりたければ多分ReadJEnc使うのが正解なんだろーけど
イチイチ小物にDLL突っ込む根性がおっさんにはありませんでしたのよオホホホホ
ってわけでおっさんは太古の記憶を元に以下を引っ張り出してどーにかしました
https://dobon.net/vb/dotnet/string/detectcode.html

で、こいつ更に元ネタがあるんだけどこの場合ライセンスってどーなんだろーね???
怖いし一応元ネタの元ネタへのリンクだけでも張っとこーか、クワバラクワバラ
http://openlab.ring.gr.jp/Jcode/index-j.html

本題

基本的にはBOMで判断しますが駄目ならスコアリングでどーにかします
コメントにも書いてありますがスコアリングのそれっぽさ判別は完全に手抜きで
末尾のややこしい処理なんかはどーせ大して効かないだろってことで普通にオミットしてます
つーわけで駄目なら駄目で大人しくこっち使ってください
(BOMなしだと)UTF-16とかそもそもガン無視だしねー

Function AutoGuessEncodingFileSimple {
    param (
        [Parameter(Mandatory = $true)] [string] $Path
    )
    begin {}
    process {
        return AutoGuessEncodingByteSimple ([System.IO.File]::ReadAllBytes($Path))
    }
    end {}
}

Function AutoGuessEncodingByteSimple {
    param (
        [Parameter(Mandatory = $true)] [byte[]] $TextData
    )
    begin {}
    process {
        # BOMによるエンコード判定
        foreach ($elm in [System.Text.Encoding]::GetEncodings()) {
            $oAmb = $elm.GetEncoding().GetPreamble()
            if ($oAmb.Length -gt 0 ) {
                if (Compare-Object $TxtData[0..($oAmb.Length - 1)] $oAmb -IncludeEqual -ExcludeDifferent) {
                    return $elm.GetEncoding()
                }
            }
        }
        # BOM無しなので日本語前提にSJIS/EUC/UTF8の何れかをソレっぽさで判定する
        # 元ネタは特徴的な文字コードを数え上げてスコアリングしてたが...
        # ・スコアリングせずソレっぽい位置を数えるように変更
        # ・末尾のややこしい処理は移植面倒なのでバッサリ破棄
        # 元々の処理が推定だし単発スクリプトならコレで良いでしょ多分
        $SJISCount = 0..($TxtData.Length - 2) | Where-Object {
            $b1 = $TxtData[$_ + 0]
            $b2 = $TxtData[$_ + 1]
            ((0x81 -le $b1 -and $b1 -le 0x9F) -or (0xE0 -le $b1 -and $b1 -le 0xFC)) -and
            ((0x40 -le $b2 -and $b2 -le 0x7E) -or (0x80 -le $b2 -and $b2 -le 0xFC))
        }
        $EUCJCount = 0..($TxtData.Length - 2) | Where-Object {
            $b1 = $TxtData[$_ + 0]
            $b2 = $TxtData[$_ + 1]
            (((0xA1 -le $b1 -and $b1 -le 0xFE) -and (0xA1 -le $b2 -and $b2 -le 0xFE)) -or
             ((0x8E -eq $b1)                   -and (0xA1 -le $b2 -and $b2 -le 0xDF)))
        }
        $UTF8Count = 0..($TxtData.Length - 2) | Where-Object {
            $b1 = $TxtData[$_ + 0]
            $b2 = $TxtData[$_ + 1]
            (0xC0 -le $b1 -and $b1 -le 0xDF) -and (0x80 -le $b2 -and $b2 -le 0xBF)
        }
        if ($SJISCount.Count -gt $EUCJCount.Count -and $SJISCount.Count -gt $UTF8Count.Count) {
            return [System.Text.Encoding]::GetEncoding("Shift-JIS")
        } elseif ($EUCJCount.Count -gt $SJISCount.Count -and $EUCJCount.Count -gt $UTF8Count.Count) {
            return [System.Text.Encoding]::GetEncoding("EUC-JP")
        } else {
            return [System.Text.Encoding]::GetEncoding("UTF-8")
        }
    }
    end {}
}

lt/le/gt/geがパット見でわかるやつって相当HTMLで鍛えてんだろーなぁ
K&R版C言語にしか見えないParamと同じでマジでキショいわ

蛇足

でもPowerShell5の闇は深ぇんすわ
実は標準コマンドレットの-Encodingは[System.Text.Encoding]じゃねぇのです
ってわけで例えばCSVとかShift-JISで出力するならこーんな感じで文字列指定が必要です
PowerShell7でも互換性のためか文字列指定可能なんで古い方に合わせるしかッ

Export-Csv -Path $Path -Encoding "OEM"

Discussion