PHP Conference Japan 2024

fgetcsv

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

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

描述

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

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

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

参数

stream

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

length

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

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

separator

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

enclosure

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

escape

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

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

警告

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

警告

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

返回值

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

注意:

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

注意: 如果PHP在读取Macintosh计算机上或由其创建的文件时无法正确识别换行符,则启用 auto_detect_line_endings 运行时配置选项可能有助于解决此问题。

错误/异常

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

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

变更日志

版本 描述
8.4.0 依赖于 escape 的默认值现已弃用。
8.3.0 如果最后一个字段仅包含未终止的引号,则返回空字符串而不是包含单个空字节的字符串。
8.0.0 length 现在可以为 null。
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);
}
?>

参见

添加笔记

用户贡献笔记 31 条笔记

james dot ellis at gmail dot com
16 年前
如果您需要设置 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;
}
?>
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 可以很好地处理字段内的换行符。文档中没有明确说明这一点。
Gandalf the White
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
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 或记事本等文本编辑器可能会添加 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数组中的自己的条目*/
} /*$table数组*/
fclose($id); //关闭文件
return $table;
}
}
?>
tomasz at marcinkowski dot pl
11年前
对于任何其他在单字节编码中遇到消失的非拉丁字符的人 - 设置 LANG 环境变量(如手册中所述)根本没有帮助。请改用 LC_ALL。

在我的例子中,它设置为“pl_PL.utf8”,但由于我的输入文件是 CP1250,所以大多数波兰字符(但并非全部!)都消失了,“Łódź”这个城市变成了“dź”。我已经用“pl_PL”修复了它。
phpnet at smallfryhosting dot co dot uk
21年前
另一个版本[修改自 mediaconcepts 的 michael]

<?php
function arrayFromCSV($file, $hasFieldNames = false, $delimiter = ',', $enclosure='') {
$result = Array();
$size = filesize($file) +1;
$file = fopen($file, 'r');
#待办事项:必须有更好的方法来找出最长行的长度……在此之前
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;
}
?>
michael dot martinek at gmail dot com
16 年前
这是我今天早上整理的东西。它允许您从 CSV 读取行并根据列名获取值。当您的标题列并不总是按相同的顺序排列时,这非常有效;例如,当您处理来自不同客户的许多 feed 时。还可以编写更简洁、更容易管理的代码。

因此,如果您的 feed 如下所示

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";
}

?>
kent at marketruler dot com
14年前
请注意,至少在 PHP 5.3 或之前的版本中,fgetcsv 无法处理 UTF-16 编码的文件。您可以选择将整个文件转换为 ISO-8859-1(或 latin1),或者逐行转换并将每一行转换为 ISO-8859-1 编码,然后使用 str_getcsv(或兼容的向后兼容实现)。如果您需要读取非拉丁字母,最好转换为 UTF-8。

对于 PHP < 5.3 的向后兼容版本,请参阅 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 版本,它会报错,所以我很快放弃了这个方法。

希望这对其他人有所帮助。
sander at NOSPAM dot rotorsolutions dot nl
11年前
如果您不想定义封闭字符,您可以执行以下操作:

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

我需要这个来检测 CSV 文件使用的封闭符。
junk at vhd dot com dot au
19 年前
fgetcsv 函数似乎遵循 MS Excel 约定,这意味着:

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

两个双引号 "" 解析后将得到一个 ",如果它们位于带引号的字段中(否则两者都不会被移除)。

\" 将得到 \",无论它是否在带引号的字段中(\\ 也一样),并且

如果单个双引号位于带引号的字段中,它将被移除。如果它不在带引号的字段中,它将保留)。

- 领先和尾随空格(\s 或 \t)永远不会被移除,无论它们是否在带引号的字段中。

- 如果字段中的换行符位于带引号的字段中,则会正确处理。(因此,之前的评论说相反的是错误的,除非他们使用的是不同的 PHP 版本……我使用的是 4.4.0。)

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

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

-------------------------
注意:如果使用反斜杠来转义引号,则可以之后轻松将其移除。领先和尾随空格也是如此。
nick at atomicdesign dot net
12 年前
在迭代 CSV 文件时,我遇到了字节耗尽错误。ini_set('auto_detect_line_endings', 1); 修复了它。
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
from_php at puggan dot se
8 年前
设置 $escape 参数不会返回未转义的字符串,而只是避免在前面有转义字符的 $delimiter 上分割。

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

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

echo
"fgetcsv转义#:" . PHP_EOL;
$f = fopen($tmp_file, 'r');
while(
$r = fgetcsv($f, 1024, ';', '"', "#"))
{
print_r($r);
}
fclose($f);
echo
PHP_EOL;
?>
daniel at softel dot jp
18 年前
请注意,fgetcsv() 使用系统区域设置来推断字符编码。
因此,如果您尝试在一个EUC-JP服务器上处理UTF-8 CSV文件(例如),
您需要在调用fgetcsv()之前执行以下操作:

setlocale(LC_ALL, 'ja_JP.UTF8');

[另请注意,setlocale()不会*永久*影响系统区域设置]
code at ashleyhunt dot co dot uk
14年前
我需要一个函数来分析文件的分隔符和换行符,以便使用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(
'逗号' => ',',
'分号' => ';',
'制表符' => "\t",
'竖线' => '|',
'冒号' => ':'
);

// 指定允许的换行符
$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
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);
?>
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);
}
}
?>
ifedinachukwu at yahoo dot com
13 年前
我有一个 csv 文件,其字段包含带有换行符的数据(由在 html textarea 中按回车键创建的 CRLF)。当然,在创建 csv 时,MySQL 对这些字段中的 LF 进行了转义。问题是我无法使 fgetcsv 正确工作,因为每个 LF 都被视为 csv 文件行尾,即使它被转义了!

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

<?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
15 年前
我使用 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。
vladimir at luchaninov dot com
9 年前
以下是如何使用此函数和生成器的示例
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']
Daniel Klein
8 年前
$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;
}
?>

注意:理想情况下,分隔符外部不应该有任何未转义的转义字符;字段应该被包含并转义。如果存在任何未转义的转义字符,则它们最终也可能被删除,具体取决于使用的函数。
mortanon at gmail dot com
19 年前
这是一个 CSV 迭代器的示例。

<?php
class CsvIterator implements Iterator
{
const
ROW_SIZE = 4096;
/**
* CSV 文件指针。
* @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 如果下一行是有效行。
*/
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 执行某些操作
}
?>
Xander
14年前
我遇到了多字节字符的问题。文件是 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');
?>

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

也许这在我不知道的某些约定下是正常的,但是从 Excel 导出的文件中,有些单元格 *有时* 会有这些值,因此 fgetcsv 返回不同行的可变单元格计数。

我使用的是 php 4.3
kurtnorgaz at web dot de
21年前
您应该注意“fgetcsv”在读取文件时会删除前导 TAB 字符“chr(9)”。

这意味着如果文件中的第一个字符是 chr(9),并且您使用 fgetcsv,则此字符将自动删除。

示例
文件内容
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
lzsiga at freemail dot c3 dot hu
2 个月前
有一种特殊的语法可以防止 Excel 自动将字段内容转换为日期或浮点数:="fieldcontent"(开头是等号)。(请注意,如果内容在内部包含行尾字符或字段分隔符字符,则不应使用此方法。)

现在,fgetcvs 不支持此语法,尽管可以通过一些后处理来实现。
kamil dot dratwa at gmail dot com
3年前
长度参数行为描述的这部分比较棘手,因为它没有提到分隔符被视为一个字符并被转换为空字符串:“否则,该行将被分成长度字符的块 (...)”。

首先,让我们看一下读取不包含分隔符的行的示例

<?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 如果您处理其他代码页,则有更多信息
To Top