PHP Conference Japan 2024

str_getcsv

(PHP 5 >= 5.3.0, PHP 7, PHP 8)

str_getcsv 将 CSV 字符串解析为数组

描述

str_getcsv(
    字符串 $string,
    字符串 $separator = ",",
    字符串 $enclosure = "\"",
    字符串 $escape = "\\"
): 数组

解析以 CSV 格式的字段的字符串输入,并返回一个包含读取的字段的数组。

注意: 此函数会考虑区域设置。例如,如果 LC_CTYPEen_US.UTF-8,则某些单字节编码的数据可能会被错误地解析。

参数

字符串

要解析的字符串。

分隔符

separator 参数设置字段分隔符。它必须是单个字节字符。

封闭符

enclosure 参数设置字段封闭字符。它必须是单个字节字符。

转义符

escape 参数设置转义字符。它必须是单个字节字符或空字符串。空字符串 ("") 将禁用专有的转义机制。

注意: 通常,enclosure 字符在字段内通过重复它来转义;但是,可以使用 escape 字符作为替代。因此,对于默认参数值 ""\" 具有相同的含义。除了允许转义 enclosure 字符外,escape 字符没有任何特殊含义;它甚至不打算转义自身。

警告

从 PHP 8.4.0 开始,依赖于 escape 的默认值已被弃用。需要通过位置或使用 命名参数 显式提供。

警告

escape 设置为空字符串 ("") 以外的任何值时,可能会导致不符合 » RFC 4180 的 CSV,或者无法通过 PHP CSV 函数进行往返处理。 escape 的默认值为 "\\",因此建议显式将其设置为空字符串。默认值将在 PHP 的未来版本(不早于 PHP 9.0)中更改。

返回值

返回一个包含读取的字段的索引数组。

错误/异常

如果 separatorenclosure 长度不是一个字节,则抛出 ValueError

如果 escape 长度不是一个字节或空字符串,则抛出 ValueError

变更日志

版本 描述
8.4.0 依赖于 escape 的默认值现已弃用。
8.4.0 现在如果 separatorenclosureescape 无效,则抛出 ValueError。这模仿了 fgetcsv()fputcsv() 的行为。
8.3.0 如果最后一个字段仅包含未终止的封闭符,则返回空字符串而不是包含单个空字节的字符串。
7.4.0 escape 参数现在将空字符串解释为禁用专有转义机制的信号。以前,空字符串被视为默认参数值。

示例

示例 #1 str_getcsv() 示例

<?php

$string
= 'PHP,Java,Python,Kotlin,Swift';
$data = str_getcsv($string);

var_dump($data);
?>

以上示例将输出

array(5) {
  [0]=>
  string(3) "PHP"
  [1]=>
  string(4) "Java"
  [2]=>
  string(6) "Python"
  [3]=>
  string(6) "Kotlin"
  [4]=>
  string(5) "Swift"
}

示例 #2 使用空字符串的 str_getcsv() 示例

警告

对于空字符串,此函数返回 [null] 值而不是空数组。

<?php

$string
= '';
$data = str_getcsv($string);

var_dump($data);
?>

以上示例将输出

array(1) {
  [0]=>
  NULL
}

参见

添加注释

用户贡献注释 29 个注释

james at moss dot io
10 年前
[编辑注 (cmb):如果字段包含换行符,则不会产生预期的结果。]

将 CSV 文件解析为数组的便捷单行代码

<?php

$csv
= array_map('str_getcsv', file('data.csv'));

?>
starrychloe at oliveyou dot net
9 年前
基于 James 的代码,这将创建一个关联数组数组,其中第一行列标题作为键。

<?php
$csv
= array_map('str_getcsv', file($file));
array_walk($csv, function(&$a) use ($csv) {
$a = array_combine($csv[0], $a);
});
array_shift($csv); # 删除列标题
?>

这将产生类似的结果
[2] => 数组
(
[Campaign ID] => 295095038
[Ad group ID] => 22460178158
[Keyword ID] => 3993587178
durik at 3ilab dot net
13 年前
由于 str_getcsv() 与 fgetcsv() 不同,它不解析 CSV 字符串中的行,所以我找到了以下简单的解决方法

<?php
$Data
= str_getcsv($CsvString, "\n"); //解析行
foreach($Data as &$Row) $Row = str_getcsv($Row, ";"); //解析行中的项目
?>

为什么不用 explode() 而用 str_getcsv() 解析行?因为 explode() 无法正确处理字符串中可能存在的带引号的部分或转义字符。
sven at e7o dot de
9 年前
PHP 在解析带有字节顺序标记 (BOM) 的 UTF-8 时会失败。在将其传递给 csv 解析器之前,使用以下代码从字符串中去除 BOM。

<?php
$bom
= pack('CCC', 0xEF, 0xBB, 0xBF);
if (
strncmp($yourString, $bom, 3) === 0) {
$body = substr($yourString, 3);
}
?>
normadize -a- gmail -d- com
11 年前
正如其他一些用户在此指出的那样,如果要符合 RFC 或大多数电子表格工具(如 Excel 或 Google Docs),则不能使用 str_getcsv()。

这些工具不转义逗号或换行符,而是用双引号 (“) 括起字段。如果字段中存在任何双引号,则用另一个双引号对其进行转义 (“ 变成 "")。所有这些看起来可能很奇怪,但这就是 RFC 和大多数工具的做法……

例如,尝试将包含换行符和逗号作为字段值一部分的 Google Docs 电子表格导出为 .csv 文件(文件 > 另存为 > .csv),然后查看 .csv 内容,然后尝试使用 str_getcsv() 解析它……无论您向其传递什么参数,它都会非常出色地失败。

这是一个可以正确处理所有内容甚至更多内容的函数

- 不使用任何 for 或 while 循环;
- 它允许使用任何分隔符(任何长度的任何字符串);
- 可选跳过空行;
- 可选修剪字段;
- 也可以处理 UTF8 数据(尽管 .csv 文件可能是非 Unicode 的)。

这是该函数更易于阅读的版本

<?php

// 返回一个二维数组,包含行和字段

function parse_csv ($csv_string, $delimiter = ",", $skip_empty_lines = true, $trim_fields = true)
{
$enc = preg_replace('/(?<!")""/', '!!Q!!', $csv_string);
$enc = preg_replace_callback(
'/"(.*?)"/s',
function (
$field) {
return
urlencode(utf8_encode($field[1]));
},
$enc
);
$lines = preg_split($skip_empty_lines ? ($trim_fields ? '/( *\R)+/s' : '/\R+/s') : '/\R/s', $enc);
return
array_map(
function (
$line) use ($delimiter, $trim_fields) {
$fields = $trim_fields ? array_map('trim', explode($delimiter, $line)) : explode($delimiter, $line);
return
array_map(
function (
$field) {
return
str_replace('!!Q!!', '"', utf8_decode(urldecode($field)));
},
$fields
);
},
$lines
);
}

?>

由于这没有使用任何循环,因此实际上可以将其编写为单行语句(单行代码)。

这是仅使用一行代码作为函数体的函数,但格式很好

<?php

// 返回与上面相同的二维数组,但使用单行代码

function parse_csv ($csv_string, $delimiter = ",", $skip_empty_lines = true, $trim_fields = true)
{
return
array_map(
function (
$line) use ($delimiter, $trim_fields) {
return
array_map(
function (
$field) {
return
str_replace('!!Q!!', '"', utf8_decode(urldecode($field)));
},
$trim_fields ? array_map('trim', explode($delimiter, $line)) : explode($delimiter, $line)
);
},
preg_split(
$skip_empty_lines ? ($trim_fields ? '/( *\R)+/s' : '/\R+/s') : '/\R/s',
preg_replace_callback(
'/"(.*?)"/s',
function (
$field) {
return
urlencode(utf8_encode($field[1]));
},
$enc = preg_replace('/(?<!")""/', '!!Q!!', $csv_string)
)
)
);
}

?>

如果需要,请将 !!Q!! 替换为另一个占位符。

玩得开心。
Jay Williams
14 年前
这是一个快速简便的方法,可以将 CSV 文件转换为关联数组

<?php
/**
* @link http://gist.github.com/385876
*/
function csv_to_array($filename='', $delimiter=',')
{
if(!
file_exists($filename) || !is_readable($filename))
return
FALSE;

$header = NULL;
$data = array();
if ((
$handle = fopen($filename, 'r')) !== FALSE)
{
while ((
$row = fgetcsv($handle, 1000, $delimiter)) !== FALSE)
{
if(!
$header)
$header = $row;
else
$data[] = array_combine($header, $row);
}
fclose($handle);
}
return
$data;
}

?>
dejiakala at gmail dot com
10 年前
我希望将 james at moss dot io 和 Jay Williams (csv_to_array()) 的两个解决方案的优点结合起来——从包含标题行的 CSV 文件创建关联数组。

<?php

$array
= array_map('str_getcsv', file('data.csv'));

$header = array_shift($array);

array_walk($array, '_combine_array', $header);

function
_combine_array(&$row, $key, $header) {
$row = array_combine($header, $row);
}

?>

然后我想为什么不尝试一些基准测试呢?我获取了一个包含 50,000 行(每行 10 列)的样本 CSV 文件和 Vulcan Logic 反汇编器 (VLD),它挂接到 Zend 引擎并转储脚本的所有操作码(执行单元)——参见 http://pecl.php.net/package/vld 和此处的示例:http://fabien.potencier.org/article/8/print-vs-echo-which-one-is-faster

结果

array_walk() 和 array_map() - 39 个操作码
csv_to_array() - 69 个操作码
daniel dot oconnor at gmail dot com
15 年前
没有这个?让 fgetcsv() 来帮你做吧。

5.1.0+

<?php
if (!function_exists('str_getcsv')) {
function
str_getcsv($input, $delimiter = ",", $enclosure = '"', $escape = "\\") {
$fiveMBs = 5 * 1024 * 1024;
$fp = fopen("php://temp/maxmemory:$fiveMBs", 'r+');
fputs($fp, $input);
rewind($fp);

$data = fgetcsv($fp, 1000, $delimiter, $enclosure); // $escape 只在 5.3.0 版本中添加

fclose($fp);
return
$data;
}
}
?>
Ryan Rubley
11 年前
@normadize - 这算是个不错的开始,但是它在字段为空但用引号括起来(返回一个包含一个双引号的字符串而不是空字符串)以及像 """""foo""""" 这样的情况(应该返回 ""foo"" 但却返回 "foo")下会失败。由于 CSV 中最后的 CRLF,我还得到一行末尾带有一个空字段的行。此外,我不太喜欢 !!Q!! 魔法或 url 编码来解决问题。另外,\R 在我的任何 php 安装中的 pcre 中都不起作用。

这是我的方法,不使用匿名函数(因此它适用于 PHP < 5.3),也不使用您的选项(因为我相信根据 RFC 解析的唯一正确方法是 $skip_empty_lines = false 和 $trim_fields = false)。

// 将 CSV 文件解析为二维数组
// 这看起来像用换行符和逗号分割字符串一样简单,但这只有在执行一些技巧时才有效
// 以确保您不会分割双引号内的换行符和逗号。
function parse_csv($str)
{
// 匹配所有未加引号的文本和一个系列的加引号的文本(或字符串的结尾)
// 每个匹配组都将使用回调函数进行解析,其中 $matches[1] 包含所有未加引号的文本,
// $matches[3] 包含引号内的所有内容
$str = preg_replace_callback('/([^"]*)("((""|[^"])*)"|$)/s', 'parse_csv_quotes', $str);

// 删除最后一个换行符以防止最后一行出现 0 字段数组
$str = preg_replace('/\n$/', '', $str);

// 按 LF 分割并使用回调函数解析每一行
return array_map('parse_csv_line', explode("\n", $str));
}

// 使用转义序列用标记替换双引号内的所有 csv 特殊字符
function parse_csv_quotes($matches)
{
// 引号内的任何可能以后用于将字符串分割成行和字段的内容,
// 需要被引用。我们可以保证唯一安全使用的字符是 CR,因为它永远不会出现在未加引号的文本中
// 所以我们将使用 CR 作为标记来创建 CR、LF、引号和逗号的转义序列。
$str = str_replace("\r", "\rR", $matches[3]);
$str = str_replace("\n", "\rN", $str);
$str = str_replace('""', "\rQ", $str);
$str = str_replace(',', "\rC", $str);

// 未加引号的文本是允许使用逗号和换行符以及进行分割的地方
// 我们将通过将所有换行符标准化为 LF 来删除未加引号文本中的所有 CR
// 这确保我们 CR 仅用作加引号文本的转义序列
return preg_replace('/\r\n?/', "\n", $matches[1]) . $str;
}

// 按逗号分割并使用回调函数解析每个字段
function parse_csv_line($line)
{
return array_map('parse_csv_field', explode(',', $line));
}

// 恢复作为数据一部分的任何 csv 特殊字符
function parse_csv_field($field) {
$field = str_replace("\rC", ',', $field);
$field = str_replace("\rQ", '"', $field);
$field = str_replace("\rN", "\n", $field);
$field = str_replace("\rR", "\r", $field);
return $field;
}
V.Krishn
11 年前
<?php
注意
函数与 str_getcsv (v5.3) 不一样,它会去除所有值的空格。
/**
* @link https://github.com/insteps/phputils (更新后的代码)
* 为 php 4+ 解析 CSV 字符串到数组。
* @param string $input 字符串
* @param string $delimiter 字符串
* @param string $enclosure 字符串
* @return array
*/
function str_getcsv4($input, $delimiter = ',', $enclosure = '"') {

if( !
preg_match("/[$enclosure]/", $input) ) {
return (array)
preg_replace(array("/^\\s*/", "/\\s*$/"), '', explode($delimiter, $input));
}

$token = "##"; $token2 = "::";
//备用标记 "\034\034", "\035\035", "%%";
$t1 = preg_replace(array("/\\\[$enclosure]/", "/$enclosure{2}/",
"/[$enclosure]\\s*[$delimiter]\\s*[$enclosure]\\s*/", "/\\s*[$enclosure]\\s*/"),
array(
$token2, $token2, $token, $token), trim(trim(trim($input), $enclosure)));

$a = explode($token, $t1);
foreach(
$a as $k=>$v) {
if (
preg_match("/^{$delimiter}/", $v) || preg_match("/{$delimiter}$/", $v) ) {
$a[$k] = trim($v, $delimiter); $a[$k] = preg_replace("/$delimiter/", "$token", $a[$k]); }
}
$a = explode($token, implode($token, $a));
return (array)
preg_replace(array("/^\\s/", "/\\s$/", "/$token2/"), array('', '', $enclosure), $a);

}

if ( !
function_exists('str_getcsv')) {
function
str_getcsv($input, $delimiter = ',', $enclosure = '"') {
return
str_getcsv4($input, $delimiter, $enclosure);
}
}
?>
Jeremy
15 年前
在过去使用了几种方法来创建 CSV 字符串而不用文件(磁盘IO很糟糕)之后,我终于决定是时候编写一个函数来处理所有这些了。这个函数可以进行一些清理,变量类型测试对于所需内容来说可能有些过分,我还没有过多考虑。

另外,我冒昧地将某些数据类型的字段替换为字符串,我觉得这样更容易处理。你们中有些人可能不同意这种做法。另请注意,“double”或浮点型已专门编码为两位精度,因为如果我使用浮点数,它很可能用于货币。

我相信你们中的一些人会喜欢这个函数。

<?php
function str_putcsv($array, $delimiter = ',', $enclosure = '"', $terminator = "\n") {
# 首先将关联数组转换为数字索引数组
foreach ($array as $key => $value) $workArray[] = $value;

$returnString = ''; # 初始化返回字符串
$arraySize = count($workArray); # 获取数组大小

for ($i=0; $i<$arraySize; $i++) {
# 嵌套数组,处理嵌套项
if (is_array($workArray[$i])) {
$returnString .= str_putcsv($workArray[$i], $delimiter, $enclosure, $terminator);
} else {
switch (
gettype($workArray[$i])) {
# 手动设置一些字符串
case "NULL": $_spFormat = ''; break;
case
"boolean": $_spFormat = ($workArray[$i] == true) ? 'true': 'false'; break;
# 确保 sprintf 有一个良好的数据类型来处理
case "integer": $_spFormat = '%i'; break;
case
"double": $_spFormat = '%0.2f'; break;
case
"string": $_spFormat = '%s'; break;
# CSV 中的未知或无效项 - 注意:上面已经处理了数组的数据类型,假设数据是嵌套的
case "object":
case
"resource":
default:
$_spFormat = ''; break;
}
$returnString .= sprintf('%2$s'.$_spFormat.'%2$s', $workArray[$i], $enclosure);
$returnString .= ($i < ($arraySize-1)) ? $delimiter : $terminator;
}
}
# 完成工作负载,返回输出信息
return $returnString;
}

?>
keananda at gmail dot com
16年前
对于那些需要此函数但尚未安装在其环境中的用户,您可以使用我下面的函数。

您可以将您的 csv 文件解析为每行的关联数组(默认情况下)或对象。
<?php
function parse_csv($file, $options = null) {
$delimiter = empty($options['delimiter']) ? "," : $options['delimiter'];
$to_object = empty($options['to_object']) ? false : true;
$str = file_get_contents($file);
$lines = explode("\n", $str);
pr($lines);
$field_names = explode($delimiter, array_shift($lines));
foreach (
$lines as $line) {
// 跳过空行
if (empty($line)) continue;
$fields = explode($delimiter, $line);
$_res = $to_object ? new stdClass : array();
foreach (
$field_names as $key => $f) {
if (
$to_object) {
$_res->{$f} = $fields[$key];
} else {
$_res[$f] = $fields[$key];
}
}
$res[] = $_res;
}
return
$res;
}
?>

注意
csv 文件的第 1 行将被视为标题(字段名)。

待办事项
- 处理包围符
- 处理转义字符
- 根据您的需要添加其他功能/增强功能

使用示例
/path/to/file.csv 的内容
CODE,COUNTRY
AD,Andorra
AE,United Arab Emirates
AF,Afghanistan
AG,Antigua and Barbuda

<?php
$arr_csv
= parse_csv("/path/to/file.csv");
print_r($arr_csv);
?>
// 输出
数组
(
[0] => 数组
(
[CODE] => AD
[COUNTRY] => 安道尔
)
[1] => 数组
(
[CODE] => AE
[COUNTRY] => 阿拉伯联合酋长国
)
[2] => 数组
(
[CODE] => AF
[COUNTRY] => 阿富汗
)
[3] => 数组
(
[CODE] => AG
[COUNTRY] => 安提瓜和巴布达
)
)

<?php
$obj_csv
= parse_csv("/path/to/file.csv", array("to_object" => true));
print_r($obj_csv);
?>
// 输出
数组
(
[0] => stdClass 对象
(
[CODE] => AD
[COUNTRY] => 安道尔
)
[1] => stdClass 对象
(
[CODE] => AE
[COUNTRY] => 阿拉伯联合酋长国
)
[2] => stdClass 对象
(
[CODE] => AF
[COUNTRY] => 阿富汗
)
[3] => stdClass 对象
(
[CODE] => AG
[COUNTRY] => 安提瓜和巴布达
)
[4] => stdClass 对象
(
[CODE] =>
[COUNTRY] =>
)
)

// 如果您在 csv 文件中使用字符 |(管道)作为分隔符,请使用
<?php
$arr_csv
= parse_csv("/path/to/file.csv", array("delimiter"=>"|"));
?>

==NSD==
hpartidas at deuz dot net
14 年前
我发现自己需要解析 CSV,但无法访问 str_getcsv,所以我为 PHP < 5.3 编写了替代函数,希望它能帮助遇到同样情况的人。

<?php
if (!function_exists('str_getcsv')) {
function
str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = '\\', $eol = '\n') {
if (
is_string($input) && !empty($input)) {
$output = array();
$tmp = preg_split("/".$eol."/",$input);
if (
is_array($tmp) && !empty($tmp)) {
while (list(
$line_num, $line) = each($tmp)) {
if (
preg_match("/".$escape.$enclosure."/",$line)) {
while (
$strlen = strlen($line)) {
$pos_delimiter = strpos($line,$delimiter);
$pos_enclosure_start = strpos($line,$enclosure);
if (
is_int($pos_delimiter) && is_int($pos_enclosure_start)
&& (
$pos_enclosure_start < $pos_delimiter)
) {
$enclosed_str = substr($line,1);
$pos_enclosure_end = strpos($enclosed_str,$enclosure);
$enclosed_str = substr($enclosed_str,0,$pos_enclosure_end);
$output[$line_num][] = $enclosed_str;
$offset = $pos_enclosure_end+3;
} else {
if (empty(
$pos_delimiter) && empty($pos_enclosure_start)) {
$output[$line_num][] = substr($line,0);
$offset = strlen($line);
} else {
$output[$line_num][] = substr($line,0,$pos_delimiter);
$offset = (
!empty(
$pos_enclosure_start)
&& (
$pos_enclosure_start < $pos_delimiter)
)
?
$pos_enclosure_start
:$pos_delimiter+1;
}
}
}
$line = substr($line,$offset);
}
} else {
$line = preg_split("/".$delimiter."/",$line);

/*
* 校验恼人的额外换行符,防止创建错误的行。
*/
if (is_array($line) && !empty($line[0])) {
$output[$line_num] = $line;
}
}
}
return
$output;
} else {
return
false;
}
} else {
return
false;
}
}
}
?>
Xkang
7年前
如何解决UTF-8 BOM问题
如何处理UTF-8编码的CSV文件中的BOM问题
$bom =( chr(0xEF) . chr(0xBB) . chr(0xBF) ); //定义BOM
$f = file_get_contents('a.csv'); //打开CSV文件
#$csv = str_getcsv($f); //这样会出现BOM问题
$csv = str_getcsv(str_replace($bom,'',$f)); //替换掉BOM
var_dump($csv); //输出
匿名用户
15 年前
由于某种原因,o'connor 的代码只能读取我 CSV 文件的一行……我不得不替换这一行:

$data = fgetcsv($fp, 1000, $delimiter, $enclosure); // $escape 只在 5.3.0 版本中添加

为:

$data;
while (!feof($fp))
{
$data[] = fgetcsv($fp, 0, $delimiter, $enclosure); // $escape 只在 5.3.0 版本中添加
}

……才能从我的字符串中获取所有数据(一些粘贴到文本框中并仅使用 stripslashes 处理的帖子数据)。
khelibert at gmail dot com
12年前
我编写了这段代码来处理:
- 带或不带引号的字段;
- 使用相同字符的转义符和引号字符(例如,Excel 中的 <<">>)

<?php
/**
* 将csv文件转换为行和列数组。
* [email protected]
* @param $fileContent 字符串
* @param string $escape 字符串
* @param string $enclosure 字符串
* @param string $delimiter 字符串
* @return array
*/
function csvToArray($fileContent,$escape = '\\', $enclosure = '"', $delimiter = ';')
{
$lines = array();
$fields = array();

if(
$escape == $enclosure)
{
$escape = '\\';
$fileContent = str_replace(array('\\',$enclosure.$enclosure,"\r\n","\r"),
array(
'\\\\',$escape.$enclosure,"\\n","\\n"),$fileContent);
}
else
$fileContent = str_replace(array("\r\n","\r"),array("\\n","\\n"),$fileContent);

$nb = strlen($fileContent);
$field = '';
$inEnclosure = false;
$previous = '';

for(
$i = 0;$i<$nb; $i++)
{
$c = $fileContent[$i];
if(
$c === $enclosure)
{
if(
$previous !== $escape)
$inEnclosure ^= true;
else
$field .= $enclosure;
}
else if(
$c === $escape)
{
$next = $fileContent[$i+1];
if(
$next != $enclosure && $next != $escape)
$field .= $escape;
}
else if(
$c === $delimiter)
{
if(
$inEnclosure)
$field .= $delimiter;
else
{
//字段结束
$fields[] = $field;
$field = '';
}
}
else if(
$c === "\n")
{
$fields[] = $field;
$field = '';
$lines[] = $fields;
$fields = array();
}
else
$field .= $c;
$previous = $c;
}
//添加最后一个元素
if(true || $field !== '')
{
$fields[] = $field;
$lines[] = $fields;
}
return
$lines;
}
?>
[email protected]
9 年前
> 49 [email protected] / 4年前
$rows = str_getcsv($csv_data, "\n");
- bug,csv中的数据可能包含"\n"
'aaa','bb
b','ccc'
[email protected]
1年前
旧版MacOS(直到约2001年)和旧版Office for MacOS(直到2007年?我认为)使用回车符作为换行符,
Microsoft Windows使用回车符+换行符作为换行符,
Unix(Linux和现代MacOS)使用换行符,
某些系统使用BOM/字节顺序标记来表示它们使用UTF-8,我甚至遇到过每个CSV行一个BOM的情况!

为了处理上述所有情况的csv文件解析器,我编写了

<?php
function parse_csv(string $csv, string $separator = ","): array
{
$csv = strtr(
$csv,
[
"\xEF\xBB\xBF" => "", // 删除UTF-8字节顺序标记(如果存在)
"\r\n" => "\n", // Windows CrLf=> Unix Lf
"\r" => "\n" // 旧版MacOS Cr => Unix Lf
// (现代MacOS和Linux都使用Lf .. Windows是唯一例外)
]
);
$lines = explode("\n", $csv);
$keys = str_getcsv(array_shift($lines), $separator);
$ret = array();
foreach (
$lines as $lineno => $line) {
if (
strlen($line) < 1) {
// ...可能格式错误的csv,但我们将允许它
continue;
}
$parsed = str_getcsv($line, $separator);
if (
count($parsed) !== count($keys)) {
throw new
\RuntimeException("csv第{{$lineno}}行错误:计数不匹配:" . count($parsed) . ' !== ' . count($keys) . ": " . var_export([
'error' => '计数不匹配',
'keys' => $keys,
'parsed' => $parsed,
'line' => $line
], true));
}
$ret[] = array_combine($keys, $parsed);
}
return
$ret;
}
?>
manngo
1年前
我没有在描述中看到这一点,但它似乎表明字段的尾随换行符将被稍微修剪。

在以下示例中

<?php
$string
= "\nPHP\r\n,Java\nScript\r\n\r\n,Fortran\n,Cobol\n\n,\nSwift\r\n\r\n\r\n";
$data = str_getcsv($string);
foreach(
$data as $d) print "[$d]";

/* 结果:
================================================
[
PHP][Java
Script
][Fortran][Cobol
][
Swift
]
================================================ */
?>

你会看到

- 保留了前导换行符;字段其余部分中的换行符也保留。
- 删除了一个尾随换行符;任何更多都保留。
- 还删除了字符串末尾的换行符;这意味着删除了末尾的两个尾随换行符。
- 换行符可以是unix/macos换行符(\n)或windows换行符(\r\n)。

在我的Macintosh上测试过,所以我不确定这有多通用。

除此之外,这意味着您可以使用file()函数读取文件,而无需包含FILE_IGNORE_NEW_LINES标志。
[email protected]
10个月前
为了与标准(RFC-4180)CSV文件最大程度地兼容,请记住应禁用专有转义机制。即,将可选的第五个参数设置为""(空字符串)。
Wade Rossmann
2年前
为了完整起见,这里有一个与fgetcsv()和fputcsv()的参数完全兼容的用户空间str_putcsv()。即$escape和$eol,其他所有函数似乎都省略了这些参数。

<?php

函数 str_putcsv(
数组
$fields,
字符串 $separator = ",",
字符串 $enclosure = "\"",
字符串 $escape = "\\",
字符串 $eol = "\n"
) {
返回
implode($separator,
array_map(
函数(
$a)使用($enclosure, $escape) {
$type = gettype($a);
switch(
$type) {
case
'integer': 返回 sprintf('%d', $a);
case
'double': 返回 rtrim(sprintf('%0.'.ini_get('precision').'f', $a), '0');
case
'boolean': 返回 ( $a ? 'true' : 'false' );
case
'NULL': 返回 '';
case
'string':
返回
sprintf('"%s"', str_replace(
[
$escape, $enclosure],
[
$escape.$escape, $escape.$enclosure],
$a
));
default: 抛出新
TypeError("无法将类型转换为字符串: $type");
}
},
$fields
)
) .
$eol;
}
[email protected]
3年前
想象一下,你需要一个同时处理URL和逗号分隔文本的函数。

这个函数正是这样工作的,它使用了`str_getcsv()`。只需插入CSV URL或逗号分隔文本,它就能很好地工作。

<?php
函数 parse_csv( $filename_or_text, $delimiter=',', $enclosure='"', $linebreak="\n" )
{
$return = 数组();

if(
false !== ($csv = (filter_var($filename_or_text, FILTER_VALIDATE_URL) ? file_get_contents($filename_or_text) : $filename_or_text)))
{
$csv = trim($csv);
$csv = mb_convert_encoding($csv, 'UTF-16LE');

foreach(
str_getcsv($csv, $linebreak, $enclosure) as $row){
$col = str_getcsv($row, $delimiter, $enclosure);
$col = array_map('trim', $col);
$return[] = $col;
}
}
else
{
抛出新
\Exception('无法打开文件。');
$return = false;
}

返回
$return;
}
?>
[email protected]
3年前
有时`str_getcsv`函数的`enclosure`参数不起作用,所以我编写了一个等效的函数。

<?php
/**
* @param string $input 输入字符串
* @param string $delimiter 分隔符
* @param string $enclosure 闭合符
* @param string $escape 转义符
* @return array 返回数组
* @author TXX
* @date 2021/1/25 15:03
*/
function my_str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = '\\') {
$output = array();

if (empty(
$input) || !is_string($input)) {
return
$output;
}

if (
preg_match("/". $escape . $enclosure ."/", $input)) {
while (
$strlen = strlen($input)) {
$pos_delimiter = strpos($input, $delimiter); //分隔符出现位置
$pos_enclosure_start = strpos($input, $enclosure); //闭合符-开始出现位置

//有闭合符且闭合符在分隔符之前
if (is_int($pos_delimiter) && is_int($pos_enclosure_start) && $pos_enclosure_start < $pos_delimiter) {
$pos_enclosure_start += 1;
$enclosed_str = substr($input, $pos_enclosure_start); //闭合字符串-开始
$pos_enclosure_end = strpos($enclosed_str, $enclosure); //闭合符-结尾闭合字符串-开始中出现位置
$pos_enclosure_end += $pos_enclosure_start; //闭合符-结尾在原始数据中出现位置

if ($pos_enclosure_end < $pos_delimiter) {
//闭合符-结束在分隔符之前,无需进行闭合
$output[] = substr($input, 0, $pos_delimiter);
$offset = $pos_delimiter + 1;
} else {
//闭合符-结束在分隔符之后,需要闭合
$pos_enclosure_end += 1;
$before_enclosed_str = substr($input, 0, $pos_enclosure_end);
$enclosed_str = substr($input, $pos_enclosure_end); //闭合字符串之后的字符串

$enclosed_arr = my_str_getcsv($enclosed_str, $delimiter, $enclosure); //将闭合之后的字符串执行自身
$enclosed_arr[0] = $before_enclosed_str . $enclosed_arr[0];

$output = array_merge($output, $enclosed_arr);
$offset = strlen($input); //光标移至结尾
}
} else {
//无闭合
if (!is_int($pos_delimiter)) {
//无分隔符,直接将字符串加入输出数组
$output[] = $input;
//光标移至结尾
$offset = strlen($input);
} else if (
$input == $delimiter) {
//如果字符串只剩下分隔符,需保存'',''
$output = array_merge($output, ['','']);
$offset = $pos_delimiter+1; //光标移至分隔符后一位
} else {
$output[] = substr($input, 0, $pos_delimiter); //将分隔符之前的数据
$offset = $pos_delimiter+1; //光标移至分隔符后一位
}
}
//将字符串更新至光标位置
$input = substr($input,$offset);
}
} else {
//字符串中不存在闭合符,直接通过分隔符分割
$input = preg_split("/". $escape . $delimiter ."/", $input);

if (
is_array($input)) {
$output = $input;
}
}

return
$output;
}

?>
匿名用户
4年前
请注意,此函数不会移除转义字符。如果您使用

<?php
str_getcsv
('"abc\"abc"')
?>

您将得到一个包含字符串(8) "abc\"abc" 的数组,反斜杠'\' 将保留。
pasmanik at gmail dot com
9 年前
我准备了一个更好的函数来解析CSV字符串。

function csv_to_array($string='', $row_delimiter=PHP_EOL, $delimiter = "," , $enclosure = '"' , $escape = "\\" )
{
$rows = array_filter(explode($row_delimiter, $string));
$header = NULL;
$data = array();

foreach($rows as $row)
{
$row = str_getcsv ($row, $delimiter, $enclosure , $escape);

if(!$header)
$header = $row;
else
$data[] = array_combine($header, $row);
}

return $data;
}
V.Krishn
11 年前
注意:与str_getcsv (v5.3)不同,此函数会修剪所有值。
/**
* @link https://github.com/insteps/phputils (更新后的代码)
* 为php 4+版本解析CSV字符串。
* @param string $input 字符串
* @param string $delimiter 字符串
* @param string $enclosure 字符串
* @return array
*/
function str_getcsv4($input, $delimiter = ',', $enclosure = '"') {

if( ! preg_match("/[$enclosure]/", $input) ) {
return (array)preg_replace(array("/^\\s*/", "/\\s*$/"), '', explode($delimiter, $input));
}

$token = "##"; $token2 = "::";
//备用标记 "\034\034", "\035\035", "%%";
$t1 = preg_replace(array("/\\\[$enclosure]/", "/$enclosure{2}/",
"/[$enclosure]\\s*[$delimiter]\\s*[$enclosure]\\s*/", "/\\s*[$enclosure]\\s*/"),
array($token2, $token2, $token, $token), trim(trim(trim($input), $enclosure)));

$a = explode($token, $t1);
foreach($a as $k=>$v) {
if ( preg_match("/^{$delimiter}/", $v) || preg_match("/{$delimiter}$/", $v) ) {
$a[$k] = trim($v, $delimiter); $a[$k] = preg_replace("/$delimiter/", "$token", $a[$k]); }
}
$a = explode($token, implode($token, $a));
return (array)preg_replace(array("/^\\s/", "/\\s$/", "/$token2/"), array('', '', $enclosure), $a);

}

if ( ! function_exists('str_getcsv')) {
function str_getcsv($input, $delimiter = ',', $enclosure = '"') {
return str_getcsv4($input, $delimiter, $enclosure);
}
}
xoneca at gmail dot com
13 年前
请注意,此函数也可用于解析其他类型的结构。例如,我已使用它来解析.htaccess AddDescription 行

AddDescription "My description to the file." filename.jpg

这些行可以这样解析

<?php

$line
= 'AddDescription "My description to the file." filename.jpg';

$parsed = str_getcsv(
$line, # 输入行
' ', # 分隔符
'"', # 闭合符
'\\' # 转义符
);

var_dump( $parsed );

?>

输出结果

array(3) {
[0]=>
string(14) "AddDescription"
[1]=>
string(27) "My description to the file."
[2]=>
string(12) "filename.jpg"
}
dave_walter at NOSPAM dot yahoo dot com
15 年前
从daniel dot oconnor at gmail dot com获得灵感,这里有一个替代的str_putcsv(),它利用现有的PHP核心功能(5.1.0+)来避免重新发明轮子。

<?php
if(!function_exists('str_putcsv')) {
function
str_putcsv($input, $delimiter = ',', $enclosure = '"') {
// 打开一个内存“文件”进行读写…
$fp = fopen('php://temp', 'r+');
// …使用 fputcsv() 将 $input 数组写入“文件”...
fputcsv($fp, $input, $delimiter, $enclosure);
// …倒回“文件”指针,以便读取我们刚刚写入的内容...
rewind($fp);
// …将整行读取到一个变量中...
$data = fgets($fp);
// …关闭“文件”...
fclose($fp);
// …并将 $data 返回给调用方,并去除 fgets() 添加的尾随换行符。
return rtrim( $data, "\n" );
}
}
?>
william dot j dot weir at gmail dot com
16年前
如果您对只使用多维数组感到满意,那么这段代码应该可以正常工作。我原本想使用keananda提供的那个,但是它在pr($lines)处卡住了。

<?php
function f_parse_csv($file, $longest, $delimiter) {
$mdarray = array();
$file = fopen($file, "r");
while (
$line = fgetcsv($file, $longest, $delimiter)) {
array_push($mdarray, $line);
}
fclose($file);
return
$mdarray;
}
?>

$longest 是一个数字,表示 fgetcsv() 所需的 csv 文件中最长的一行的长度。fgetcsv() 的页面上说最长行可以设置为 0 或省略,但我无法在没有设置的情况下使其工作。在我必须使用它时,我只是将其设置得非常大。
To Top