グルジア文字と case insensitive filesystem
Mercurial で case insensitive filesystem 周りの修正をする際に仕込んだ知識のまとめエントリその1。
グルジア語 (Georgian) の文字 U+10A0 と U+2D00 は、Unicode 仕様上は大文字/小文字の関係にあるので、例えば最新の Python 処理系で以下のような条件判定は True を返す。
u'\u10A0'.lower() == u'\u2D00'
u'\u10A0' == u'\u2D00'.upper()
大文字小文字を区別しない case insensitive filesystem 上であれば、一方の文字を使用した名前のファイルに対して、他方の文字での存在確認等が可能であることを期待するのが当然なのだが、Windows 環境で実際に試してみると以下のような結果に。
- Windows XP: ファイル不在と判定
- Windows 7: ファイル存在と判定
この挙動は以下のような Python コードで確認可能。
import os name = u'\u10a0' test = u'\u2d00' f = file(name, 'w'); f.write('a'); f.close() print 'os.path.lexists(\u%04x) = %s' % (ord(name), os.path.lexists(name)) print 'os.path.lexists(\u%04x) = %s' % (ord(test), os.path.lexists(test))
ちなみに、cygwin 環境の Python で実行する場合は、ロケール系環境変数でファイル名文字コードを utf-8 に設定する必要がある。
# 例: LC_CTYPE=ja_JP.utf-8
で、この挙動の差は Unicode 仕様策定の歴史的経緯が関係している模様。
仕様策定の経緯は、グルジア文字の wiki ページに詳細が:
Unicode 1.0 時点では、"Asomtavruli"(upper) と "Nuskhuri"(lower) いう文字種別が共に U+10A0 ... U+10CF を共有していたけれど、2005 年 3 月に制定された Unicode 4.1 では、"Asomtavruli" が U+10A0 ... U+10CF を、"Nuskhuri" が U+2D00 ... U+2D2F を使用するように改変
Windows 2000 や XP (2001 年発売) では、そもそもファイルシステム実装時点で U+10A0 と U+2D00 の間に何の関連も無いのだから、上手く扱えるわけが無い。
手元に環境が無いので Vista での挙動は確認できないけど、発売開始が 2007 年だとすると間に合った可能性の方が高いかな?
ところで、この判定の挙動は『どの版の OS 上で実行するか?』ではなく、『どの版の OS でフォーマットした HDD 上で実行するか?』に依存するとのこと。
これは、ファイルシステムの初期化時点で変換マッピングを埋め込むことで実現しているらしい。変換マッピング情報は、$UpCase という特殊ファイルに格納されるのだとか。
つまり、Windows 2000 や XP でフォーマットした HDD を使えば、Windows 7 上でも U+10A0 と U+2D00 は一致しないし、Windows 7 でフォーマットした HDD を使えば、Windows 2000 や XP 上でも両者は一致するわけだ。
確かに、同一媒体を使用した場合の挙動、とりわけファイルの一致判定などという根源的なものが、OS の版毎にコロコロ変動したのではかなわん、というのは納得。OS のアップデートも安心してできないしね。
ちなみに、Mac OS X の HFS+ も case insensitive filesystem なのだが、実はこれも U+10A0 と U+2D00 の同一性を判定できないことから、古い Unicode 仕様をベースにしているのかな?
ところで、Michael Kaplan 氏のページでは、グルジア文字の分類を:
- Khutsuri: 古文字/大文字
- Nushkuri(Nuskhuri?): 古文字/小文字
- Mkhedruli: 現代文字/文字大小なし (caseless)
- Khutsuri: 古文字
- Asomtavruli: 古文字/大文字
- Nuskhuri: 古文字/小文字
- Mkhedruli: 現代文字/文字大小なし
と分類しているように見えるのだけど、単に僕の英語力の問題?