fgetcsv

(PHP 4, PHP 5, PHP 7, PHP 8)

fgetcsv从文件指针获取行并解析为 CSV 字段

说明

fgetcsv(
    资源 $stream,
    ?整数 $length = null,
    字符串 $separator = ",",
    字符串 $enclosure = "\"",
    字符串 $escape = "\\"
): 数组|false

类似于 fgets(),除了 fgetcsv() 解析它读取的行的 CSV 格式的字段,并返回包含读取的字段的数组。

注意:

此函数将考虑区域设置。如果 LC_CTYPE 为例如 en_US.UTF-8,则此函数可能会错误地读取单字节编码的文件。

参数

stream

一个有效的文件指针,指向由 fopen()popen()fsockopen() 成功打开的文件。

length

必须大于 CSV 文件中要找到的最长行(以字符为单位)(允许有尾随的换行符)。否则,行将被分割成 length 个字符的块,除非分割发生在封闭符内。

省略此参数(或将其设置为 0,或在 PHP 8.0.0 或更高版本中设置为 null)则最大行长度不受限制,这会稍微慢一些。

separator

可选的 separator 参数设置字段分隔符(仅一个单字节字符)。

enclosure

可选的 enclosure 参数设置字段封闭符(仅一个单字节字符)。

escape

可选的 escape 参数设置转义符(最多一个单字节字符)。空字符串 ("") 禁用专有转义机制。

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

返回值

如果成功,则返回一个包含读取的字段的索引数组;如果失败,则返回 false

注意:

CSV 文件中的空白行将作为包含单个 null 字段的数组返回,不会被视为错误。

注意: 如果 PHP 在读取文件时无法正确识别行结束符,或者是在 Mac 电脑上创建的文件,则启用 auto_detect_line_endings 运行时配置选项可能有助于解决问题。

变更日志

版本 说明
8.0.0 length 现在可以为空。
7.4.0 escape 参数现在也接受空字符串以禁用专有转义机制。

范例

范例 #1 读取并打印 CSV 文件的全部内容

<?php
$row
= 1;
if ((
$handle = fopen("test.csv", "r")) !== FALSE) {
while ((
$data = fgetcsv($handle, 1000, ",")) !== FALSE) {
$num = count($data);
echo
"<p> $num 行中的字段 $row: <br /></p>\n";
$row++;
for (
$c=0; $c < $num; $c++) {
echo
$data[$c] . "<br />\n";
}
}
fclose($handle);
}
?>

参见

  • str_getcsv() - 将 CSV 字符串解析为数组
  • explode() - 通过字符串拆分字符串
  • file() - 将整个文件读入数组
  • pack() - 将数据打包成二进制字符串
  • fputcsv() - 将行格式化为 CSV 并写入文件指针

添加说明

用户贡献的说明 39 说明

james dot ellis at gmail dot com
15 年前
如果需要设置 auto_detect_line_endings 来处理 Mac 行结束符,这可能看起来很明显,但请记住,它应该在 fopen 之前设置,而不是之后

这将起作用
<?php
ini_set
('auto_detect_line_endings',TRUE);
$handle = fopen('/path/to/file','r');
while ( (
$data = fgetcsv($handle) ) !== FALSE ) {
// 处理
}
ini_set('auto_detect_line_endings',FALSE);
?>

这将不起作用,您仍然会在换行符位置获得连接的字段
<?php
$handle
= fopen('/path/to/file','r');
ini_set('auto_detect_line_endings',TRUE);
while ( (
$data = fgetcsv($handle) ) !== FALSE ) {
// 处理
}
ini_set('auto_detect_line_endings',FALSE);
?>
shaun at slickdesign dot com dot au
6 年前
当提供 BOM 字符时,`fgetscsv` 似乎将第一个元素包装在“双引号”中。忽略它的最简单方法是在使用 `fgetcsv` 之前将文件指针移到第四个字节。

<?php
// BOM 作为字符串用于比较。
$bom = "\xef\xbb\xbf";

// 从开头读取文件。
$fp = fopen($path, 'r');

// 移动文件指针并获取前 3 个字符以与 BOM 字符串进行比较。
if (fgets($fp, 4) !== $bom) {
// 未找到 BOM - 将指针倒回文件开头。
rewind($fp);
}

// 将 CSV 读取到数组中。
$lines = array();
while(!
feof($fp) && ($line = fgetcsv($fp)) !== false) {
$lines[] = $line;
}
?>
灰袍甘道夫
7 年前
忘记这个 while() 循环的胡言乱语吧!使用这个

$rows = array_map('str_getcsv', file('myfile.csv'));
$header = array_shift($rows);
$csv = array();
foreach ($rows as $row) {
$csv[] = array_combine($header, $row);
}

来源: https://steindom.com/articles/shortest-php-code-convert-csv-associative-array
michael dot arnauts at gmail dot com
12 年前
fgetcsv 似乎可以很好地处理字段内的换行符。所以实际上它不是读取一行,而是不断读取,直到找到一个未被引用为字段的 \n 字符。

示例

<?php
/* test.csv 包含:
"col 1","col2","col3"
"this
is
having
multiple
lines","this not","this also not"
"normal record","nothing to see here","no data"
*/

$handle = fopen("test.csv", "r");
while ((
$data = fgetcsv($handle)) !== FALSE) {
var_dump($data);
}
?>

返回
array(3) {
[0]=>
string(5) "col 1"
[1]=>
string(4) "col2"
[2]=>
string(4) "col3"
}
array(3) {
[0]=>
string(29) "this
is
having
multiple
lines"
[1]=>
string(8) "this not"
[2]=>
string(13) "this also not"
}
array(3) {
[0]=>
string(13) "normal record"
[1]=>
string(19) "nothing to see here"
[2]=>
string(7) "no data"
}

这意味着您可以预期 fgetcsv 能够很好地处理字段内的换行符。这在文档中并不清楚。
i at camerongreen dot org
6 年前
这种风格在此页面上以及互联网上的许多示例中都显示为示例

<?php
$rows
= array_map('str_getcsv', file('myfile.csv'));
?>

请注意,这不会处理 CSV 字段内的换行符,因此应避免使用。
Sbastien
4 年前
要将 fgetcsv() 与字符串而不是文件一起使用,可以使用 data: 包装器 https://php.net/wrappers.data

<?php

$csv
= <<<CSV
v1.1,v1.2,v1.3
v2.1,v2.2,v2.3
CSV;

$fp = fopen('data://text/plain,' . $csv, 'r');

print_r(fgetcsv($fp));
print_r(fgetcsv($fp));

/*

Array
(
[0] => v1.1
[1] => v1.2
[2] => v1.3
)
Array
(
[0] => v2.1
[1] => v2.2
[2] => v2.3
)

*/

?>
myrddin at myrddin dot myrddin
18 年前
这是一个基于 OOP 的导入器,类似于之前发布的导入器。但是,它在灵活性方面略胜一筹,因为您可以导入大型文件而不会出现内存不足的问题,您只需要在 get() 方法中使用限制即可

小型文件示例用法:-
-------------------------------------
<?php
$importer
= new CsvImporter("small.txt",true);
$data = $importer->get();
print_r($data);
?>


大型文件示例用法:-
-------------------------------------
<?php
$importer
= new CsvImporter("large.txt",true);
while(
$data = $importer->get(2000))
{
print_r($data);
}
?>


以下是该类:-
-------------------------------------
<?php
class CsvImporter
{
private
$fp;
private
$parse_header;
private
$header;
private
$delimiter;
private
$length;
//--------------------------------------------------------------------
function __construct($file_name, $parse_header=false, $delimiter="\t", $length=8000)
{
$this->fp = fopen($file_name, "r");
$this->parse_header = $parse_header;
$this->delimiter = $delimiter;
$this->length = $length;
$this->lines = $lines;

if (
$this->parse_header)
{
$this->header = fgetcsv($this->fp, $this->length, $this->delimiter);
}

}
//--------------------------------------------------------------------
function __destruct()
{
if (
$this->fp)
{
fclose($this->fp);
}
}
//--------------------------------------------------------------------
function get($max_lines=0)
{
// 如果 $max_lines 设置为 0,则获取所有数据

$data = array();

if (
$max_lines > 0)
$line_count = 0;
else
$line_count = -1; // 因此循环限制被忽略

while ($line_count < $max_lines && ($row = fgetcsv($this->fp, $this->length, $this->delimiter)) !== FALSE)
{
if (
$this->parse_header)
{
foreach (
$this->header as $i => $heading_i)
{
$row_new[$heading_i] = $row[$i];
}
$data[] = $row_new;
}
else
{
$data[] = $row;
}

if (
$max_lines > 0)
$line_count++;
}
return
$data;
}
//--------------------------------------------------------------------

}
?>
chris at ocproducts dot com
7 年前
此函数没有特殊的 BOM 处理。第一行第一个单元格将继承 BOM 字节,即比预期长 3 个字节。由于 BOM 是不可见的,您可能不会注意到。

Windows 上的 Excel 或 Notepad 等文本编辑器可能会添加 BOM。
jc at goetc dot net
20 年前
最近我参与了很多处理 CSV 文件的项目,所以我创建了以下类来读取 CSV 文件并返回一个包含列名作为键的二维数组。唯一的要求是第一行包含列标题。

我今天才编写它,所以将来可能会扩展它。

<?php
class CSVparse
{
var
$mappings = array();

function
parse_file($filename)
{
$id = fopen($filename, "r"); //打开文件
$data = fgetcsv($id, filesize($filename)); /*这将获取我们 */
/*主要列名 */

if(!$this->mappings)
$this->mappings = $data;

while(
$data = fgetcsv($id, filesize($filename)))
{
if(
$data[0])
{
foreach(
$data as $key => $value)
$converted_data[$this->mappings[$key]] = addslashes($value);
$table[] = $converted_data; /* 将每行放入 */
} /* 它在 */
} /* $table 数组中 */
fclose($id); //关闭文件
return $table;
}
}
?>
michael dot martinek at gmail dot com
15 年前
这是我今天早上整理的内容。它允许您从 CSV 文件中读取行并根据列名获取值。当您的标题列不总是按相同顺序排列时,这非常有用;比如当您处理来自不同客户的许多数据源时。也使代码更简洁,更易于管理。

所以如果您的数据源看起来像这样

product_id,category_name,price,brand_name, sku_isbn_upc,image_url,landing_url,title,description
123,Test Category,12.50,No Brand,0,http://www.example.com, http://www.example.com/landing.php, Some Title,Some Description

您可以执行
<?php
while ($o->getNext())
{
$dPrice = $o->getPrice();
$nProductID = $o->getProductID();
$sBrandName = $o->getBrandName();
}
?>

如果您对此类有任何疑问或意见,可以发送至 [email protected],因为我可能不会再到这里查看。

<?php
define
('C_PPCSV_HEADER_RAW', 0);
define('C_PPCSV_HEADER_NICE', 1);

class
PaperPear_CSVParser
{
private
$m_saHeader = array();
private
$m_sFileName = '';
private
$m_fp = false;
private
$m_naHeaderMap = array();
private
$m_saValues = array();

function
__construct($sFileName)
{
//快速且肮脏的打开和处理.. 您可能希望清理它
if ($this->m_fp = fopen($sFileName, 'r'))
{
$this->processHeader();
}
}

function
__call($sMethodName, $saArgs)
{
//检查以查看这是一个 set() 还是 get() 请求,并提取名称
if (preg_match("/[sg]et(.*)/", $sMethodName, $saFound))
{
//将 [gs]et 的名称部分转换为大写以进行标题检查
$sName = strtoupper($saFound[1]);

//查看该条目是否存在于我们的命名标题-> 索引映射中
if (array_key_exists($sName, $this->m_naHeaderMap))
{
//确实存在.. 所以查询标题映射以查看此标题控制的索引
$nIndex = $this->m_naHeaderMap[$sName];
if (
$sMethodName{0} == 'g')
{
//返回存储在此名称关联的索引中的值
return $this->m_saValues[$nIndex];
}
else
{
//设置值
$this->m_saValues[$nIndex] = $saArgs[0];
return
true;
}
}
}

//我们不控制任何东西,所以用 false 退出
return false;
}

//获取格式良好的标题名称。这将把 product_id 变为
//标题映射中的 PRODUCTID。因此现在您不必担心是否需要
//执行 getProductID、getproductid 或 getProductId.. 所有操作都将起作用。
public static function GetNiceHeaderName($sName)
{
return
strtoupper(preg_replace('/[^A-Za-z0-9]/', '', $sName));
}

//处理标题条目,以便我们可以将我们命名的标题字段映射到数字索引,我们
//将在使用 fgetcsv() 时使用。
private function processHeader()
{
$sLine = fgets($this->m_fp);
//您可能希望将此配置
$saFields = split(",", $sLine);

$nIndex = 0;
foreach (
$saFields as $sField)
{
//获取要用于“get”和“set”的漂亮名称。
$sField = trim($sField);

$sNiceName = PaperPear_CSVParser::GetNiceHeaderName($sField);

//跟踪原始名称-> 美化名称的相关性,因此我们不必进行即时的美化名称检查
$this->m_saHeader[$nIndex] = array(C_PPCSV_HEADER_RAW => $sField, C_PPCSV_HEADER_NICE => $sNiceName);
$this->m_naHeaderMap[$sNiceName] = $nIndex;
$nIndex++;
}
}

//读取下一个 CSV 条目
public function getNext()
{
//这是一个基本的读取操作,您可能希望更改它以适应您
//用于 CSV 参数的内容(制表符、封装等)。
if (($saValues = fgetcsv($this->m_fp)) !== false)
{
$this->m_saValues = $saValues;
return
true;
}
return
false;
}
}


//快速使用示例
$o = new PaperPear_CSVParser('F:\foo.csv');
while (
$o->getNext())
{
echo
"Price=" . $o->getPrice() . "\r\n";
}

?>
Tim Henderson
16 年前
fgetcsv() 的唯一问题,至少在 PHP 4.x 中,是数据中出现的任何转义斜杠,如果碰巧出现在双引号分隔符之前,就会破坏它,即导致字段分隔符被转义。我找不到直接处理它的方法,因为 fgetcsv() 不会给你机会在读取和解析它之前操纵该行...我不得不先将文件中所有出现的“\”更改为“”,然后才能将其提供给 fgetcsv()。否则,这对于 Microsoft-CSV 公式来说非常完美,可以优雅地处理所有问题。
phpnet at smallfryhosting dot co dot uk
20 年前
另一个版本 [由 mediaconcepts 中的 michael 修改]

<?php
function arrayFromCSV($file, $hasFieldNames = false, $delimiter = ',', $enclosure='') {
$result = Array();
$size = filesize($file) +1;
$file = fopen($file, 'r');
#TO DO: 必须有更好的方法来找出最长行的长度... 否则
if ($hasFieldNames) $keys = fgetcsv($file, $size, $delimiter, $enclosure);
while (
$row = fgetcsv($file, $size, $delimiter, $enclosure)) {
$n = count($row); $res=array();
for(
$i = 0; $i < $n; $i++) {
$idx = ($hasFieldNames) ? $keys[$i] : $i;
$res[$idx] = $row[i];
}
$result[] = $res;
}
fclose($file);
return
$result;
}
?>
tomasz at marcinkowski dot pl
10 年前
对于任何其他在单字节编码中遇到消失的非拉丁字符的人来说,设置 LANG 环境变量(如手册中所述)根本没有帮助。请查看 LC_ALL 而不是。

在我的情况下,它被设置为“pl_PL.utf8”,但由于我的输入文件是 CP1250,所以大多数波兰字符(但不是全部!)都消失了,而且“Łódź”这个城市变成了“dź”。我用“pl_PL”修复了它。
kent at marketruler dot com
14 年前
请注意,fgetcsv 至少在 PHP 5.3 或更早版本中,无法处理 UTF-16 编码的文件。您的选择是将整个文件转换为 ISO-8859-1(或 latin1),或者逐行转换并将每行转换为 ISO-8859-1 编码,然后使用 str_getcsv(或兼容的向后兼容实现)。如果您需要读取非拉丁字母,最好转换为 UTF-8。

有关 PHP < 5.3 的向后兼容版本的 str_getcsv,请参阅 str_getcsv,有关 Rasmus Andersson 编写的提供 utf16_decode 的函数,请参阅 utf8_decode。我添加的修改是,BOP 出现在文件顶部,然后不在后续行中出现。因此,您需要存储字节序,然后在每次后续行解码时重新发送它。此修改后的版本返回字节序(如果可用的话)

<?php
/**
* 解码 UTF-16 编码的字符串。
*
* 可以处理带 BOM 的数据和不带 BOM 的数据。
* 如果没有 BOM,则假定为大端字节序。
* 来自:https://php.net/manual/en/function.utf8-decode.php
*
* @param string $str 要解码的 UTF-16 编码数据。
* @return string UTF-8 / ISO 编码数据。
* @access public
* @version 0.1 / 2005-01-19
* @author Rasmus Andersson {@link http://rasmusandersson.se/}
* @package Groupies
*/
function utf16_decode($str, &$be=null) {
if (
strlen($str) < 2) {
return
$str;
}
$c0 = ord($str{0});
$c1 = ord($str{1});
$start = 0;
if (
$c0 == 0xFE && $c1 == 0xFF) {
$be = true;
$start = 2;
} else if (
$c0 == 0xFF && $c1 == 0xFE) {
$start = 2;
$be = false;
}
if (
$be === null) {
$be = true;
}
$len = strlen($str);
$newstr = '';
for (
$i = $start; $i < $len; $i += 2) {
if (
$be) {
$val = ord($str{$i}) << 4;
$val += ord($str{$i+1});
} else {
$val = ord($str{$i+1}) << 4;
$val += ord($str{$i});
}
$newstr .= ($val == 0x228) ? "\n" : chr($val);
}
return
$newstr;
}
?>

尝试“setlocale”技巧对我来说不起作用,例如:

<?php
setlocale
(LC_CTYPE, "en.UTF16");
$line = fgetcsv($file, ...)
?>

但这可能是因为我的平台不支持它。但是,fgetcsv 只支持单个字符作为分隔符等,并且在您传入所述字符的 UTF-16 版本时会抱怨,所以我很快就放弃了它。

希望这对某些人有所帮助。
junk at vhd dot com dot au
18 年前
fgetcsv 函数似乎遵循 MS excel 约定,这意味着

- 引号字符由自身转义,而不是反斜杠。
(例如,让我们使用双引号(“)作为引号字符

两个双引号“”将在解析后得到一个“,如果它们在引号字段内(否则它们都不会被删除)。

\" 将得到 \" 无论它是否在引号字段内(与 \\ 相同),以及

如果单个双引号在引号字段内,它将被删除。如果它不在引号字段内,它将保留)。

- 前导和尾随空格(\s 或 \t)永远不会被删除,无论它们是否在引号字段内。

- 如果字段内的换行符在引号字段内,则会正确处理。(因此,以前声称相反的评论是错误的,除非它们使用的是不同的 PHP 版本……我正在使用 4.4.0。)

所以 fgetcsv 实际上非常完整,可以处理所有可能的情况。(它确实需要帮助处理 Macintosh 换行符,如帮助文件中所述。)

我希望我一开始就知道所有这些。根据我自己的基准测试,fgetcsv 在内存消耗和速度之间取得了很好的平衡。

-------------------------
注意:如果使用反斜杠转义引号,则可以在之后轻松删除它们。前导和尾随空格也是如此。
sander at NOSPAM dot rotorsolutions dot nl
10 年前
如果您不想定义一个封闭字符,您可以执行以下操作

<?php
$row
= fgetcsv($handle, 0, $delimiter, 0x00);
?>

我需要使用这个代码来检测 CSV 文件中使用的分隔符。
nick at atomicdesign dot net
12 年前
在遍历 CSV 文件时,我遇到了字节耗尽错误。使用 ini_set('auto_detect_line_endings', 1); 修复了此问题。
jonathangrice at yahoo dot com
14 年前
以下是将 CSV 文件读入多维数组的方法。

<?php
# 打开文件。
if (($handle = fopen("file.csv", "r")) !== FALSE) {
# 将父多维数组的键设置为 0。
$nn = 0;
while ((
$data = fgetcsv($handle, 1000, ",")) !== FALSE) {
# 统计行中键的总数。
$c = count($data);
# 填充多维数组。
for ($x=0;$x<$c;$x++)
{
$csvarray[$nn][$x] = $data[$x];
}
$nn++;
}
# 关闭文件。
fclose($handle);
}
# 打印多维数组的内容。
print_r($csvarray);
?>
code at ashleyhunt dot co dot uk
13 年前
我需要一个函数来分析文件中的分隔符和换行符,以便在使用 LOAD DATA LOCAL INFILE 将文件导入 MySQL 时进行处理。

我编写了这个函数来完成这项工作,结果(大多数情况下)非常准确,并且在处理大型文件时也能很好地工作。
<?php
function analyse_file($file, $capture_limit_in_kb = 10) {
// 记录开始时的内存使用情况
$output['peak_mem']['start'] = memory_get_peak_usage(true);

// 记录采样文件的限制(单位:KB)
$output['read_kb'] = $capture_limit_in_kb;

// 读取文件
$fh = fopen($file, 'r');
$contents = fread($fh, ($capture_limit_in_kb * 1024)); // 单位:KB
fclose($fh);

// 指定允许的字段分隔符
$delimiters = array(
'comma' => ',',
'semicolon' => ';',
'tab' => "\t",
'pipe' => '|',
'colon' => ':'
);

// 指定允许的换行符
$line_endings = array(
'rn' => "\r\n",
'n' => "\n",
'r' => "\r",
'nr' => "\n\r"
);

// 循环并统计每个换行符出现的次数
foreach ($line_endings as $key => $value) {
$line_result[$key] = substr_count($contents, $value);
}

// 按数组值从大到小排序
asort($line_result);

// 记录到输出数组中
$output['line_ending']['results'] = $line_result;
$output['line_ending']['count'] = end($line_result);
$output['line_ending']['key'] = key($line_result);
$output['line_ending']['value'] = $line_endings[$output['line_ending']['key']];
$lines = explode($output['line_ending']['value'], $contents);

// 删除数组的最后一行,因为这行可能不完整?
array_pop($lines);

// 从合法行中创建字符串
$complete_lines = implode(' ', $lines);

// 记录统计信息到输出数组中
$output['lines']['count'] = count($lines);
$output['lines']['length'] = strlen($complete_lines);

// 循环并统计每个分隔符出现的次数
foreach ($delimiters as $delimiter_key => $delimiter) {
$delimiter_result[$delimiter_key] = substr_count($complete_lines, $delimiter);
}

// 按数组值从大到小排序
asort($delimiter_result);

// 将统计信息记录到输出数组中,并以最大计数作为值
$output['delimiter']['results'] = $delimiter_result;
$output['delimiter']['count'] = end($delimiter_result);
$output['delimiter']['key'] = key($delimiter_result);
$output['delimiter']['value'] = $delimiters[$output['delimiter']['key']];

// 记录结束时的内存使用情况
$output['peak_mem']['end'] = memory_get_peak_usage(true);
return
$output;
}
?>

示例用法
<?php
$Array
= analyse_file('/www/files/file.csv', 10);

// 可用部分示例
// $Array['delimiter']['value'] => ,
// $Array['line_ending']['value'] => \r\n
?>

完整函数输出
数组
(
[peak_mem] => 数组
(
[start] => 786432
[end] => 786432
)

[line_ending] => 数组
(
[results] => 数组
(
[nr] => 0
[r] => 4
[n] => 4
[rn] => 4
)

[count] => 4
[key] => rn
[value] =>

)

[lines] => 数组
(
[count] => 4
[length] => 94
)

[delimiter] => 数组
(
[results] => 数组
(
[colon] => 0
[semicolon] => 0
[pipe] => 0
[tab] => 1
[comma] => 17
)

[count] => 17
[key] => comma
[value] => ,
)

[read_kb] => 10
)

尽情享受!

Ashley
matthias dot isler at gmail dot com
14 年前
如果您想要为应用程序加载一些翻译,请不要使用 CSV 文件,即使它更容易处理。

以下代码片段

<?php
$lang
= array();

$handle = fopen('en.csv', 'r');

while(
$row = fgetcsv($handle, 500, ';'))
{
$lang[$row[0]] = $row[1];
}

fclose($handle);
?>

比这段代码慢大约 400%

<?php
$lang
= array();

$values = parse_ini_file('de.ini');

foreach(
$values as $key => $val)
{
$lang[$key] = $val;
}
?>

这就是为什么你应该始终使用 .ini 文件进行翻译...

https://php.net/parse_ini_file
matasbi at gmail dot com
13 年前
从 Microsoft Excel "Unicode 文本 (*.txt)" 格式解析

<?php
function parse($file) {
if ((
$handle = fopen($file, "r")) === FALSE) return;
while ((
$cols = fgetcsv($handle, 1000, "\t")) !== FALSE) {
foreach(
$cols as $key => $val ) {
$cols[$key] = trim( $cols[$key] );
$cols[$key] = iconv('UCS-2', 'UTF-8', $cols[$key]."\0") ;
$cols[$key] = str_replace('""', '"', $cols[$key]);
$cols[$key] = preg_replace("/^\"(.*)\"$/sim", "$1", $cols[$key]);
}
echo
print_r($cols, 1);
}
}
?>
daniel at softel dot jp
18 年前
请注意,fgetcsv() 使用系统区域设置来对字符编码做出假设。
因此,如果您尝试在 EUC-JP 服务器(例如)上处理 UTF-8 CSV 文件,
您需要在调用 fgetcsv() 之前执行以下操作

setlocale(LC_ALL, 'ja_JP.UTF8');

(另外,请注意 setlocale() 不会 *永久地* 影响系统区域设置)
from_php at puggan dot se
7 年前
设置 $escape 参数不会返回未转义的字符串,而只是避免在前面有转义字符的 $delimiter 上进行分割

<?php
$tmp_file
= "/tmp/test.csv";
file_put_contents($tmp_file, "\"first\\\";\\\"secound\"");
echo
"raw:" . PHP_EOL . file_get_contents($tmp_file) . PHP_EOL . PHP_EOL;

echo
"fgetcsv escaped bs:" . PHP_EOL;
$f = fopen($tmp_file, 'r');
while(
$r = fgetcsv($f, 1024, ';', '"', "\\"))
{
print_r($r);
}
fclose($f);
echo
PHP_EOL;

echo
"fgetcsv escaped #:" . PHP_EOL;
$f = fopen($tmp_file, 'r');
while(
$r = fgetcsv($f, 1024, ';', '"', "#"))
{
print_r($r);
}
fclose($f);
echo
PHP_EOL;
?>
ifedinachukwu at yahoo dot com
13 年前
我有一个 CSV 文件,其字段包含带有换行符的数据(由在 html 文本区域中按回车键创建的 CRLF)。当然,在创建 CSV 文件时,这些字段中的 LF 会被 MySQL 转义。问题是我无法让 fgetcsv 在这里正常工作,因为每个 LF 都被视为 CSV 文件行尾,即使它被转义了!

由于我想要的是获取 CSV 文件的 *第一行*,然后通过在所有未转义的逗号上进行 explode 来计算字段数量,我不得不求助于此

<?php
/*
CSV 的前五行:第 4 行在数据字段中有一个换行符。LF 代表换行符或 \n
1,okonkwo joseph,nil,2010-01-12 17:41:40LF
2,okafor john,cq and sulphonamides,2010-01-12 17:58:03LF
3,okoye andrew,lives with hubby in abuja,2011-03-30 13:39:19LF
4,okeke peter,In 2001\, had appendicectomy in AbaCR
\LF
In 2004\, had ELCS at a private hoapital in Lagos,2011-03-30 13:39:19LF
5,adewale chris,cq and sulphonamides,2010-01-12 17:58:03LF

*/

$fp = fopen('file.csv', 'r');
$i = 1;
$str='';
$srch='';
while (
false !== ($char = fgetc($fp))) {
$str .= $char;//使用此来收集要输出的字符串
$srch .= $char;//使用此来搜索 LF,可能由 \ 作为前缀
if(strlen($srch) > 2){
$srch = substr($srch, 1);//即去掉第一个字符
}
if(
$i > 1 && $srch[1] == chr(10) && $srch[0] != '\\'){//chr(10) 是 LF,即 \n
break;//如果您到达了 *未* 由 \ 作为前缀的 \n,那就是真正的行尾,停止收集字符串;
}

$i++;
}
echo
$str;//应该包含第一行作为字符串

?>
也许有更优雅的解决方案,如果有,我很乐意知道!
jaimthorn at yahoo dot com
14 年前
我使用 fgetcsv 读取以管道分隔的数据文件,并遇到了以下怪癖。

数据文件包含类似于以下内容的数据

RECNUM|TEXT|COMMENT
1|hi!|some comment
2|"error!|another comment
3|where does this go?|yet another comment
4|the end!"|last comment

我这样读取文件

<?php
$row
= fgetcsv( $fi, $length, '|' );
?>

这在记录 2 上会导致问题:管道后的引号会导致文件读取到下一个引号——在本例中,在记录 4 中。介于两者之间的所有内容都存储在 $row 的单个元素中。

在这个特定情况下很容易发现,但我的脚本正在处理数千条记录,我花了些时间才弄清楚问题出在哪里。

恼人的是,似乎没有优雅的解决方法。您无法告诉 PHP 不要使用围栏——例如,像这样

<?php
$row
= fgetcsv( $fi, $length, '|', '' );
?>

(好吧,你可以告诉 PHP 这样做,但它不起作用。)

因此您必须求助于使用极不可能的围栏的解决方案,但由于围栏只能是一个字符长,所以可能很难找到。

或者(在我看来:更优雅地),您可以选择像这样读取这些文件,而不是

<?php
$line
= fgets( $fi, $length );
$row = explode( '|', $line );
?>

因为这种方法更直观且更具弹性,所以我决定从现在开始使用这种“构造”而不是 fgetcsv。
mortanon at gmail dot com
18 年前
这是一个 CSV 迭代器的示例。

<?php
class CsvIterator implements Iterator
{
const
ROW_SIZE = 4096;
/**
* 指向 cvs 文件的指针。
* @var resource
* @access private
*/
private $filePointer = null;
/**
* 当前元素,每次迭代时都会返回。
* @var array
* @access private
*/
private $currentElement = null;
/**
* 行计数器。
* @var int
* @access private
*/
private $rowCounter = null;
/**
* csv 文件的分隔符。
* @var str
* @access private
*/
private $delimiter = null;

/**
* 这是构造函数。它尝试打开 csv 文件。如果失败,该方法会抛出异常。
*
* @access public
* @param str $file csv 文件。
* @param str $delimiter 分隔符。
*
* @throws Exception
*/
public function __construct($file, $delimiter=',')
{
try {
$this->filePointer = fopen($file, 'r');
$this->delimiter = $delimiter;
}
catch (
Exception $e) {
throw new
Exception('无法读取文件 "'.$file.'"。');
}
}

/**
* 此方法重置文件指针。
*
* @access public
*/
public function rewind() {
$this->rowCounter = 0;
rewind($this->filePointer);
}

/**
* 此方法将当前 csv 行作为二维数组返回。
*
* @access public
* @return array 当前 csv 行,作为二维数组。
*/
public function current() {
$this->currentElement = fgetcsv($this->filePointer, self::ROW_SIZE, $this->delimiter);
$this->rowCounter++;
return
$this->currentElement;
}

/**
* 此方法返回当前行号。
*
* @access public
* @return int 当前行号。
*/
public function key() {
return
$this->rowCounter;
}

/**
* 此方法检查是否已到达文件末尾。
*
* @access public
* @return boolean 如果已到达 EOF,则返回 true,否则返回 false。
*/
public function next() {
return !
feof($this->filePointer);
}

/**
* 此方法检查下一行是否为有效行。
*
* @access public
* @return boolean 如果下一行是有效行,则返回 true。
*/
public function valid() {
if (!
$this->next()) {
fclose($this->filePointer);
return
false;
}
return
true;
}
}
?>

使用方法

<?php
$csvIterator
= new CsvIterator('/path/to/csvfile.csv');
foreach (
$csvIterator as $row => $data) {
// 对 $data 执行某些操作
}
?>
mustafa dot kachwala at gmail dot com
13 年前
一个简单的函数,用于通过解析 CSV 文件来返回二维数组。

<?php
function get2DArrayFromCsv($file,$delimiter) {
if ((
$handle = fopen($file, "r")) !== FALSE) {
$i = 0;
while ((
$lineArray = fgetcsv($handle, 4000, $delimiter)) !== FALSE) {
for (
$j=0; $j<count($lineArray); $j++) {
$data2DArray[$i][$j] = $lineArray[$j];
}
$i++;
}
fclose($handle);
}
return
$data2DArray;
}
?>
jack dot peterson at gmail dot com
13 年前
如果您收到以下格式的数据

Time,Dataset1,Dataset2,
timestamp1,item 1 for dataset 1,item1 for dataset2
timestamp2,item 2 for dataset 1,item2 for dataset2

则以下代码将输出一系列按列分组的数组,结果格式如下
array (
[column 1 title] => array (
[timestamp1] => item1 for dataset1
[timestamp2] => item2 for dataset1
)

[column 2 title] => array (
[timestamp1] => item1 for dataset2
[timestamp2] => item2 for dataset2
)
)

<?php

# 打开文件。
if (($handle = fopen("rawdata.csv", "r")) !== FALSE) {
# 将父多维数组键设置为 0。
$nn = 0;
while ((
$data = fgetcsv($handle, 0, ",")) !== FALSE) {
# 统计行中的总键数。
$c = count($data);
# 填充多维数组。
for ($x=0;$x<$c;$x++)
{
$csvarray[$nn][$x] = $data[$x];
}
$nn++;
}
# 关闭文件。
fclose($handle);
}

// 将行化的数据进行列化处理
function columnizeArray($csvarray) {
$array = array();
foreach(
$csvarray as $key=>$value) {
// 重新解析成可用的数组数据。
if ($key == 0) {
foreach (
$value AS $key2=>$value2) {
$array[$key2] = array();
$array[$key2][] = $value2;
}
}else if (
$key > 0){
foreach (
$value as $key3=>$value3) {
$array[$key3][] = $value3;
}
}else{
}
}
return
$array;
}
function
groupColumns($array = null) {
$array2 = array();
foreach (
$array as $k=>$v) {
// 处理每一列
// $k = 列号
// $v = 行数组
if ($k == 0) {}else{ // 处理第 2 列或更高列
$array2[$v[0]] = array();
foreach (
$array[0] as $k1=>$v1) {
if (
$v1 > 0) { // 忽略列标题
// 将第一列变量存储为键。
// 将与此项关联的值存储为值。
$array2[$v[0]][$v1] = $v[$k1];
}
}
}
}
return
$array2;
}

$array2 = groupColumns(columnizeArray($csvarray));

print_r($array2);

?>
fil dot dogaru at gmail dot com
1 年前
针对 jack dot peterson at gmail dot com 提出的处理方法的更简洁的解决方案。不确定是否更高效,但我想现在大家至少都有 1GB 内存吧 :))。欢迎通过邮件联系我。

他写道:“如果你接收到的数据格式如下

Time,Dataset1,Dataset2,
timestamp1,item 1 for dataset 1,item1 for dataset2
timestamp2,item 2 for dataset 1,item2 for dataset2

则以下代码将输出一系列按列分组的数组,结果格式如下
array (
[column 1 title] => array (
[timestamp1] => item1 for dataset1
[timestamp2] => item2 for dataset1
)

[column 2 title] => array (
[timestamp1] => item1 for dataset2
[timestamp2] => item2 for dataset2
)
)”

$filename = "mybeautifulcsv.csv";
$collected = array_map('str_getcsv', file($filename));
$total = count($collected[0]);
for($i=0; $i<$total; $i++)
$formated[$collected[0][$i]] = array_column($collected, $i, 0);
endfor;
array_shift($formated);

//var_dump($formated);
php at richardneill dot org
7 个月前
为了与标准 (RFC-4180) CSV 文件最大程度地兼容,请记住应禁用专有转义机制。即,将可选的第五个参数设置为 ""(空字符串)。
kamil dot dratwa at gmail dot com
2 年前
长度参数行为描述的这一部分有点棘手,因为它没有提到分隔符被视为一个字符并转换为空字符串:“否则,该行将被分割成长度字符的块 (...)”。

首先,查看读取不包含分隔符的行的示例

<?php
file_put_contents
('data.csv', 'foo'); // 没有分隔符
$handle = fopen('data.csv', 'c+');
$data = fgetcsv($handle, 2);
var_dump($data);
?>

上面的示例将输出
array(1) {
[0]=>
string(2) "fo"
}

现在让我们添加分隔符

<?php
file_put_contents
('data.csv', 'f,o,o'); // 逗号用作分隔符
$handle = fopen('data.csv', 'c+');
$data = fgetcsv($handle, 2);
var_dump($data);
?>

第二个示例将输出

array(2) {
[0]=>
string(1) "f"
[1]=>
string(0) ""
}

现在让我们改变长度

<?php
file_put_contents
('data.csv', 'f,o,o');
$handle = fopen('data.csv', 'c+');
$data = fgetcsv($handle, 3); // 注意更新的长度
var_dump($data);
?>

最后一个示例的输出是

array(2) {
[0]=>
string(1) "f"
[1]=>
string(1) "o"
}

最终结论是,在将行分割成块时,分隔符在读取过程中被视为一个字符,但随后被转换为空字符串。此外,如果分隔符位于块的开头或结尾,它将被包含在结果数组中,但如果它位于其他字符之间,则它将被忽略。
lewiscowles at me dot com
4 年前
如果有人在处理字节顺序标记时遇到困难,以下方法应该有效。和往常一样,不保证,你应该测试你的代码... 它仅适用于 UTF-8

<?php

//...

$fh = fopen('wut.csv', 'r');
$firstThreeBytes = fread($fh , 3);
if(
$firstThreeBytes !== "\xef\xbb\xbf") {
rewind($fh);
}
while((
$row = fgetcsv($fh, 10000, ',')) !== false) {
// 你的代码在此处
}

这将读取 3 个字节 检查 它们是否 匹配

https://en.wikipedia.org/wiki/Byte_order_mark 如果您处理其他代码页,则提供更多信息
Daniel Klein
7 年前
$escape 参数完全不直观,但它并不存在问题。以下是 fgetcsv() 行为的分解。在示例中,我使用下划线 (_) 表示空格,使用方括号 ([]) 表示各个字段

- 如果每个字段中的前导空格直接出现在包含符之前,则将被剥离:___"foo" -> [foo]
- 每个字段只能有一个包含符,但它将与出现在结束包含符和下一个分隔符/换行符之间的任何数据连接起来,包括任何尾随空格:___"foo"_"bar"__ -> [foo_"bar"__]
- 如果字段没有以(前导空格 +)包含符开头,则整个字段将被解释为原始数据,即使包含符字符出现在字段内的其他位置:_foo"bar"_ -> [_foo"bar"_]
- 分隔符不能在包含符之外转义,它们必须在包含符中进行转义。分隔符不需要在包含符中转义:"foo,bar","baz,qux" -> [foo,bar][baz,qux]; foo\,bar -> [foo\][bar]; "foo\,bar" -> [foo\,bar]
- 单个包含符中的双重包含符将转换为单个包含符:"foobar" -> [foobar]; "foo""bar" -> [foo"bar]; """foo""" -> ["foo"]; ""foo"" -> [foo""](空包含符后面跟着原始数据)
- $escape 参数按预期工作,但与包含符不同的是,它不会被反转义。有必要在代码中的其他地方对数据进行反转义:"\"foo\"" -> [\"foo\"]; "foo\"bar" -> [foo\"bar"]

注意:以下数据(这是非常常见的问题)是无效的:“\”。它的结构等同于 “@ 或换句话说,一个开放的包含符,一些数据,但没有结束包含符。

以下函数可用于获得预期行为

<?php
// 在两个括号和转义符之前移除转义字符,但保留其他所有内容,类似于单引号
function fgetcsv_unescape_enclosures_and_escapes($fh, $length = 0, $delimiter = ',', $enclosure = '"', $escape = '\\') {
$fields = fgetcsv($fh, $length, $delimiter, $enclosure, $escape);
if (
$fields) {
$regex_enclosure = preg_quote($enclosure);
$regex_escape = preg_quote($escape);
$fields = preg_replace("/{$regex_escape}({$regex_enclosure}|{$regex_escape})/", '$1', $fields);
}
return
$fields;
}

// 不移除字段末尾的单个转义字符
function fgetcsv_unescape_all($fh, $length = 0, $delimiter = ',', $enclosure = '"', $escape = '\\') {
$fields = fgetcsv($fh, $length, $delimiter, $enclosure, $escape);
if (
$fields) {
$regex_escape = preg_quote($escape);
$fields = preg_replace("/{$regex_escape}(.)/s", '$1', $fields);
}
return
$fields;
}

// 移除字段末尾的单个转义字符
function fgetcsv_unescape_all_strip_last($fh, $length = 0, $delimiter = ',', $enclosure = '"', $escape = '\\') {
$fields = fgetcsv($fh, $length, $delimiter, $enclosure, $escape);
if (
$fields) {
$regex_escape = preg_quote($escape);
$fields = preg_replace("/{$regex_escape}(.?)/s", '$1', $fields);
}
return
$fields;
}
?>

注意:理想情况下,括号外不应该有任何未转义的转义字符;字段应该被包含并转义。如果有,它们可能会被移除,具体取决于所使用的函数。
vladimir at luchaninov dot com
8 年前
以下是如何使用生成器函数的示例
https://github.com/luchaninov/csv-file-loader (composer require "luchaninov/csv-file-loader:1.*")

$loader = new CsvFileLoader();
$loader->setFilename('/path/to/your_data.csv');

foreach ($loader->getItems() as $item) {
var_dump($item); // 在这里进行操作
}

如果您的 CSV 文件像这样

id,name,surname
1,Jack,Black
2,John,Doe

您将得到 2 个项目

['id' => '1', 'name' => 'Jack', 'surname' => 'Black']
['id' => '2', 'name' => 'John', 'surname' => 'Doe']
Xander
13 年前
我在处理多字节时遇到问题。文件是 windows-1250,脚本是 UTF-8,set_locale 没有起作用,所以我做了一个简单安全的解决方法

<?php
$fc
= iconv('windows-1250', 'utf-8', file_get_contents($_FILES['csv']['tmp_name']));

file_put_contents('tmp/import.tmp', $fc);
$handle = fopen('tmp/import.tmp', "r");
$rows = array();
while ((
$data = fgetcsv($handle, 0, ";")) !== FALSE) {

$rows[] = $data;

}
fclose($handle);
unlink('tmp/import.tmp');
?>

希望您能发现它有用。
抱歉我的英语。
Anonymous
18 年前
注意二进制值为 0 的字符,因为它们似乎会导致 fgetcsv 忽略它们出现的行的剩余部分。

也许这是某些我所不知道的约定下的正常情况,但是从 Excel 导出的文件有时会将这些作为某些单元格的值,因此 fgetcsv 会对不同的行返回可变的单元格数量。

我正在使用 php 4.3
kurtnorgaz at web dot de
21 年前
您应该注意 "fgetcsv" 在读取文件时会移除前导制表符 "chr(9)"。

这意味着如果您在文件中使用 fgetcsv,文件中的第一个字符是 chr(9),那么该字符将被自动删除。

示例
文件内容
chr(9)first#second#third#fourth

源代码
<?php $line = fgetcsv($handle,500,"#"); ?>

数组 $line 看起来像这样
$line[0] = first
$line[1] = second
$line[2] = third
$line[3] = fourth

而不是
$line[0] = chr(9)first
$line[1] = second
$line[2] = third
$line[3] = fourth

任何其他字符之后的 chr(9) 不会被删除!

示例
文件内容
Achr(9)first#second#third#fourth

源代码
<?php $line = fgetcsv($handle,500,"#"); ?>

数组 $line 看起来像这样
$line[0] = Achr(9)first
$line[1] = second
$line[2] = third
$line[3] = fourth
tokai at binaryriot dot com
18 年前
较新的 PHP 版本处理 cvs 文件的方式与旧版本略有不同。

"Max Mustermann"|"Muster Road 34b"|"Berlin" |"Germany"
"Sophie Master" |"Riverstreet" |"Washington"|"USA"

示例中某些字段后面的额外空格(在您手动管理小型 csv 数据库以对齐列时很有用)被 PHP 4.3 中的 fgetcsv 忽略。在新的 4.4.1 版本中,它们被附加到字符串,因此您最终得到 "Riverstreet " 而不是预期的 "Riverstreet"。

简单的解决方法是在读取所有字段后对其进行修剪。

<?php
while ( $data = fgetcsv($database, 32768, "|") )
{
$i = 0;

while(isset(
$data[$i]))
{
$data[$i] = rtrim($data[$i]);
$i++;
}

// ....
}
?>
do not spam aleske at live dot ru
14 年前
PHP 的 CSV 处理功能不符合标准,与 RFC4180 相矛盾,因此 fgetcsv() 无法正确处理像维基百科上的这个示例这样的文件

1997,Ford,E350,"ac, abs, moon",3000.00
1999,Chevy,"Venture ""Extended Edition""","",4900.00
1999,Chevy,"Venture ""Extended Edition, Very Large""","",5000.00
1996,Jeep,Grand Cherokee,"MUST SELL!
air, moon roof, loaded",4799.00

请注意:包含符号在字段内是双重的,字段数据可以包含换行符,并且没有真正的转义符号。此外,fputcsv() 会创建非标准 CSV 文件。

这里有一个快速简便的 RFC 兼容的 CSV 创建和解析实现

<?php
function array_to_csvstring($items, $CSV_SEPARATOR = ';', $CSV_ENCLOSURE = '"', $CSV_LINEBREAK = "\n") {
$string = '';
$o = array();

foreach (
$items as $item) {
if (
stripos($item, $CSV_ENCLOSURE) !== false) {
$item = str_replace($CSV_ENCLOSURE, $CSV_ENCLOSURE . $CSV_ENCLOSURE, $item);
}

if ((
stripos($item, $CSV_SEPARATOR) !== false)
|| (
stripos($item, $CSV_ENCLOSURE) !== false)
|| (
stripos($item, $CSV_LINEBREAK !== false))) {
$item = $CSV_ENCLOSURE . $item . $CSV_ENCLOSURE;
}

$o[] = $item;
}

$string = implode($CSV_SEPARATOR, $o) . $CSV_LINEBREAK;

return
$string;
}

function
csvstring_to_array(&$string, $CSV_SEPARATOR = ';', $CSV_ENCLOSURE = '"', $CSV_LINEBREAK = "\n") {
$o = array();

$cnt = strlen($string);
$esc = false;
$escesc = false;
$num = 0;
$i = 0;
while (
$i < $cnt) {
$s = $string[$i];

if (
$s == $CSV_LINEBREAK) {
if (
$esc) {
$o[$num] .= $s;
} else {
$i++;
break;
}
} elseif (
$s == $CSV_SEPARATOR) {
if (
$esc) {
$o[$num] .= $s;
} else {
$num++;
$esc = false;
$escesc = false;
}
} elseif (
$s == $CSV_ENCLOSURE) {
if (
$escesc) {
$o[$num] .= $CSV_ENCLOSURE;
$escesc = false;
}

if (
$esc) {
$esc = false;
$escesc = true;
} else {
$esc = true;
$escesc = false;
}
} else {
if (
$escesc) {
$o[$num] .= $CSV_ENCLOSURE;
$escesc = false;
}

$o[$num] .= $s;
}

$i++;
}

// $string = substr($string, $i);

return $o;
}
?>

参考资料
RFC4180 - http://tools.ietf.org/html/rfc4180
维基百科 - http://en.wikipedia.org/wiki/Comma-separated_values#Example

此外,在 http://code.google.com/p/parsecsv-for-php/ 可以找到完整的 CSV 处理解决方案。
To Top