17
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

BOMを制する者はUTF-8を制す! Excel文字化け・謎の先頭3バイト・CSVが壊れる全ての元凶を完全に理解する

Posted at

はじめに

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は以下の優先順位でエンコーディングを判定します。

  1. BOMがあれば、それに従う
  2. BOMがなければ、システムのデフォルト(日本語環境ならShift_JIS) と解釈

つまり、BOMなしUTF-8は「Shift_JISだろう」と誤認されるのです。

◆ 解決策:BOMを付ける

EF BB BF 名前,金額
田中,1000

これでExcelは「UTF-8だ」と認識し、正しく表示します。

……しかし、これが別の地獄を生みます。

3. BOMを付けると発生する"別の地獄"

BOMはExcelを救いますが、他のツールを壊します

(1) プログラムで読み込むと先頭に謎の文字

C#
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付き)

C#
// BOM付きUTF-8
using var writer = new StreamWriter("output.csv", false, new UTF8Encoding(true));
writer.WriteLine("名前,金額");
writer.WriteLine("田中,1000");

new UTF8Encoding(true)trueBOMを付ける 指定です。

✔ C# でCSV出力(他システム連携用にBOMなし)

C#
// BOMなしUTF-8
using var writer = new StreamWriter("output.csv", false, new UTF8Encoding(false));

✔ C# でBOM付きファイルを読み込む(BOMをスキップ)

C#
var text = File.ReadAllText("data.csv");
if (text.Length > 0 && text[0] == '\uFEFF')
{
    text = text.Substring(1); // BOMを除去
}

または、StreamReaderデフォルトでBOMを自動検出して除去します。

C#
using var reader = new StreamReader("data.csv", Encoding.UTF8, detectEncodingFromByteOrderMarks: true);

✔ Python でCSV出力(Excel用)

python
with open('output.csv', 'w', encoding='utf-8-sig') as f:
    f.write('名前,金額\n')

utf-8-sigBOM付きUTF-8 です。

✔ Python でBOMなし出力

python
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を意識してファイルを出力する側のエンジニアになりましょう!

  1. 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

  2. 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

  3. 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/

17
6
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?