ttt

getttyent

(FreeBSD) locale設定とphpのfgetcsvの挙動の関係がよくわかんない

2009-02-23 22:43:14 | デジタル・インターネット

なんか勘違いしてるのかなぁ~?

fgetcsvを使って、こんなphpスクリプトを書いてみました。

% cat csvtest.php
<?php
$fp = fopen("csvtest.csv","r");
while( FALSE != ($data=fgetcsv($fp)) ) {
  echo "data=";
  print_r($data);
}
?>

「csvtest.php」をダウンロード

読み込むCSVファイルcsvtest.csvはこんな内容。

% cat csvtest.csv
1,2,3,"あいうえお",4,5,6
2,3,"あいうえお",4,5,6,1
3,"あいうえお",4,5,6,1,2

「csvtest.csv」をダウンロード

文字コードは、日本語EUCにしてあります。

% hd csvtest.csv
00000000  31 2c 32 2c 33 2c 22 a4  a2 a4 a4 a4 a6 a4 a8 a4  |1,2,3,"あいうえ・
00000010  aa 22 2c 34 2c 35 2c 36  0a 32 2c 33 2c 22 a4 a2  |・,4,5,6.2,3,"あ|
00000020  a4 a4 a4 a6 a4 a8 a4 aa  22 2c 34 2c 35 2c 36 2c  |いうえお",4,5,6,|
00000030  31 0a 33 2c 22 a4 a2 a4  a4 a4 a6 a4 a8 a4 aa 22  |1.3,"あいうえお"|
00000040  2c 34 2c 35 2c 36 2c 31  2c 32 0a                 |,4,5,6,1,2.|
0000004b

phpの関数fgetcsvは、CSVファイルを読み出して、フィールドごとに分解してくれるのですが、マニュアルによれば

http://www.php.net/manual/ja/function.fgetcsv.php

注意: この関数はロケール設定を考慮します。

ということになっています。

localeをCにして実行してみると

% env LC_CTYPE=C php csvtest.php
data=Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => あいうえお
    [4] => 4
    [5] => 5
    [6] => 6
)
data=Array
(
    [0] => 2
    [1] => 3
    [2] => あいうえお
    [3] => 4
    [4] => 5
    [5] => 6
    [6] => 1
)
data=Array
(
    [0] => 3
    [1] => あいうえお
    [2] => 4
    [3] => 5
    [4] => 6
    [5] => 1
    [6] => 2
)

これは、まあそういうこともあるかな、という結果。

ところが、localeを、日本語EUCのja_JP.eucJPにすると

% env LC_CTYPE=ja_JP.eucJP php csvtest.php
data=Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => あいうえお",4,5,6
2,3,あいうえお"
    [4] => 4
    [5] => 5
    [6] => 6
    [7] => 1
)
data=Array
(
    [0] => 3
    [1] => あいうえお",4,5,6,1,2

)

ダブルクォート「"」を見失って、「迷走」してます。どうして?

最初、ja_JP.EUC-JPにしたら

% env LC_CTYPE=ja_JP.EUC-JP php csvtest.php
data=Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => あいうえお
    [4] => 4
    [5] => 5
    [6] => 6
)
~以下略~

となって、意図どおり動いているように見えたんで、locale名が間違ってるのか、と思ったんですが、どうもこちらは、ja_JP.EUC-JPなんていうlocaleは存在しないので、C相当の動作になっている、ってことらしいです。

さて、エンコードをUTF-8にしてみると

% mv csvtest.csv csvtest.csv.org
% iconv -f EUC-JP -t UTF-8 csvtest.csv.org > csvtest.csv

% env LC_CTYPE=ja_JP.UTF-8 php csvtest.php | iconv -f UTF-8 -t EUC-JP

data=Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => あいうえお
    [4] => 4
    [5] => 5
    [6] => 6
)
data=Array
(
    [0] => 2
    [1] => 3
    [2] => あいうえお
    [3] => 4
    [4] => 5
    [5] => 6
    [6] => 1
)
data=Array
(
    [0] => 3
    [1] => あいうえお
    [2] => 4
    [3] => 5
    [4] => 6
    [5] => 1
    [6] => 2
)

ということで、UTF-8なら大丈夫っぽい。

ちなみに

% env LANG=ja_JP.UTF-8 date | iconv -f UTF-8 -t EUC-JP
2009年 2月23日 月曜日 22時30分10秒 JST

なので、ja_JP.UTF-8というlocale名は正しいと思う。

デフォルト設定では、こんな感じになってるんですが、何か関係あるのでしょうかね。よくわかってないままphpを使っていたりするのが悪いのかも。

% php -i | grep -i encoding
iconv.input_encoding => ISO-8859-1 => ISO-8859-1
iconv.internal_encoding => ISO-8859-1 => ISO-8859-1
iconv.output_encoding => ISO-8859-1 => ISO-8859-1
mbstring.encoding_translation => Off => Off
mbstring.internal_encoding => no value => no value
mbstring.script_encoding => no value => no value
SQLite Encoding => UTF-8

わかんないことがあればソースコードを見ろ!ってことで、cd /usr/ports/lang/php5/; make patchして、grepでそれらしい場所を推測して、眺めてみました。オープンソースって便利だね。FreeBSDって便利だね。

mblenとかいう、マルチバイトを考慮して文字列長を調べるのかな?って感じの関数が使われていましたが、よくわかんなかったです。