[PHP] csv・tsvファイルの行末の改行コードが認識されない

PHP

SplFileObject(READ_CSV)を使ってtsvファイルを読み込んだところ、全ての値が1行として扱われた。。。csvファイルで試しても同じ。
tsvファイルの行末の改行コードが認識されていなかったことが原因だった。

起きていること

b.tsvを読み込むと全ての値が1行になる
# b.tsvファイル
aaa	bbb	ccc
a1	b1	c1
a2	b2	c2
a3	b3	c3

// PHP処理
$file = new SplFileObject("b.tsv");
$file->setFlags(SplFileObject::READ_CSV | SplFileObject::SKIP_EMPTY | SplFileObject::READ_AHEAD);
$file->setCsvControl("\t");
foreach ($file as $line) {
	echo "line No:".$file->key();
	echo "
"; echo "
";
	var_dump($line);
	echo "
"; }
実行結果
line No:0 array(9) {  [0]=>  string(3) “aaa"  [1]=>  string(3) “bbb"  [2]=>  string(6) “ccc a1"  [3]=>  string(2) “b1"  [4]=>  string(5) “c1 a2"  [5]=>  string(2) “b2"  [6]=>  string(5) “c2 a3"  [7]=>  string(2) “b3"  [8]=>  string(2) “c3" }
改行されるべきところが1つの要素になってしまっている…

b.tsvファイルはずっとMacのテキストエディットで開いていてテキストエディットだと改行されているが、Atomで開くと改行がない

display_tsv_with_atom
display_tsv_with_atom

ターミナルで改行コードを調べる


$ file b.tsv
b.tsv: ASCII text, with CR line terminators
改行コードが「CR」になっている

原因

fgets()やfile()などPHPのファイル操作系関数では、「CR+LF」と「LF」は改行として扱われるが、「CR」は改行として認識しない

解決方法

auto_detect_line_endingsをOnにするとCRも改行コードとして認識させることができる(デフォルトではOffになっている)

ini_set('auto_detect_line_endings',true);

auto_detect_line_endingsとは

公式ドキュメント 実行時設定 onにした場合、PHPは fgets() および file() により読み込まれたデータを評価し、UNIX、MS-DOS、Machintoshの行末 表記を使用しているかどうかを調べます。

これにより、PHPがMacintoshシステムと相互運用できるようになりますが、 デフォルトはOffとなっています。これは、最初の行の行末表記を検出 する際にごく僅かな性能劣化があるためと、UNIXシステムのもとで復改 文字を項目セパレータとして使用している人が従来のバージョンと互換 性がない動作であると感じる可能性があるためです。
公式ドキュメント fgets 注意: マッキントッシュコンピュータ上で作成されたファイルを読み込む際に、 PHP が行末を認識できないという問題が発生した場合、 実行時の設定オプションauto_detect_line_endings を有効にする必要が生じるかもしれません。

preg_replace("/\r\n|\r|\n/", $to, $string);とかで改行コードを変換する方法もあるが、それだとfile_get_contentsで一度ファイルの中身を読み込んだりする必要があるので微妙

Posted by Agopeanuts