【php】fgetcsv()はロケールの設定に依存する
問題
fgetcsv()でShift_JISのCSVファイルをそのまま読み取るとき、「”(ダブルクォーテーション)」に囲まれていないマルチバイト文字があると、正常に読み取れない。
「”名前”,”住所”,”岐阜”,”愛知”,”東京”」は読めるけど、「名前,住所,岐阜,愛知,東京」は読めない。
改行が含まれている部分は、「”」で囲まないといけないので、きれいに読み取れるのだが、「名前,住所,岐阜,愛知,東京」が読めないのが不思議だ。
なんで?
答え
phpのソースを読むと、C言語のmblen()がOSのロケールに依存するというところに行き着くが、マニュアルにもちゃんと「ロケール設定を考慮します」と書いてある。
注意:
この関数はロケール設定を考慮します。もし LANG が例えば en_US.UTF-8 の場合、 ファイル中の 1 バイトエンコーディングは間違って読み込まれます。
つまり、OSの設定(環境変数のLANG)やphpのsetlocale()関数で動作を調整できる。
例
・ OSの設定がLANG=”ja_JP.UTF-8″、CSVもUTF-8の場合
開いて、そのままfgetcsv()して問題ない。
$fp = fopen('/tmp/utf-8.csv', 'r'); while ($line = fgetcsv($fp)) { var_dump($line); }
・ OSの設定がLANG=”ja_JP.eucjp”、CSVもEUC-JPの場合
開いて、そのままfgetcsv()して問題ない。
$fp = fopen('/tmp/euc.csv', 'r'); while ($line = fgetcsv($fp)) { var_dump($line); }
・ OSの設定がLANG=”ja_JP.UTF-8″、CSVがEUC-JPの場合
開いて、そのままfgetcsv()すると正常に読み取れない。setlocale()する。
setlocale(LC_ALL, 'ja_JP.eucJP'); $fp = fopen('/tmp/euc.csv', 'r'); while ($line = fgetcsv($fp)) { //読み取ったデータはEUC-JPなので、必要に応じて変換して mb_convert_variables('UTF-8', 'eucjp-win', $line); var_dump($line); }
・ OSの設定がLANG=”ja_JP.eucjp”、CSVがUTF-8の場合
上と同様 setlocale()する。
setlocale(LC_ALL, 'ja_JP.UTF-8');
・ OSの設定がLANG=C、CSVがUTF-8の場合
CSVがUTF-8だとしても、localeと一致していないので、setlocale()などしないと正しく読み取れない。
・ CSVがShift_JISの場合
localeに ja_JP.sjis なんてない。
# locale -a | grep ja ja_JP ja_JP.eucjp ja_JP.ujis ja_JP.utf8 japanese japanese.euc
ファイルの文字コードを変えて処理するのが一番簡単。 → 【php】fgetcsv()の基本作法 (Ver.2) ということになる。
おまけ::localeにja_JP.sjisを追加する方法
OSさんに文句(警告)を言われつつも、言語に日本語Shift_JISを設定できる。
# localedef -f SHIFT_JIS -i ja_JP ja_JP.sjis キャラクタマップ `SHIFT_JIS' は ASCII 互換ではありません, ロケールは ISO C に従っていません
すると
# locale -a | grep ja ja_JP ja_JP.eucjp ja_JP.sjis ja_JP.ujis ja_JP.utf8 japanese japanese.euc
この状態だと、次の方法でShift_JISのCSVをfgetcsv()できる。
setlocale(LC_ALL, 'ja_JP.sjis'); $fp = fopen('/tmp/sjis.csv', 'r'); while ($line = fgetcsv($fp)) { //読み取ったデータはShift_JISなので、必要に応じて変換して mb_convert_variables('UTF-8', 'sjis-win', $line); var_dump($line); }
あまりOSに依存したくないので、引数か何かで切り替えられるようにして欲しい。
メモ
現在のロケールの設定を確認するには以下のようにする。
echo setlocale(LC_ALL, '0');
文字化け治った太郎 2012年11月10日 15:55
PHPでサーバーに置いたtxtを編集して表示する際に文字化けに悩まされていました。
setlocale(LC_ALL, ‘ja_JP.eucJP’);
たったこれだけの文で問題が解消されました。
有難うございました!