シーゴの Excel 研究室

タイトル変更しました (旧称:今日を乗り切るExcel研究所)

【最新版】シフトJIS を UTF-8 に変換するバッチ

今回は、UTF-8 変換バッチを再度改良しましたので公開します。

最新版 UTF-8 変換バッチ Version3完全版 スペシャル

だいぶ前の記事で、 シフト JIS のテキストファイルを BOM なしの UTF−8 に変換するバッチを紹介しました。

時間も経って色々問題点も明らかになり、それら改善して完成度を上げたバッチを作成しました。

基本的な機能は変わらないのですが、使い勝手も信頼性も向上しています。

過去記事のバッチをご使用の方には、特に問題がなければ、こちらの新しいバッチの方をご利用をお勧めします。

もし、不具合や要望等ありましたら、本記事のコメントか Twitter にてお知らせください。

UTF-8 変換の各種バッチスクリプト

テキストファイルの UTF-8 変換でありそうな、以下の4つの パターンの変換バッチを用意しました。

以前の記事のものと基本的に機能は変わらないのですが、使い勝手が改善されています。

  • ファイル指定にワイルドカードが使えます(*?[]
  • ファイル名に含まれるとエラーとなってしまう文字が減りました
  • エラーが発生した場合、バッチ実行を中断するようになりました
  • 入力ファイルのエンコーディング(コードページ)が厳密になりました(システムロケールに依存しない)
  • 一部、ファイルサイズによる制限が緩和されています
  • 一部、パフォーマンスが向上しているものもあります
  • 実行時に一時ファイルを使わなくなりました

スクリプト自体もだいぶ解りやすくなりまして、その分、行数も増えています。

【免責】
本記事に掲載のバッチスクリプト(本バッチ)の使用によって生じたいかなる損害・損失についても本ブログの著者は責任を負いかねますので予めご了承ください。
本バッチは新規に作成されたもので、十分な検証・運用実績がありません。 また、本バッチはあくまで個人の業務効率化のために供するものであり、事業や組織のシステムに組み込んだり運用管理作業の自動化等での使用はご遠慮ください 。
 
【セキュリティ上の注意】
本バッチは読者の所属する組織のセキュリティポリシーによっては使用を許容されない場合があります。 使用に際してはシステム管理者やセキュリティ責任者に相談し、その指示に従ってください。
また本バッチを他者と共有する場合は、バッチファイル自体をやり取りせずに、本ブログの URL を伝えるようにしてください。 本バッチに限らず、バッチファイルをメールやチャットで送信したり、共有フォルダやクラウドに公開するなどファイル自体を共有してはいけません。 また安全が確認できないバッチファイルは決して実行しないよう注意してください。

 



 

シフトJIS ⇒ 標準 UTF-8(BOM なし)

シフトJIS のテキストを 標準の UTF-8 (BOM なし) に変換して別ファイルに出力します。

@setlocal&set "a=%*"&powershell -nop "'&{#'+(gc \""%~f0\"" -raw)+'} '+($Env:a -replace '(?=[''(){},;`$&])','`')|iex"&pause&exit/b

# Shift_JIS -> UTF-8 (no BOM)

$ErrorActionPreference = 'Stop'
$fileTypes = @('*.txt', '*.csv')

function sjis_to_utf8($src, $dst) {
    $text = [IO.File]::ReadAllText($src, [Text.Encoding]::GetEncoding(932))
    New-Item -Path $dst -Value $text -ItemType File -Force > $null
}

foreach ($file in Get-ChildItem -Path $args) {
    if (Test-Path -LiteralPath $file -Include $fileTypes -Type Leaf) {
        $dst = $file.fullname + ".utf8" + $file.extension
        echo $dst
        sjis_to_utf8 $file $dst
    }
}

 

シフトJIS ⇒ BOM 付き UTF-8

シフトJIS のテキストを BOM 付き UTF-8 に変換したファイルを出力します。

@setlocal&set "a=%*"&powershell -nop "'&{#'+(gc \""%~f0\"" -raw)+'} '+($Env:a -replace '(?=[''(){},;`$&])','`')|iex"&pause&exit/b

# Shift_JIS -> BOM UTF-8

$ErrorActionPreference = 'Stop'
$fileTypes = @('*.txt', '*.csv')

function sjis_to_bom_utf8($src, $dst) {
    $text = [IO.File]::ReadAllText($src, [Text.Encoding]::GetEncoding(932))
    Set-Content -Path $dst -Value $text -Force -NoNewLine -Encoding UTF8
}

foreach ($file in Get-ChildItem -Path $args) {
    if (Test-Path -LiteralPath $file -Include $fileTypes -Type Leaf ) {
        $dst = $file.fullname + ".utf8bom" + $file.extension
        echo $dst
        sjis_to_bom_utf8 $file $dst
    }
}

 

BOM 付き UTF-8 ⇒ 標準 UTF-8(BOM なし)

BOM 付きの UTF-8 の BOM を取り除き、標準の UTF-8 (BOM なし) としたファイルを出力します。

もし、元ファイルがもともと標準 UTF-8 だったら、何も変換せず、単純にファイルをコピーします。

@setlocal&set "a=%*"&powershell -nop "'&{#'+(gc \""%~f0\"" -raw)+'} '+($Env:a -replace '(?=[''(){},;`$&])','`')|iex"&pause&exit/b

# BOM UTF-8 -> UTF-8 (no BOM)

$ErrorActionPreference = 'Stop'
$fileTypes = @('*.txt', '*.csv')

function bom_utf8_to_std_utf8($src, $dst) {
    $bom = [Byte[]](0xEF,0xBB,0xBF)
    $tip = @() + (Get-Content -LiteralPath $src -First 3 -Encoding Byte)
    $hasBom = ($null -eq (Compare-Object -SyncWindow 0 $bom $tip))
    if ($hasBom) {
        $text = (Get-Content -LiteralPath $src -Encoding UTF8 -Raw)
        New-Item -Path $dst -Value $text -ItemType File -Force > $null
    } else {
        $copy = (Copy-Item -Path $src -Destination $dst -Force -PassThru)
        $copy.LastWritetime = (Get-Date)
    }
}

foreach ($file in Get-ChildItem -Path $args) {
    if (Test-Path -LiteralPath $file -Include $fileTypes -Type Leaf ) {
        $dst = $file.fullname + ".utf8" + $file.extension
        echo $dst
        bom_utf8_to_std_utf8 $file $dst
    }
}

 

標準 UTF-8(BOM なし)⇒ BOM 付き UTF-8

標準の UTF-8 (BOM なし) に BOM を付加し、 BOM 付きの UTF-8 として別ファイルに保存します。

ダブルクリックして CSV ファイルを Excel で開いたときに文字化けしたら、このバッチで変換してから再度開いてみてください。

もし、元からファイルが BOM 付きの UTF-8 だったら、何も変換せず、単純にファイルをコピーします。

@setlocal&set "a=%*"&powershell -nop "'&{#'+(gc \""%~f0\"" -raw)+'} '+($Env:a -replace '(?=[''(){},;`$&])','`')|iex"&pause&exit/b

# UTF-8 (no BOM) -> BOM UTF-8

$ErrorActionPreference = 'Stop'
$fileTypes = @('*.txt', '*.csv')

function std_utf8_to_bom_utf8($src, $dst) {
    $bom = [byte[]](0xEF,0xBB,0xBF)
    $tip = @() + (Get-Content -LiteralPath $src -First 3 -Encoding Byte)
    $hasBom = ($null -eq (Compare-Object -SyncWindow 0 $bom $tip))
    if ($hasBom) {
        $copy = (Copy-Item -Path $src -Destination $dst -Force -PassThru)
        $copy.LastWritetime = (Get-Date)
    } else {
        Set-Content -Path $dst -Value $bom -Force -Encoding Byte
        $msg, $ok = (cmd /c copy /b `"$dst`" `+ `"$src`" `"$dst`"), $?
        if (!$ok) { throw "CMD Error: $msg" }
    }
}

foreach ($file in Get-ChildItem -Path $args) {
    if (Test-Path -LiteralPath $file -Include $fileTypes -Type Leaf ) {
        $dst = $file.fullname + ".utf8bom" + $file.extension
        echo $dst
        std_utf8_to_bom_utf8 $file $dst
    }
}

 



使い方

上記4つのバッチスクリプトのうち、必要なものをテキストエディタにコピー&ペーストし、任意のファイル名で拡張子「.bat」にして保存して下さい。

あとはそのバッチのアイコンに、変換したいテキストファイルをドラッグ&ドロップするだけです。 同じフォルダ内に、UTF-8 変換されたファイルが保存されます。

コンテキストメニューの「送る」へバッチを登録すると、右クリックから実行できて便利でしょう(後述)。

変換対象となるファイルは、拡張子(ファイル名の最後)が「.txt」か「.csv」となっているものだけです。 (大文字でも可)

複数ファイルをドラッグ&ドロップして、一括変換も可能です。 ただし、あまり大量のファイルを与えると、バッチ実行時の文字数制限(約 8000 字)に引っかかって、 エラーになる可能性もありますので注意してください。

大量のファイルを一括処理したい場合は、コマンドプロンプトのコマンドラインでワイルドカードを使って入力ファイルを指定し、バッチを直接実行してみてください。

「BOM 付き UTF-8 ⇒ 標準 UTF-8(BOM なし)」と「標準 UTF-8(BOM なし)⇒ BOM 付き UTF-8」は、入力ファイルが変換後と同じ UTF-8 形式なら単純なファイルコピーとなります。 これで複数ファイルに BOM 付き/BOM なしが混在していても問題なく一括で処理できます。

実行後は、文字化けしていないか、必ず出力ファイルの内容を確認してください。

【Note】 テキストファイルのエンコーディング形式(文字コード)が何になっているのかを確認するには、「メモ帳」で開いてみるのが簡単です (Windows 10 以降なら) 。 メモ帳で開いたとき、ウィンドウの右下(ステータスバー)に、そのテキストファイルのエンコーディング形式が表示されます。
ここで「ANSI」と表示されていればシフト JIS、「UTF-8」とあればそれはBOMなしの標準UTF-8、もし BOM があれば「UTF-8 (BOM 付き) 」と表示されます。

仕様詳細

  • 入力ファイル形式: 「*.txt」、「*.csv」
  • 対応エンコーディング: バッチスクリプトにより、Shift_JIS(CP932)、UTF-8(BOMなし)、BOM 付き UTF-8 の組み合わせ
  • 一括ファイル数制限: 各ファイルのフルパス名の合計が、約 8000 文字を超えない程度(ワイルドカードで回避可)
  • 入力ファイル名: 特殊な記号を含むとエラーになることがあります。コマンドラインから、ファイル名をダブルクオーテーションで囲む、特殊文字を^でエスケープする、ワイルドカードを使うなどすれば回避できるかもしれません。
    特に以下の特殊文字は回避が難しいのでファイル名に含まれないよう注意してください。
    • &(アンパサンド)、^(ハット)、[](ブラケット)
  • ワイルドカード: PowerShell のワイルドカードが使用できます
    • *(0文字以上)
    • ? (1文字)
    • [] (文字セット)
  • 出力ファイル名: 入力ファイルのあるフォルダに、以下の要領でサフィックスを付加したファイル名で保存します。すでに同名のファイルがある場合は上書きされます。
    • 標準 UTF-8: 「テキスト.txt」⇒「テキスト.txt.utf8.txt」
    • BOM 付き UTF-8: 「テキスト.csv」⇒「テキスト.csv.utf8bom.csv」
  • エラー処理: 複数ファイルを処理しているときなどに途中でエラーが発生すると、バッチ自体を中断するため、以降のファイルが処理されることはありません。

【Note】「送る」メニューへの登録  
UTF-8 への変換作業を日常的に行うのなら、コンテキストメニューの「送る」メニューからバッチ実行できると便利です。

複数ファイルを選択状態で実行すれば、一括で変換できます。
 
このバッチを「送る」メニューに追加するには、バッチファイルを「SendTo」フォルダ内に保存するだけです。 「SendTo」フォルダとは、エクスプローラ(フォルダウィンドウ)のアドレスバーに「sendto」と入力すると開けるフォルダです。
 
これで UTF-8 変換したいテキストファイルのアイコンの右クリックからバッチを実行できるようになります。 キー操作のみで「送る」メニューを開くには、キーボードの右下の方にあるメニューキー(アプリケーションキー)を押してからNキーを押します 。 もしキーボードにメニューキーがなければ F10 キー(Windows 11 の場合 Shift+F10 )が使えます。
 
ところで Windows 11 になってからコンテキストメニューの内容が整理され、これまでのメニューにアクセスするには最下部の「その他のオプションを表示」から間接的にたどらなければいけなくなりました。

Windows 11 で従来のコンテキストメニューを一発で直接出したければ、マウス操作なら Shift キーを押しながら右クリック、キー操作なら Shift+F10 をタイプします。
あるいはキーボードに メニューキー(アプリケーションキー)があるのなら、そのまま今まで通りコンテキストメニューを出せます。

 



【おまけ】PowerShell で BOM なし UTF-8 出力

バレバレだとは思いますが、上記バッチはバッチと言いつつ実態は PowerShell スクリプトなのでした。

当ブログを PowerShell に関心のある方が訪問されることもあるようなので、今回調べて理解したことを共有しておきます。

PowerShell の UTF-8 事情

PowerShell のテキストファイルの扱いには色々クセがあってやっかいです。

Set-Content などのコマンドレットは Encoding パラメータでテキストエンコーディングの指定ができるのですが、 これにに UTF8 を指定すると BOM 付き UTF-8 になります。

環境設定などでデフォルトのエンドーディングを UTF-8 に変更する方法もいくつかあるのですが、それでも BOM 付き UTF-8 になります。

PowerShell 7.x なら BOM なし UTF-8 が扱える(むしろ推奨している)のですが、Windows 11 に至ってもまだ PowerShell 5.1 のままなので、標準環境では如何ともしがたいです。

現状の PowerShell で BOM なし UTF-8 を出力する対応策には以下のようなものがあります。

# New-Item コマンドレットを使う
function sjis2utf8_01($src, $dst) {
    $text = Get-Content -LiteralPath $src -Raw -Encoding Default
    New-Item -ItemType "file" -Path $dst -Value $text -Force > $null
}

# System.IO.File.WriteAllText()を使う
function sjis2utf8_02($src, $dst) {
    $sjis = [System.Text.Encoding]::GetEncoding("shift_jis") # 932
    $text = [System.IO.File]::ReadAllText($src, $sjis)
    [System.IO.File]::WriteAllText($dst, $text)
}

# System.IO.File.Convert()を使う
function sjis2utf8_03($src, $dst) {
    $srcEnc = [System.Text.Encoding]::GetEncoding("shift_jis") # 932
    $dstEnc = [System.Text.UTF8Encoding]::new($false) # no BOM
    $srcBytes = [System.IO.File]::ReadAllBytes($src)
    $dstBytes = [System.Text.Encoding]::Convert($srcEnc, $dstEnc, $srcBytes)
    [System.IO.File]::WriteAllBytes($dst, $dstBytes)
}

New-Item コマンドレット

New-Item コマンドレットでファイルを作成すると BOM なし UTF-8 で保存されます。

Set-Content の代わりに使えそうですが、パイプラインを受け付けないなど、不便な点もあります。(訂正:使えます)

System.IO.File.WriteAllText()

.Net Framework の System.Text.Encoding.WriteAllText() は、Encoding を指定できるのですが、これを省略すると BOM なし の UTF-8 で出力する、という仕様になっています。

良かれと思って UTF8 を指定すると、かえって BOM 付き の UTF-8 になってしまいますので注意が必要です。 指定するなら BOM なしであることを明示しておく必要があります。

# しれっとBOMなしUTF-8で出力される
[System.IO.FIle]::WriteAllText($file, $text)

# UTF8を指定するとBOM付きUTF-8になってしまう!
[System.IO.FIle]::WriteAllText($file, $text, [System.Text.Encoding]::UTF8)

# BOMなしUTF-8のEncodingを用意する
$stdUTF8 = [System.Text.UTF8Encoding]::new($false)
[System.IO.FIle]::WriteAllText($file, $text, $stdUTF8)

System.Text.Encoding.Convert()

.Net Framework に System.Text.Encoding.Convert() というエンコーディング変換関数が用意されています。 これは文字列をバイナリデータ(byte[])として扱います。

考えてみると、PowerShell の string は文字を UTF-16 で保持しているので、エンコーディングの変換は2回発生していることになります。

Shift_JIS ⇒ UTF-16 ⇒ UTF-8

Encoding.Convert() で無駄な UTF-16 を挟まずに直接変換できたら、もっと高速になりそうな気がします。

Shift_JIS ⇒ UTF-8

他の方法と比較してみると、筆者の環境では、そうですね、だいたい 2.5 倍くらい遅いです。 残念。

まとめ

三度目の正直。

UTF-8 変換バッチを一から作り直して、使い勝手も向上したと思います。

バッチを書きな直そうと思ったのは、以前の記事のバッチにいくつもの問題があることが分かったからで、完全に PowerShell の無知と検証不足によるものでした。

今回できるだけ省略なしで PowerShell の作法に従ったを書きましたので、PowerShell の有識者方、ぜひ突っ込みを入れてやってください。

かなり調査と検証をしたつもりですが、まだ問題がないとは言い切れません。

本当にお願いなのですが、もしなにか不具合を見つけた場合には、お手数ですが下のコメント欄かTwitterにてお知らせください。 よろしくお願いいたします。

参考資料

関連記事

www.shegolab.jp

旧バッチ

過去記事のバッチはお勧めしません。

www.shegolab.jp

www.shegolab.jp

更新履歴

  • [2023/04/24] 公開
  • [2023/04/28] New-Item コマンドレットでウソをついていたので訂正削除
  • [2023/05/12] 一部文言とタイプミスを修正