【bat】バッチでUnicodeファイルを扱えない原因と解決策完全ガイド|chcp・BOM・for /f・PowerShell・実践例まで

【bat】Unicode ファイルを扱えないときの原因と解決策 bat

バッチファイルでよくある「UTF‑8のテキストが文字化けする」「UTF‑16のファイルを for /f で読めない」「日本語が ??? になる」といった問題は、cmd.exeのコードページとファイルのエンコーディングの不一致が主因です。

この記事では根本原因の理解から、chcp 65001 による切り替え・PowerShell委譲・cmd /U の使い分けまで、実践例付きで体系的に解説します。

日本語の一般的な文字化けについては「バッチの文字化けを直す方法(chcp 65001・UTF-8・Shift-JIS)」も合わせてご覧ください。

スポンサーリンク
  1. 問題の本質:cmd.exeとUnicodeの歴史的制約
  2. ステップ1:現状確認(コードページとBOMを調べる)
    1. 現在のコードページを確認する
    2. ファイルのエンコーディングを確認する(BOMチェック)
  3. 原因1:バッチファイル自体をBOM付きUTF-8で保存している
  4. 原因2:コンソールのコードページとファイルのエンコーディングが不一致
    1. 解決策:chcp 65001 で一時切り替え(表示・簡易処理用途)
  5. 原因3:for /f がUTF-8/UTF-16を正しく読めない
    1. 解決策:PowerShell Get-Content でエンコーディングを明示する
  6. UTF-8・UTF-16でファイルに書き出す
    1. UTF-8 BOMなしで書き出す(推奨)
    2. UTF-8 BOM付きで書き出す
    3. UTF-16LE で書き出す
  7. cmd /U でパイプ・リダイレクトをUTF-16LEにする
  8. Unicodeファイル名(日本語・絵文字)の操作
  9. 実践例A:UTF-8 CSVをfor /fで処理してコンソールに表示
  10. 実践例B:処理ログをUTF-8 BOMなしで記録する
  11. 実践例C:Shift-JIS → UTF-8 BOMなし 変換バッチ
  12. 切り分け手順:それでも化けるときのフロー
  13. よくある落とし穴5選
    1. 落とし穴1:chcp 65001 で for /f が不安定になる
    2. 落とし穴2:PowerShell 5.x の Set-Content UTF8 はBOM付き
    3. 落とし穴3:リダイレクト > で作ったファイルはコードページ依存
    4. 落とし穴4:for /f の tokens 分割でマルチバイト文字が壊れる
    5. 落とし穴5:type コマンドのBOM自動処理の誤解
  14. よくある質問(FAQ)
  15. まとめ

問題の本質:cmd.exeとUnicodeの歴史的制約

cmd.exe(コマンドプロンプト)は歴史的に Shift-JIS(コードページ932)前提で動作しており、Unicodeネイティブではありません。

コードページ chcp番号 主な用途 cmd.exeとの相性
Shift-JIS 932 日本語Windows標準 ◎ 安定
UTF-8 65001 Web・クロスプラットフォーム △ 部分的(for /f が不安定)
UTF-16LE 1200 Windowsメモ帳・PowerShell出力 ✕ 直接読み書き不可
US-ASCII 437 英語環境 ◎ 安定(英数字のみ)

つまり UTF-8やUTF-16ファイルを扱うためには、何らかの橋渡しが必要です。

ステップ1:現状確認(コードページとBOMを調べる)

現在のコードページを確認する

:: 現在のコードページを確認
chcp

:: 出力例:
:: Active code page: 932   (Shift-JIS)
:: Active code page: 65001 (UTF-8)
:: Active code page: 1200  (UTF-16LE)

ファイルのエンコーディングを確認する(BOMチェック)

BOM(Byte Order Mark)はファイル先頭の特殊バイト列で、エンコーディングの識別子です。

エンコーディング BOM(先頭バイト) メモ帳保存時の名称
UTF-8 BOM付き EF BB BF “UTF-8 (BOM付き)”
UTF-8 BOMなし (なし) “UTF-8”
UTF-16LE FF FE “Unicode”
UTF-16BE FE FF “Unicode(big endian)”
Shift-JIS (なし) “ANSI”
@echo off
:: ファイルの先頭バイトをHEXで表示してBOMを確認
powershell -NoProfile -Command ^
  "$b=(Get-Content -Encoding Byte -ReadCount 4 -TotalCount 4 'input.txt')[0];"
  "if($b[0] -eq 0xEF -and $b[1] -eq 0xBB -and $b[2] -eq 0xBF){'UTF-8 BOM付き'}"
  "elseif($b[0] -eq 0xFF -and $b[1] -eq 0xFE){'UTF-16LE (BOM付き)'}"
  "elseif($b[0] -eq 0xFE -and $b[1] -eq 0xFF){'UTF-16BE'}"
  "else{'BOMなし(UTF-8またはSJIS)'}"

原因1:バッチファイル自体をBOM付きUTF-8で保存している

バッチファイル(.bat)を BOM付きUTF-8 で保存すると、先頭の3バイト(EF BB BF)がコマンドとして解釈されエラーになります。

:: NG: バッチファイル自体をBOM付きUTF-8で保存した場合
::   先頭3バイト(EF BB BF)がコマンドとして解釈される
::
:: エラー例:
:: '・@echo' is not recognized as an internal or external command
::
:: 対処: バッチファイルはBOMなしUTF-8 または ANSI(SJIS) で保存する
:: VSCode: 右下エンコーディング表示 → "UTF-8"(BOMなし)を選択
:: メモ帳: [名前を付けて保存] → 文字コード "UTF-8"(BOMなし)

バッチファイルの保存形式:.bat 本文は BOMなしUTF-8 または ANSI(Shift-JIS)で保存します。日本語文字列は外部ファイルやPowerShellから読み込む設計が安全です。

原因2:コンソールのコードページとファイルのエンコーディングが不一致

コンソールが CP932(SJIS)のまま UTF-8 ファイルを typeecho で表示しようとすると文字化けします。

解決策:chcp 65001 で一時切り替え(表示・簡易処理用途)

表示が目的であれば chcp 65001 に切り替えてから処理し、最後に元に戻すのが最小限の対処です。

@echo off
setlocal

:: 元のコードページを保存
for /f "tokens=2 delims=: " %%A in ('chcp ^| find ":"') do set "_orig=%%A"

:: UTF-8 モードに切り替え
chcp 65001 >nul

:: UTF-8 テキストを表示
type utf8_file.txt

:: 元のコードページに戻す
chcp %_orig% >nul
endlocal

注意:chcp 65001 は for /f による行読み込みが不安定になることがあります(日本語文字の欠落・空行化)。ファイルの行処理にはPowerShell委譲を使ってください。

原因3:for /f がUTF-8/UTF-16を正しく読めない

for /f は内部的にANSIを前提としており、UTF-8/UTF-16の行を正確に読み取れません。

for /f の基本は「外部コマンドの結果を変数に格納する方法(for /f 活用)」を参照してください。

解決策:PowerShell Get-Content でエンコーディングを明示する

UTF-8 ファイルを行処理する:

@echo off
setlocal

:: PowerShell経由でUTF-8ファイルを1行ずつ処理
for /f "usebackq delims=" %%L in (`
  powershell -NoProfile -Command
  "Get-Content -Encoding UTF8 -Path 'C:\data\utf8.txt'"
`) do (
  echo 行: %%L
)
endlocal

UTF-16LE(Windowsメモ帳「Unicode」)ファイルを行処理する:

@echo off
setlocal

:: PowerShell経由でUTF-16LE(Unicodeメモ帳保存)ファイルを処理
for /f "usebackq delims=" %%L in (`
  powershell -NoProfile -Command
  "Get-Content -Encoding Unicode -Path 'C:\data\utf16.txt'"
`) do (
  echo 行: %%L
)
endlocal

PowerShellのバッチからの呼び出し方詳細は「PowerShellをバッチファイルから呼び出す方法」も参照してください。

UTF-8・UTF-16でファイルに書き出す

> リダイレクトで作られるファイルの文字コードは現在のコードページに依存します。確実に特定エンコーディングで保存するにはPowerShellを使います。

リダイレクトの詳細は「リダイレクト(> >> 2>&1)の使い方完全ガイド」も参照してください。

UTF-8 BOMなしで書き出す(推奨)

@echo off
setlocal
set "MSG=UTF-8 BOMなし書き出しのテスト"

:: PowerShell 5.x: UTF8はBOM付きになるため、.NET直接指定でBOMなし
powershell -NoProfile -Command ^
  "[IO.File]::WriteAllText('out-utf8nobom.txt', $env:MSG,
   [Text.Encoding]::UTF8)"

:: PowerShell 7+: UTF8NoBOM を使える
:: pwsh -NoProfile -Command "Set-Content -Path out.txt -Value $env:MSG -Encoding UTF8NoBOM"
endlocal

UTF-8 BOM付きで書き出す

@echo off
setlocal
set "MSG=UTF-8 BOM付き書き出しのテスト"

:: PowerShell 5.x: Set-Content の UTF8 は BOM付き
powershell -NoProfile -Command ^
  "Set-Content -Path 'out-utf8bom.txt' -Value $env:MSG -Encoding UTF8"
endlocal

UTF-16LE で書き出す

@echo off
setlocal
set "MSG=UTF-16LE 書き出しのテスト"

powershell -NoProfile -Command ^
  "Set-Content -Path 'out-utf16.txt' -Value $env:MSG -Encoding Unicode"
endlocal
方法 エンコーディング BOM PowerShell 5.x PowerShell 7+
Set-Content UTF8 UTF-8 付き BOM付き BOMなし
WriteAllText UTF8 UTF-8 なし BOMなし BOMなし
WriteAllLines UTF8 UTF-8 なし BOMなし(末尾改行あり) BOMなし
Set-Content Unicode UTF-16LE 付き BOM付き BOM付き

cmd /U でパイプ・リダイレクトをUTF-16LEにする

cmd /U を使うと、パイプとリダイレクトのストリームが UTF-16LE になります。

:: cmd /U: パイプとリダイレクトのストリームをUTF-16LEにする
cmd /U /C "dir C:\data\ > C:\work\dir-utf16.txt"

:: UTF-16LE出力をPowerShellで読む
powershell -NoProfile -Command "Get-Content -Encoding Unicode 'C:\work\dir-utf16.txt'"

注意:cmd /U は工程全体でUTF-16LEを扱える場合に限定します。下流がANSIを期待していると別の文字化けを招きます。

Unicodeファイル名(日本語・絵文字)の操作

ファイル名が非ASCIIの場合も扱いに注意が必要です。

@echo off
setlocal

:: 日本語ファイル名はダブルクォートで囲めば通常操作可能
copy "C:\data\日本語レポート.txt" "D:\backup\日本語レポート.txt"

:: 絵文字・異体字などはcmd.exeで扱えない場合がある
:: → PowerShell -LiteralPath を使う(ワイルドカード展開なし)
powershell -NoProfile -Command ^
  "Copy-Item -LiteralPath 'C:\data\レポート2024.txt' ^
   -Destination 'D:\backup\レポート2024.txt' -Force"

endlocal

実践例A:UTF-8 CSVをfor /fで処理してコンソールに表示

ヘッダー行をスキップしてデータ行をカンマ区切りで処理する例です。

@echo off
setlocal

:: UTF-8 CSVを読んでコンソール(SJIS)に表示
:: ヘッダー行をスキップしてデータ行だけ処理

set "SKIP=1"
for /f "usebackq skip=1 tokens=1,2,3 delims=," %%A in (`
  powershell -NoProfile -Command
  "Get-Content -Encoding UTF8 -Path 'C:\data\sales.csv'"
`) do (
  echo 日付: %%A  商品: %%B  金額: %%C
)

endlocal

実践例B:処理ログをUTF-8 BOMなしで記録する

日時付きログをUTF-8 BOMなしで追記するサブルーチンパターンです。Excelや他ツールとの連携に適しています。

@echo off
setlocal

set "LOGFILE=C:\logs\process_%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%.log"

call :log 処理開始

:: --- ここに処理を記述 ---
call :log ファイルのコピー完了
:: -------------------------

call :log 処理終了
echo ログ: %LOGFILE%
endlocal
goto :EOF

:log
:: UTF-8 BOMなしで追記(.NETのStreamWriter使用)
powershell -NoProfile -Command ^
  "$w=New-Object IO.StreamWriter('%LOGFILE%',$true,[Text.Encoding]::UTF8);"
  "$w.WriteLine('[%DATE% %TIME%] %1');$w.Close()"
goto :EOF

実践例C:Shift-JIS → UTF-8 BOMなし 変換バッチ

既存のSJISファイルをUTF-8 BOMなしに一括変換します。Web公開やGit管理前の前処理として使えます。

@echo off
setlocal

set "SRC=C:\data\sjis_file.txt"
set "DST=C:\data\utf8_file.txt"

:: Shift-JIS (cp932) から UTF-8 BOMなしへ変換
powershell -NoProfile -Command ^
  "$content = Get-Content -Encoding Default -Path '%SRC%';"
  "[IO.File]::WriteAllLines('%DST%', $content, [Text.Encoding]::UTF8)"

if errorlevel 1 (
    echo [ERROR] 変換に失敗しました
    exit /b 1
)
echo 変換完了: %SRC% -^> %DST%
endlocal

切り分け手順:それでも化けるときのフロー

  1. chcp でコンソールのコードページを確認する
  2. 対象ファイルのBOM・エンコーディングをエディタまたはPowerShellで確認する
  3. type でファイルを直接表示し、コンソールの問題か中身の問題かを切り分ける
  4. PowerShell の Get-Content -Encoding UTF8 で正しく読めるか確認する
  5. 正しく読めるなら → cmd.exe の限界。PowerShell委譲に切り替える
  6. PowerShellでも化けるなら → ファイル自体のエンコーディングが想定と異なる

日本語文字化けの詳細な切り分けは「バッチファイルで日本語が文字化けする原因と解決策」も参照してください。

よくある落とし穴5選

落とし穴1:chcp 65001 で for /f が不安定になる

UTF-8モードでの for /f は日本語が欠落したり空行になることがあります。

:: NG: chcp 65001 のまま for /f を使うと文字が欠落することがある
chcp 65001 >nul
for /f "delims=" %%L in (utf8.txt) do echo %%L
:: → 日本語が欠けたり空行になることがある

:: OK: PowerShell経由でエンコーディングを明示して読む
for /f "usebackq delims=" %%L in (`
  powershell -NoProfile -Command
  "Get-Content -Encoding UTF8 -Path 'utf8.txt'"
`) do echo %%L

落とし穴2:PowerShell 5.x の Set-Content UTF8 はBOM付き

WindowsデフォルトのPowerShell 5.xでは、UTF8指定はBOM付きになります。ExcelやWebサーバーとの連携でトラブルになることがあります。

:: NG: PowerShell 5.x の UTF8 は BOM付き(Excel等が誤認することがある)
powershell -NoProfile -Command "Set-Content -Path out.txt -Value $env:MSG -Encoding UTF8"
:: → EF BB BF (BOM) が先頭に付く

:: OK: .NET の StreamWriter または WriteAllText を使う
powershell -NoProfile -Command ^
  "[IO.File]::WriteAllText('out.txt', $env:MSG, [Text.Encoding]::UTF8)"
:: → BOMなし UTF-8

落とし穴3:リダイレクト > で作ったファイルはコードページ依存

echo 日本語 > file.txt で作ったファイルはコンソールのコードページ(通常SJIS)で保存されます。

:: NG: リダイレクト出力のコードページはコンソールに依存
@echo off
echo 日本語テキスト > output.txt
:: → chcp 932(SJIS)環境では SJIS で保存される

:: OK: PowerShell で明示的に書き出す
powershell -NoProfile -Command ^
  "[IO.File]::WriteAllText('output.txt', '日本語テキスト', [Text.Encoding]::UTF8)"

落とし穴4:for /f の tokens 分割でマルチバイト文字が壊れる

UTF-8の日本語文字(2〜3バイト)の区切り文字誤認識でフィールドが壊れることがあります。

:: NG: tokens= でフィールド分割するとUTF-8の2バイト目が, に見えることがある
for /f "tokens=1,2 delims=," %%A in (utf8_csv.txt) do echo %%A %%B

:: OK: PowerShell で分割してから渡す
for /f "usebackq delims=" %%L in (`
  powershell -NoProfile -Command
  "Import-Csv -Encoding UTF8 -Path 'utf8_csv.txt' | ForEach-Object { $_.Name + ',' + $_.Value }"
`) do echo %%L

落とし穴5:type コマンドのBOM自動処理の誤解

type はBOM付きUTF-16LEを自動変換しますが、BOMなしUTF-8はchcp 65001切り替えが必要です。

:: Windows の type コマンドはBOM付きUTF-16LEファイルを自動変換して表示できる
:: しかしBOMなしUTF-8では化ける
::
:: BOM付きUTF-16LE → type で正しく表示される
type utf16le_bom.txt

:: BOMなしUTF-8 → chcp 65001 に切り替えてから type
chcp 65001 >nul
type utf8_nobom.txt
chcp 932 >nul

よくある質問(FAQ)

Q. バッチファイル自体をUTF-8で保存するとエラーになります。なぜですか?
A. BOM付きUTF-8で保存すると、先頭3バイト(EF BB BF)がコマンドとして解釈されエラーになります。バッチファイルは BOMなしUTF-8 または ANSI(Shift-JIS)で保存してください。VSCodeなら右下のエンコーディング表示から「UTF-8」(BOMなし)を選択できます。
Q. chcp 65001 に切り替えると for /f の出力がおかしくなります。
A. chcp 65001 環境での for /f は不安定で、日本語が欠落することがあります。PowerShell の Get-Content -Encoding UTF8 経由で読み込む方法に切り替えてください。
Q. Windowsメモ帳で保存した「Unicode」ファイルは何エンコーディングですか?
A. Windows 10 以前のメモ帳の「Unicode」は UTF-16LE(BOM付き)です。Windows 10以降のメモ帳では「Unicode」が UTF-16LE、「UTF-8」がBOM付きUTF-8を指します。PowerShellで読む場合は -Encoding Unicode を指定します。
Q. UTF-8 BOMなしのファイルをバッチで出力するにはどうすればよいですか?
A. PowerShellの [IO.File]::WriteAllText() または [IO.File]::WriteAllLines()[Text.Encoding]::UTF8 を指定します。Set-Content -Encoding UTF8(PowerShell 5.x)はBOM付きになるため注意してください。
Q. PowerShell 5.x と PowerShell 7 でUTF-8の扱いが違うのですか?
A. はい、異なります。PowerShell 5.x(Windows組み込み)では Set-Content -Encoding UTF8 がBOM付きUTF-8を出力しますが、PowerShell 7+(pwsh)の既定はBOMなしUTF-8です。移植性のためには明示的に指定することを推奨します。
Q. Shift-JISのファイルを一括でUTF-8に変換する方法はありますか?
A. 実践例C(本記事)のように、PowerShellの Get-Content -Encoding Default(SJIS読み込み)と [IO.File]::WriteAllText()(UTF-8 BOMなし書き出し)を組み合わせて変換できます。フォルダ内のファイルを一括変換する場合は Get-ChildItem と組み合わせてください。

まとめ

バッチでUnicodeファイルを扱う際の指針をまとめます。

用途 推奨方法
UTF-8ファイルを表示する chcp 65001type(表示のみ)
UTF-8ファイルを行処理する PowerShell Get-Content -Encoding UTF8 + for /f
UTF-16LEファイルを行処理する PowerShell Get-Content -Encoding Unicode + for /f
UTF-8 BOMなしで書き出す [IO.File]::WriteAllText()(.NET直接)
UTF-8 BOM付きで書き出す PowerShell 5.x: Set-Content -Encoding UTF8
SJIS→UTF-8変換 PowerShell Get-Content -Encoding Default + WriteAllText
バッチファイルの保存形式 BOMなしUTF-8 または ANSI(SJIS)
Unicodeファイル名を操作する PowerShell -LiteralPath を使う

cmd.exeは歴史的な制約でUnicodeネイティブではありませんが、PowerShellへの委譲でほぼすべての問題を解決できます。

chcp・文字化けの全般については「バッチの文字化けを直す方法(chcp 65001・UTF-8・Shift-JIS)」、日本語固有の問題は「バッチで日本語が文字化けする原因と解決策」も合わせてご覧ください。