テキスト(UTF-8 BOM)ファイル形式
バイトオーダーマーク付きの UTF-8 でエンコードされたプレーンテキスト。
- MIME タイプ
- text/plain
- 拡張子
- .txt
- カテゴリ
- テキスト・マークアップ
マジックナンバー(ファイル署名)
| 0x00000000 | EFBBBF | ··· | UTF-8 BOM |
テキスト(UTF-8 BOM)形式について
バイトオーダーマークとは、ファイルの一番先頭に置かれた U+FEFF という文字のことです。UTF-16 では名前どおりの働きをします。FF FE と FE FF は区別できて、読む側もどちら向きなのかを本当に知る必要があるからです。ところが UTF-8 には印を付けるべきバイト順がありません。EF BB BF の 3 バイトは「このファイルは UTF-8 である」と示すためだけに存在し、Unicode の標準はこれを要求も推奨もしていません。それでも Windows のエディタが付けるのは、付けなければ Windows ではシステムのコードページとして読まれてしまうからでした。その結果生まれたのが、Windows と Unix のツール群のあいだにある、決着を試みるたびに生き延びてきた低レベルの摩擦です。
ヘッダーの構造(バイト単位)
| オフセット | 長さ | フィールド | 意味 |
|---|---|---|---|
| 0 | 3 | UTF-8 byte-order mark | EF BB BF、U+FEFF を UTF-8 で符号化したもので、この文字の Unicode 上の名前は zero width no-break space です。この前には何も置けません。オフセット 0 に無い BOM はもはや BOM ではなく、テキストの途中に居座る見えない文字にすぎません。 |
| 0 | 1 | Lead byte EF | 2 進では 1110 1111。上位 3 ビットが 3 バイト列の始まりを示していて、これこそが BOM が UTF-8 では 3 バイト、UTF-16 では 2 バイトで済む理由です。文字そのものは変わらず、符号化が長いだけです。 |
| 1 | 2 | Continuation bytes BB BF | 10 111011 と 10 111111。それぞれ上位 2 ビットを外して残りを先頭バイトの 4 ビットのペイロードにつなげると 1111 1110 1111 1111、つまり U+FEFF になります。一度手で計算してみるのが、この印を意味不明なマジックナンバー扱いするのをやめる一番早い方法です。 |
| 3 | variable | Start of the text | ファイルが本来運びたいものは、ここから始まります。シェバン行、XML 宣言、JSON の開き波括弧、CSV のヘッダ行、PHP の開始タグ。どれもが 3 バイトぶん遅れて始まることになり、この形式が引き起こす問題はすべて、その 1 つの事実の帰結です。 |
| 3 | 3 | A second byte-order mark | EF BB BF EF BB BF は、わざわざ確認する価値があるくらいよく起きます。BOM を付けるエディタが、すでに BOM のあるファイルを保存したとき、そしてファイルを連結したときです。後者では 2 つ目のファイルの BOM が結果の真ん中に埋もれ、誰の目にも触れない場所に残ります。 |
似た形式との見分け方
| 紛らわしい形式 | 見分け方 |
|---|---|
| XML | そもそも選択肢の関係ではありません。BOM は接頭辞であり、その下にあるファイルは元のままです。変わるのは、その形式自身の判別が最初に 3 バイト飛ばさなければならなくなることだけ。XML はこの問題に正面から向き合っている数少ない標準で、宣言の前にバイトオーダーマークを置くことは許し、それ以外は何も許しません。つまりあの 3 バイトは合法で、空白 1 つは違法です。 |
| UTF-16 text | 3 バイトではなく 2 バイトで、リトルエンディアンなら FF FE、ビッグエンディアンなら FE FF。もっとも、見分けるのに BOM が要ることはめったにありません。UTF-16 のテキストは hex ダンプで一目瞭然だからです。ASCII の文字ごとに 00 が隣に付くので、英語の文書なら右の列に文字とゼロバイトが交互に延々と並びます。 |
| UTF-32 text | ビッグエンディアンなら 00 00 FE FF、リトルエンディアンなら FF FE 00 00。後者は本当に曖昧です。同じ 4 バイトは「UTF-16 リトルエンディアンの BOM に NUL 文字が続いたもの」とも読めて、どちらの解釈を採るかは、その後ろに何が来るかを見るしかありません。 |
| Windows-1252 text | 同じバイト列を、間違った前提で読んだ姿です。UTF-8 の BOM を CP1252 や Latin-1 としてデコードすると、文書の先頭に見える文字が 3 つ現れます。ウムラウト付きの i、右向きの二重山括弧、逆さの疑問符。これが見えたときは、ファイルのほうは無事で、読み手が間違っています。見た目の印象とは正反対です。 |
よくある壊れ方
- スクリプトの先頭に付いた BOM。カーネルはインタプリタを見つけるためにオフセット 0 の 23 21 を探し、そこに EF BB BF を見つけます。スクリプトは「bad interpreter」や exec format error で失敗しますが、そのメッセージは BOM にも 1 行目にも触れません。しかもエディタではどう見ても完璧なファイルに見えます。
- PHP の include に付いた BOM。3 バイトは開始タグの外側にあるので出力として数えられ、次にヘッダを設定しようとした呼び出しが headers-already-sent エラーで落ちます。しかも名指しされるのは、1 行目の上に何も無さそうに見えるファイルです。
- CSV に付いた BOM。1 列目の名前が「BOM + 本来の文字列」になるので、ヘッダ名での照合が外れる一方、画面上のファイルは完璧に見えます。Excel はこれを意図的に書きます(Excel が UTF-8 の CSV を認識する手段がこれです)。だから、まさに意図どおりにエクスポートされたファイルで一番よく起きます。
- JSON の先頭に付いた BOM。仕様は「実装は付けてはならない、パーサーは無視してよい」と言っていますが、Node やブラウザの背後にある JavaScript エンジンはそのどちらもせず、unexpected token エラーを投げます。しかも名指しされる文字は、どのエディタも表示せず、カーソルも乗りません。
- 途中に紛れ込んだ BOM。cat でファイルを連結したり、テキストを追記するコードを通したりすると、2 つ目のファイルの BOM が結果の中に幅ゼロの文字として埋め込まれます。diff を生き延び、エディタでは見えず、通るはずの文字列比較を、見た目には何の理由もなく失敗させます。
この形式の現在地
この印は消えませんし、標準になることもありません。Windows のメモ帳、古い PowerShell の出力コマンドレット、Visual Studio、Excel は今もこれを書きますし、Unix 生まれのものはほぼすべて、これが付いたファイルを少し壊れたものとして扱います。現場で落ち着いた運用は「基本は BOM なしの UTF-8 で書く」、ただし例外が 1 つ。Excel で開く CSV です。ここでは、Excel がシステムのコードページを勝手に推測してアクセント付きの名前を全部壊すのを確実に止められる手段が、この印しかありません。エディタはこの選択を 2 つの別々のエンコーディングとして提示し、たいていは UTF-8 と UTF-8 with BOM という名前が付いています。両方が混ざったプロジェクトは、いずれ「変わったように見えて実は変わっていない行」の diff を生みます。一番見落とされがちなのは、この印がメタデータではなく中身だということ。Git は保存しますし、ハッシュの対象になりますし、バイト数にも含まれます。BOM のあるファイルと無いファイルは、どんなツールのどんな尺度で測っても別のファイルです。
テキスト(UTF-8 BOM)ファイルをこのサイトで開く
どれもブラウザ内で動きます。ファイルが端末から出ることはありません。
関連する形式
テキスト(UTF-8 BOM)と混同しやすい形式です。先頭バイトを見比べてください。