はじめに
UTF-8とExcelは、Windows開発者にとってもっとも信頼できない組み合わせです。
- 「CSVをExcelで開いたら文字化けした」
- 「プログラムで出力したCSVが、Excelでだけ壊れる」
- 「BOM付きにしたら今度は別のツールでエラー」
- 「先頭に謎の空白文字が入ってる」
これらすべて、犯人は BOM(Byte Order Mark) です 1。
今回は、表に出にくい「UTF-8とBOMの地獄」を歴史と仕様の両面から完全に言語化し、どう書けば事故らないかを徹底的にまとめます。
1. BOMとは何か? ― 3バイトの呪い
BOM(Byte Order Mark)は、テキストファイルの先頭に置かれる「これはUnicodeですよ!」という目印です。
| エンコーディング | BOMのバイト列 |
|---|---|
| UTF-8 | EF BB BF |
| UTF-16 LE | FF FE |
| UTF-16 BE | FE FF |
元々はUTF-16のバイト順(リトルエンディアン/ビッグエンディアン)を判別するために生まれました。
問題 : UTF-8にバイト順は存在しない
UTF-8は1バイト単位で処理するため、バイトオーダーの概念がありません2。
つまり、UTF-8のBOMは本来不要です。
しかし、Microsoftが「UTF-8の識別用」としてBOMを採用した Microsoftアプリが広く採用した結果、事実上の慣習となったことで、地獄が始まりました。
2. ExcelがCSV地獄を生む構造
ExcelはBOMがないUTF-8を認識できません(古いバージョンほど顕著)。
◆ BOMなしUTF-8のCSVをExcelで開くと……
名前,金額
田中,1000
鈴木,2000
↓ Excelで開く
蜷咲┌,驥醍┬
逕ー荳ュ,1000
驥ス譛ャ,2000
Shift_JIS として解釈されて文字化けします。
◆ なぜExcelはUTF-8を認識しないのか?
Excelは以下の優先順位でエンコーディングを判定します。
- BOMがあれば、それに従う
- BOMがなければ、システムのデフォルト(日本語環境ならShift_JIS) と解釈
つまり、BOMなしUTF-8は「Shift_JISだろう」と誤認されるのです。
◆ 解決策:BOMを付ける
EF BB BF 名前,金額
田中,1000
これでExcelは「UTF-8だ」と認識し、正しく表示します。
……しかし、これが別の地獄を生みます。
3. BOMを付けると発生する"別の地獄"
BOMはExcelを救いますが、他のツールを壊します。
(1) プログラムで読み込むと先頭に謎の文字
var lines = File.ReadAllLines("data.csv");
Console.WriteLine(lines[0]); // "名前" のはずが……
【出力】
名前
先頭に 目に見えないゴミ(U+FEFF) が入っています。
文字列比較で一致しない、JSONパースが壊れる、などの原因になります。
(2) シェルスクリプトでエラー
#!/bin/bash
このシェバンの前に BOM や Windows 由来の制御文字(CRLF)があると...
/bin/bash^M: bad interpreter
LinuxはBOMを「ファイルの一部」として認識するため、スクリプトが実行できません。
(3) CSVをプログラムで結合すると BOM が増殖
EF BB BF 名前,金額
田中,1000
EF BB BF 名前,金額 ← 結合したファイルの先頭
鈴木,2000
ファイル結合時にBOMが残ると、途中にBOMが挟まるという最悪の事態に。
(4) git diff で差分が出る
同じ内容でもBOMの有無だけで差分が発生。
チーム開発で「誰だこれ変えたの」案件に。
4. 言語・ツール別 BOM対策まとめ
とりあえず、まとめてみます。
✔ C# でCSV出力(Excel用にBOM付き)
// BOM付きUTF-8
using var writer = new StreamWriter("output.csv", false, new UTF8Encoding(true));
writer.WriteLine("名前,金額");
writer.WriteLine("田中,1000");
new UTF8Encoding(true) の true が BOMを付ける 指定です。
✔ C# でCSV出力(他システム連携用にBOMなし)
// BOMなしUTF-8
using var writer = new StreamWriter("output.csv", false, new UTF8Encoding(false));
✔ C# でBOM付きファイルを読み込む(BOMをスキップ)
var text = File.ReadAllText("data.csv");
if (text.Length > 0 && text[0] == '\uFEFF')
{
text = text.Substring(1); // BOMを除去
}
または、StreamReader はデフォルトでBOMを自動検出して除去します。
using var reader = new StreamReader("data.csv", Encoding.UTF8, detectEncodingFromByteOrderMarks: true);
✔ Python でCSV出力(Excel用)
with open('output.csv', 'w', encoding='utf-8-sig') as f:
f.write('名前,金額\n')
utf-8-sig が BOM付きUTF-8 です。
✔ Python でBOMなし出力
with open('output.csv', 'w', encoding='utf-8') as f:
f.write('名前,金額\n')
✔ PowerShell の罠
"テスト" | Out-File -Encoding utf8 output.txt
PowerShell 5.x の -Encoding utf8 はBOM付きです。
BOMなしにするには...
[System.IO.File]::WriteAllText("output.txt", "テスト", [System.Text.UTF8Encoding]::new($false))
PowerShell 7以降は -Encoding utf8NoBOM が使えます。
5. 結局どうすればいいのか? ― 判断フローチャート
CSVを誰が開く?
│
├─► Excel(人間が開く)
│ → BOM付きUTF-8 で出力
│
├─► プログラム(API連携・バッチ処理)
│ → BOMなしUTF-8 で出力
│
└─► 両方使う
→ BOMなしで出力し、
Excelで開く人には「データ → テキストから」を案内
または Excel側でPower Query経由で読み込む
6. BOMが生まれた歴史的背景
ここを知ると「なぜこんな仕様なのか」が腑に落ちます。
◆ Unicode初期(1990年代)
- UTF-16が主流
- CPUによってバイト順が違う(Intel: リトルエンディアン、Motorola: ビッグエンディアン)
- → バイト順を示すためにBOMが必要だった
◆ UTF-8の登場
- バイト順の概念がない
- 本来BOMは不要
- しかし「Unicodeである」ことを示す目印としてMicrosoftが採用
◆ Windowsのメモ帳事件(2018年まで)
- Windows標準の「メモ帳」は、UTF-8保存時に強制的にBOMを付けていた3
- 2018年のアップデートでようやく「BOMなしUTF-8」がデフォルトに
→ Windowsエコシステム全体が「BOM付きUTF-8」を前提に作られていたため、
Excelも未だにBOMがないと認識できないのです。
7. まとめ
UTF-8の文字化け・謎のゴミ文字・Excel地獄のほとんどは、BOMの有無を意識していないことに起因します。
| やりたいこと | 正解 |
|---|---|
| ExcelでCSVを開きたい | BOM付きUTF-8 |
| プログラムで処理したい | BOMなしUTF-8 |
| Linux/Macで使いたい | BOMなしUTF-8 |
| 複数環境で使う | BOMなし + 開く側で対応 |
BOMの歴史を知り、出力先に応じて使い分けた人から、
UTF-8のストレスは確実に消えていく。
BOMを意識してファイルを出力する側のエンジニアになりましょう!
-
BOM(Byte Order Mark)は、Unicodeテキストの先頭に付く数バイトの目印で、UTF-16/UTF-32 のバイト順を示す仕組みとして用いられました。UTF-8 では本来不要ですが、Windows 環境では UTF-8 を識別するための目印として実質的に使われることになりました。
出典: 「バイト順マーク(BOM)」, Wikipedia 日本語版, https://ja.wikipedia.org/wiki/%E3%83%90%E3%82%A4%E3%83%88%E9%A0%86%E3%83%9E%E3%83%BC%E3%82%AF ↩ -
UTF-8 はバイト順の概念を持たず、BOM は付与してもよいが必須ではないことが
Unicode 公式仕様および RFC3629 で示されています。
出典: The Unicode Standard(UTF-8 の節), RFC 3629 "UTF-8, a transformation format of ISO 10646"
https://www.unicode.org/versions/latest/
https://datatracker.ietf.org/doc/html/rfc3629 ↩ -
Windows 10 のメモ帳は長らく「UTF-8 は BOM 付きのみ」でしたが、
2018年の更新で BOM なし UTF-8 を新規既定値として保存できるようになりました。
出典: "Windows 10 Notepad is Getting Better UTF-8 Encoding Support"
https://www.bleepingcomputer.com/news/microsoft/windows-10-notepad-is-getting-better-utf-8-encoding-support/ ↩