SOFTELメモ Developer's blog

会社概要 ブログ 調査依頼 採用情報 ...
技術者募集中

【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');

関連するメモ

コメント(1)

文字化け治った太郎 2012年11月10日 15:55

PHPでサーバーに置いたtxtを編集して表示する際に文字化けに悩まされていました。
setlocale(LC_ALL, ‘ja_JP.eucJP’);
たったこれだけの文で問題が解消されました。
有難うございました!