最近携わっているPHPのプロジェクトでは、プログラムを日本語で書いています。クラス名・変数名はもちろんファイル名も日本語です。(なぜ日本語で書くことにしたのか、そこらへんのモチベーションについては別記事にまとめたい)
このプロジェクトは開発環境がMac、プロダクション環境がLinuxなのですが、日本語ファイル名のPHPがオートロードされないなどの問題が発生しました。しらべたところ、MacのファイルシステムとLinuxのファイルシステムでUnicodeの規格が違うのが原因でした。詳しくは、「紹介マニアどらふと版: Mac OS X におけるファイル名に関するメモ(NFC, NFD等)」の記事が参考になります。
簡単にファイルシステムの違いを説明すると、
Mac: NFDという規格。濁点半濁点などバラしている(正規化)。「だ」は「た」「゛」の6バイトになる
Linux: NFCという規格。濁点半濁点をバラさない(非正規化)。「だ」は3バイトになる
という違いがあります。
Macで作ったNFDのファイルをgitにcommitすると、そのまま正規化された状態でステージされます。それを、Linuxでgit pullしたときに、NFDからNFCに変換してくれればいいのですが、NFDのままファイルが作られます。PHPのソースコードはNFCですから、ファイル名が決め打ちで参照されていると、「Macでは動いていたけど、Linuxで動かなくなった」という現象が発生するのです。
日本語ファイルをコミットされてしまったのはしょうがないので、ひとまず問題のあるファイルを洗い出すために、NFDのファイルを根こそぎ探しだすスクリプトをPythonで作りました。
使い方
$ find-nfd -h
usage: find-nfd [-h] [path]
Find NFD files
positional arguments:
path path to find(Default: current working directory)
optional arguments:
-h, --help show this help message and exit
ソースコード
#!/usr/bin/env python
import os
import argparse
from unicodedata import normalize
def fild_all_files(directory):
for root, dirs, files in os.walk(directory):
yield root
for file in files:
yield os.path.join(root, file)
def to_nfc(string):
string = string.decode("utf8")
string = normalize("NFC", string)
string = string.encode("utf8")
return string
def is_nfd(string):
if to_nfc(string) == string:
return False
else:
return True
def find_nfd_files(directory):
for file in fild_all_files(directory):
if is_nfd(file):
yield file
def main():
parser = argparse.ArgumentParser(description="Find NFD files")
parser.add_argument("path", type=str, help="path to find(Default: current working directory)", nargs='?', default=os.getcwd())
args = parser.parse_args()
count = 0
for file in find_nfd_files(args.path):
print file
count += 1
print ""
print "%u files found" % (count)
if __name__ == "__main__":
main()
使ってみる
Macで作ったファイルです↓
$ php -r 'var_dump(glob("/tmp/test/1/*"));'
array(7) {
[0] =>
string(13) "/tmp/test/1/a"
[1] =>
string(13) "/tmp/test/1/b"
[2] =>
string(17) "/tmp/test/1/schon"
[3] =>
string(19) "/tmp/test/1/schön"
[4] =>
string(30) "/tmp/test/1/한글"
[5] =>
string(27) "/tmp/test/1/はひふへほ"
[6] =>
string(42) "/tmp/test/1/ぱぴぷぺぽ"
}
見た目はNFDなのかNFCなのか全く区別がつきませんが、「はひふへほ」と「ぱぴぷぺぽ」でstringのバイト数が違うのがわかります。
ドイツ語のウムラウトや、韓国語のハングルもNFDになっているのが分かります。
この中から、NFDのファイルを探してみます:
$ find-nfd.py /tmp/test/1
/tmp/test/1/schön
/tmp/test/1/한글
/tmp/test/1/ぱぴぷぺぽ
3 files found
3つ見つかりました。
こういうファイルを見つけたら、Linux環境かWindows環境でリネームして、gitに入れなおすことになります。
「こんなめんどくさいこと毎回やるのか?」と言われそうですが、vagrantでDebian環境がたった5分でできあがるように開発の仕組み自体を変えています:)
Macとは言え、プロダクション環境ではないので、無駄なハマリを回避するには、プロダクション環境と全く同じ開発環境を作ることが大事ですね。