文字コード入門 - UTF-8と文字化けの仕組み
この記事で解決できること
- 文字コードと UTF-8 が何を指すのか、用語の関係を整理できる
- 「文字化け(mojibake)」が なぜ起きるのか を仕組みから理解できる
- Linux で文字コードを 確認・変換する型(
file/iconv/nkf/locale)が身につく
結論(先に要点)
- 文字コード=「文字」と「バイト列」の対応表。符号化文字集合(Unicode) と 符号化方式(UTF-8) は別物
- UTF-8 は ASCII 互換・可変長(1〜4バイト)で、現在の Web/Linux の事実上の標準
- 文字化けは 書いたときと読むときの符号化方式が食い違う ときに起きる
- 確認は
file -i/nkf -g、変換はiconvが基本の型
文字コードとは?
結論: 文字コードは「文字」と「バイト列」を対応づける規則。コンピュータはバイトしか扱えないため、文字を保存・送信するには必ず符号化が必要になる。
コンピュータが内部で扱えるのはバイト(0〜255 の数値)だけ。「あ」や「A」という文字そのものは保存できない。そこで「どの文字をどのバイト列で表すか」を決めた対応表が必要になる。これが文字コード。
ここで混同しやすい 2 つの層を分けて理解すると、文字化けの理屈が一気に明確になる。
| 層 | 役割 | 例 |
|---|---|---|
| 符号化文字集合(character set) | 文字に一意の番号(コードポイント)を割り当てる | Unicode、JIS X 0208 |
| 符号化方式(encoding) | コードポイントを実際のバイト列に変換する規則 | UTF-8、UTF-16、Shift_JIS、EUC-JP |
例えば「あ」は Unicode で U+3042 という番号を持つ。この U+3042 をどんなバイト列にするかは符号化方式によって変わる。
文字「あ」 = Unicode コードポイント U+3042 UTF-8 では: E3 81 82 (3バイト) UTF-16 では: 30 42 (2バイト) EUC-JP では: A4 A2 (2バイト)
ポイント: 同じ「あ」でも符号化方式が違えばバイト列は別物になる。だから「どの方式で書かれたか」を取り違えると、正しく文字に戻せなくなる。これが文字化けの正体。
UTF-8 とは?なぜ主流なのか
結論: UTF-8 は Unicode をバイト列にする符号化方式の一つ。ASCII 互換・可変長・バイト順問題なしという特性から、Web と Linux の事実上の標準になっている。
UTF-8 は Unicode の全文字を**可変長(1〜4 バイト)**で表現する符号化方式。主流になった理由は次の特性にある。
- ASCII 互換:
U+0000〜U+007F(英数字・記号)は 1 バイトで、ASCII とまったく同じ並び。既存の英語前提ツールがそのまま動く - 可変長で無駄が少ない: 英数字は 1 バイト、日本語などは 2〜3 バイト。固定長より省容量になりやすい
- バイト順(エンディアン)問題がない: UTF-16 と違い BOM(バイト順マーク)に依存しない
- 自己同期性: 各バイトの先頭ビットで「先頭バイトか継続バイトか」が判別でき、途中から読んでも文字境界を復元しやすい
# 文字列のバイト数を確認(UTF-8 環境) echo -n "あ" | wc -c
3
UTF-8 と Unicode は同義ではない。Unicode は「文字に番号を振る規格」、UTF-8 は「その番号をバイトにする方式の一つ」。「UTF-8 で保存」は正しいが、「Unicode で保存」は本来あいまいな言い方になる。
BOM 付き UTF-8 の注意
UTF-8 の先頭に BOM(EF BB BF の 3 バイト)が付くことがある。Windows のメモ帳などが付与するケースがあり、シェルスクリプトの先頭に紛れ込むと #!/bin/bash が認識されず実行エラーになる。Linux では BOM なし UTF-8 が基本。
文字化け(mojibake)はなぜ起きるのか?
結論: 文字化けは、書いたときの符号化方式と読むときの符号化方式が食い違うことで起きる。バイト列は壊れておらず、解釈のルールがずれているだけ。
文字化け(海外でも mojibake で通じる)は、バイト列を間違った符号化方式で解釈すると発生する。典型例は次の 3 つ。
- Shift_JIS で書いたファイルを UTF-8 として開く(逆も同様)
- UTF-8 のファイルを Latin-1(ISO-8859-1)として開く(「é」のような並びになる)
- 端末のロケールがファイルの符号化と一致していない
重要なのは、元のバイト列は壊れていないという点。解釈の規則を正しく合わせれば、多くの場合は元に戻せる。
正しい: こんにちは (UTF-8 のバイト列を UTF-8 で解釈) 化ける: ã“ã‚“ã«ã¡ã¯ (UTF-8 のバイト列を Latin-1 で解釈)
文字化けを見たら「ファイルが壊れた」と慌てる前に、**「何の方式で書かれ、何の方式で読まれているか」**を切り分けるのが先決。原因の大半はこの不一致。
文字コードを確認・変換するには?
結論: 確認は
file -iかnkf -g、変換はiconvが基本の型。端末側の表示問題はlocaleとLANGを疑う。
ファイルの符号化を推定する
# MIME 形式で文字コードを表示 file -i notes.txt
notes.txt: text/plain; charset=utf-8
file はあくまで推定であり、短いファイルや曖昧なバイト列では unknown-8bit などになることもある。日本語の判定には nkf -g(guess)が有効。
# 日本語エンコーディングを判定(nkf は要インストール) nkf -g legacy.txt
Shift_JIS
符号化方式を変換する
iconv は「変換元(-f)」と「変換先(-t)」を指定して変換する。
# Shift_JIS から UTF-8 へ変換して保存 iconv -f SHIFT_JIS -t UTF-8 legacy.txt -o utf8.txt
# 利用可能な符号化方式の一覧を確認 iconv -l
変換元の指定を間違えると、化けたまま別の方式に変換され復旧が難しくなることがある。変換前に file -i / nkf -g で必ず元の符号化を確認し、元ファイルは残しておくこと。
端末・ロケールの確認
ファイル自体は UTF-8 なのに端末で化ける場合、ロケール設定が原因のことが多い。
# 現在のロケールを確認 locale
LANG=en_US.UTF-8 LC_CTYPE="en_US.UTF-8" ...
LANG や LC_CTYPE が *.UTF-8 になっていれば端末は UTF-8 として表示する。C や POSIX だと日本語が化けることがある。一時的に切り替えるには次のようにする。
export LANG=en_US.UTF-8
よくあるトラブルと対処
結論: 「ファイルは正しいか」「端末は正しいか」を分けて切り分ける。多くは符号化の不一致かロケール設定で説明できる。
| 症状 | 主な原因 | 対処 |
|---|---|---|
| 開いたテキストが全部化ける | 符号化方式の取り違え | file -i で確認 → iconv で変換 |
| 端末でだけ日本語が化ける | ロケールが UTF-8 でない | locale 確認 → LANG=...UTF-8 |
| スクリプト先頭でエラー | BOM 付き UTF-8 | BOM を除去して保存し直す |
| ファイル名が化ける | 作成時と別の符号化で表示 | ロケールを合わせる/convmv で変換 |
実践のコツ: 迷ったらまず file -i。これで「ファイル側の問題」か「端末側の問題」かをほぼ切り分けられる。新規ファイルは常に BOM なし UTF-8 で作るのが最も事故が少ない。
ブラウザ上の仮想ターミナルで echo や wc -c を実際に試したい場合は、学習ターミナル で手を動かしながら確認できる。