最近文字コードの勉強をしたところ、人力でもある程度は文字コードを判別できそうな気がしてきたのでやってみました。
サーバ上のコンフィグやらスクリプトやらのコメントが文字化けしたときに、さくっと文字コードを当てられたりしたらかっこいいですよね。
今回は日本語の文章が次のうちのどの文字コードか判別したいと思います。
- UTF-8
- EUC-JP
- Shift_JIS
- ISO-2022-JP
なお、この記事で言う「日本語」とは以下の文字を指すことにします。
- 全角ひらがな
- 全角カタカナ
- 漢字(JIS第1水準くらいまでをイメージ)
。
ただ実際の文章では日本語だけでなく半角英数字が混ざっていることも多いかと思うので、例文は以下にしました。
2019年5月より元号は令和<REIWA>
この文章をiconvでいろいろな文字コードに変換し、hexdumpでバイナリを読み解いていきます。
※この記事は正確には正しくない表現があるかもしれません。hexdumpを軽く見てなんとなく文字コードがわかればいいな程度に書いています。
Step0 hexdumpについて
hexdumpはバイナリデータを16進数で表示させることができるツールです。
-C
オプションをつけないと2バイトごとに順序が入れ替わって表示されるようなので注意してください。
Step1 ISO-2022-JPか?
まずは4つの文字コードの中で、最もわかりやすい気がしているISO-2022-JPから見ていきます。
この文字コードはSMTPなどで日本語をやり取りできるように1バイトのうち7ビットまでしか使われていません。
つまり、最上位の1ビットは常に0。16進数に変換すると0x80から0xFFまでは一切使用しません。
hexdumpの結果を見て、日本語の文章であるはずなのに0x7F以下の値しか登場しなければISO-2022-JPであると判断できます。
$ echo "元号は2019年5月より令和<REIWA>" | iconv -t ISO-2022-JP | hexdump -C
00000000 1b 24 42 38 35 39 66 24 4f 1b 28 42 32 30 31 39 |.$B859f$O.(B2019|
00000010 1b 24 42 47 2f 1b 28 42 35 1b 24 42 37 6e 24 68 |.$BG/.(B5.$B7n$h|
00000020 24 6a 4e 61 4f 42 1b 28 42 3c 52 45 49 57 41 3e |$jNaOB.(B<REIWA>|
00000030 0a |.|
00000031
Step2 UTF-8か?
これも比較的わかりやすい気がしています。
ただあまりメジャーではない漢字や絵文字などが含まれていると、必ずしもここに書いたとおりではないかもしれません。
UTF-8の日本語はだいたい先頭の4ビットが0xEから始まる3バイトであることが多いです。
つまり、頻繁に0xE? ?? ??が何度も繰り返し登場してきたときはUTF-8の可能性が高いと言えるでしょう。
また、UTF-8はASCIIコードと互換性があるので半角英数字は必ず最上位ビットが0の1バイトで表現されます。
したがって日本語の文章の中に半角英数字が混ざっていれば0x7F以下のバイトも登場します。
$ echo "元号は2019年5月より令和<REIWA>" | iconv -t UTF-8 | hexdump -C
00000000 e5 85 83 e5 8f b7 e3 81 af 32 30 31 39 e5 b9 b4 |.........2019...|
00000010 35 e6 9c 88 e3 82 88 e3 82 8a e4 bb a4 e5 92 8c |5...............|
00000020 3c 52 45 49 57 41 3e 0a |<REIWA>.|
00000028
Step3 EUC-JPか?
EUC-JPでは、日本語は最上位ビットが常に1である2バイトのコートで表されます。ただし諸事象あって0x80から0xA0までは使用されません。
したがって先頭の文字が0xA以上のバイトが偶数個並んでいればEUC-JPの可能性があります。
※ただしまれに0x8Eか0x8Fが登場する場合があります。
またEUC-JPもASCIIコードと互換性があるので、半角英数字が文章に混ざっていれば、そこだけは0x7F以下になります。
$ echo "元号は2019年5月より令和<REIWA>" | iconv -t EUC-JP | hexdump -C
00000000 b8 b5 b9 e6 a4 cf 32 30 31 39 c7 af 35 b7 ee a4 |......2019..5...|
00000010 e8 a4 ea ce e1 cf c2 3c 52 45 49 57 41 3e 0a |.......<REIWA>.|
0000001f
Step4 Shift_JISか?
Shift_JISは一番掴みどころがない印象です。というわけでやっつけ感がありますが、一番最後まで残ればShift_JIS。
一応EUC-JPと異なる点を書いておくと、2バイトで日本語一文字ではあるんですが、2バイト目の最上位ビットが0になる可能性があります。
つまり先頭の文字が0x80以上のバイトが奇数個並んでいればShift_JISの可能性があります。
※まれに8Eか8Fが登場したせいでEUC-JPも奇数個になってしまう可能性はある。
また、半角カタカナを使用しなければ0xA0から0xDFが登場することがない点もEUC-JPと異なります。
$ echo "元号は2019年5月より令和<REIWA>" | iconv -t SJIS | hexdump -C
00000000 8c b3 8d 86 82 cd 32 30 31 39 94 4e 35 8c 8e 82 |......2019.N5...|
00000010 e6 82 e8 97 df 98 61 3c 52 45 49 57 41 3e 0a |......a<REIWA>.|
0000001f
まとめ
Shift_JISとEUC-JPの切り分けがイマイチな感じになってしまいました。もっとわかりやすくまとめられたら、、、
本題とは関係ないですが、hexdumpの横っちょにASCIIで表示された様子をみて、なるほどISO-2022-JPとShift_JISはASCIIとの互換性がイマイチだなと実感しました。
ちなみにこの記事の内容は以下の書籍で勉強させていただきました。
文字コードについてもっと詳しくまとめられていておすすめです。
https://gihyo.jp/book/2019/978-4-297-10291-3