使用fputcsv()/ fgetcsv()写入csv时数据会出现乱码

时间:2017-06-08 06:13:08

标签: php csv escaping fgetcsv fputcsv

使用fputcsv()和fgetcsv()在PHP中似乎存在编码问题或错误。

以下PHP代码:

$row_before = ['A', json_encode(['a', '\\', 'b']), 'B'];

print "\nBEFORE:\n";
var_export($row_before);
print "\n";

$fh = fopen($file = 'php://temp', 'rb+');

fputcsv($fh, $row_before);

rewind($fh);

$row_after = fgetcsv($fh);

print "\nAFTER:\n";
var_export($row_after);
print "\n\n";

fclose($fh);

给我这个输出:

BEFORE:
array (
  0 => 'A',
  1 => '["a","\\\\","b"]',
  2 => 'B',
)

AFTER:
array (
  0 => 'A',
  1 => '["a","\\\\',
  2 => 'b""]"',
  3 => 'B',
)

很明显,数据在途中受损。最初行中只有3个单元格,之后行中有4个单元格。由于反斜杠也被用作转义字符,因此中间单元格被拆分。

另见 https://3v4l.org/nc1oE 或者在这里,使用明确的分隔符值,enclosure,escape_char:https://3v4l.org/Svt7m

在写入CSV之前,有什么方法可以清理/转义我的数据,以保证从文件中读取的数据完全相同?

CSV是完全可逆的格式吗?

编辑:目标是一种正确编写和读取任何数据为csv的机制,这样在一次往返之后数据仍然是相同的。

编辑:我意识到我并不真正理解$ escape_char参数。另请参阅fgetcsv/fputcsv $escape parameter fundamentally broken也许对此的回答也会使我们更接近解决方案。

5 个答案:

答案 0 :(得分:3)

罪魁祸首是fputcsv()使用转义字符,这是CSV的非标准扩展名。 (好吧,就RFC 7111而言,可以视为标准。)基本上,必须禁用此转义字符,但将空字符串作为$ escape传递给fputcsv()不起作用。通常,传递NUL字符应该会产生所需的结果,但请参阅https://3v4l.org/MlluN

答案 1 :(得分:0)

将代码用于特定的分隔符,但更改以下行将有效...

$enclosure = "'";

我认为这可能与认为\正在逃避以下引用有关。

答案 2 :(得分:0)

就像在php中一样,\\用来逃避反斜杠(link for PHP manual escape sequence),所以为了使它成为字符串你需要再使用一个单引号('')。

所以你的输入数组应该是......

$row_before = ['A', json_encode(['a', "'\\'", 'b']), 'B'];

答案 3 :(得分:0)

这不是PHP错误。似乎json_encode()使用相同的分隔符(,),enclosure(“)和escape(\),它与fputcsv()fgetcsv()的默认分隔符,enclosure和escape相同。您可以区分机箱或转义,并在必要时分隔。

正如已经回答的那样,在这种情况下,它可以通过使用(')而不是:

指定enclosure
$row_before = ['A', json_encode(['a', '\\', 'b']), 'B'];

print "\nBEFORE:\n";
var_export($row_before);
print "\n";

$fh = fopen($file = 'php://temp', 'rb+');

fputcsv($fh, $row_before, ',', "'");

rewind($fh);

$row_after = fgetcsv($fh, 0, ',', "'");

print "\nAFTER:\n";
var_export($row_after);
print "\n\n";

fclose($fh);

答案 4 :(得分:0)

与其他人的说法相反,我声称这是一个PHP错误。我要报告,并更新这个答案。

编辑:现在报告https://bugs.php.net/bug.php?id=74713

在这个答案中讨论:

  • 更改分隔符有帮助吗? - >不是真的。
  • 可以修复fputcsv()吗? - >是。

更改分隔符有帮助吗?

可以证明,这可以通过分隔符,封闭和转义字符的任意组合来重现。

https://3v4l.org/a29kR

$delimiter = 'X';
$enclosure = 'Y';
$escape_char = "Z";

$row_before = [
  'A',
  "[{$enclosure}a{$enclosure}{$delimiter}{$enclosure}{$escape_char}{$escape_char}{$enclosure}{$delimiter}{$enclosure}b{$enclosure}]",
  'B',
];

print "\nBEFORE:\n";
var_export($row_before);
print "\n";

$fh = fopen($file = 'php://temp', 'rb+');

fputcsv($fh,$row_before,$delimiter,$enclosure, $escape_char);

rewind($fh);

$row_plain = fread($fh, 1000);

print "\nPLAIN:\n";
var_export($row_plain);
print "\n";

rewind($fh);

$row_after = fgetcsv($fh, 500,$delimiter,$enclosure, $escape_char);

print "\nAFTER:\n";
var_export($row_after);
print "\n\n";

fclose($fh);

输出:

BEFORE:
array (
  0 => 'A',
  1 => '[YaYXYZZYXYbY]',
  2 => 'B',
)

PLAIN:
'AXY[YYaYYXYYZZYXYYbYY]YXB
'

AFTER:
array (
  0 => 'A',
  1 => '[YaYXYZZ',
  2 => 'bYY]Y',
  3 => 'B',
)

fputcsv()可以修复吗?

为此,让我们回到更常见和可读的分隔符,封闭和转义字符。

$delimiter = ',';
$enclosure = '"';
$escape_char = "@";

结果如下:

BEFORE:
array (
  0 => 'A',
  1 => '["a","@@","b"]',
  2 => 'B',
)

PLAIN:
'A,"[""a"",""@@",""b""]",B
'

AFTER:
array (
  0 => 'A',
  1 => '["a","@@',
  2 => 'b""]"',
  3 => 'B',
)

我们发现'"@@"'部分导出为'""@@"',而它应该已导出为'""@@""'

事实上,使用fwrite()代替fputcsv()手动执行此操作可以解决问题:https://3v4l.org/4U1CQ