2024 年 PHP Conference Japan

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,則某些單一位元組編碼的資料可能會被錯誤地解析。

參數

字串 (string)

要解析的字串。

分隔符號 (separator)

separator 參數設定欄位分隔符號。它必須是單一位元組字元。

括住字元 (enclosure)

enclosure 參數設定欄位括住字元。它必須是單一位元組字元。

跳脫字元 (escape)

escape 參數設定跳脫字元。它必須是單一位元組字元或空字串。空字串 ("") 會停用專有的跳脫機制。

注意通常在欄位內,enclosure 字元會透過重複自身來跳脫;但是,escape 字元可以用作替代方案。因此,對於預設參數值,""\" 具有相同的含義。除了允許跳脫 enclosure 字元之外,escape 字元沒有特殊含義;它甚至不 meant to 跳脫自身。

警告

從 PHP 8.4.0 開始,依賴 escape 的預設值已被棄用。需要明確地提供它,無論是透過位置參數還是使用命名參數

警告

escape 設定為非空字串 ("") 時,可能會導致 CSV 不符合 » RFC 4180 或無法透過 PHP CSV 函式來回轉換。 escape 的預設值是 "\\",因此建議將其明確設定為空字串。預設值將在未來的 PHP 版本中更改,不早於 PHP 9.0。

返回值

返回一個包含已讀取欄位的索引陣列。

錯誤/例外

如果 separatorenclosure 不是單一位元組長,則會拋出 ValueError

如果 escape 不是單一位元組長或空字串,則會拋出 ValueError

更新日誌

版本 說明
8.4.0 PHP 8.4.0
8.4.0 依賴 escape 的預設值現已被棄用。
8.3.0 如果 separatorenclosureescape 無效,現在會拋出 ValueError。這模仿了 fgetcsv()fputcsv() 的行為。
7.4.0 PHP 8.0.0

如果最後一個欄位僅包含未結束的括住字元,則返回空字串,而不是帶有單個空位元組的字串。

PHP 7.4.0

escape 參數現在將空字串解釋為停用專有跳脫機制的訊號。以前,空字串被視為預設參數值。

範例

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] => 陣列
(
[廣告活動 ID] => 295095038
[廣告群組 ID] => 22460178158
[關鍵字 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 試算表)的規範,則無法使用 str_getcsv()。

這些工具不會跳脫逗號或換行符號,而是在欄位前後加上雙引號 (")。如果欄位中包含雙引號,則會使用另一個雙引號進行跳脫 (" 變為 "")。這看起來可能很奇怪,但這正是 RFC 和大多數工具的做法…

例如,嘗試以 .csv 格式匯出 Google 試算表(檔案 > 下載為 > .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
);
}

?>

由於這沒有使用任何迴圈,您實際上可以將它寫成單行程式碼 (one-liner)。

以下是僅使用一行代碼作為函式主體的函式,但經過良好的格式化

<?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
*/
函式 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 Disassembler (VLD),它會掛鉤到 Zend Engine 並轉儲腳本的所有操作碼(執行單元) - 請參閱 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!! 魔法或 urlencoding 來解決問題。此外,\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
Note
: The function trims all values unlike str_getcsv (v5.3).
/**
* @link https://github.com/insteps/phputils (for updated code)
* Parse a CSV string into an array for php 4+.
* @param string $input String
* @param string $delimiter String
* @param string $enclosure String
* @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 = "::";
//alternate tokens "\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") {
# First convert associative array to numeric indexed array
foreach ($array as $key => $value) $workArray[] = $value;

$returnString = ''; # Initialize return string
$arraySize = count($workArray); # Get size of array

for ($i=0; $i<$arraySize; $i++) {
# Nested array, process nest item
if (is_array($workArray[$i])) {
$returnString .= str_putcsv($workArray[$i], $delimiter, $enclosure, $terminator);
} else {
switch (
gettype($workArray[$i])) {
# Manually set some strings
case "NULL": $_spFormat = ''; break;
case
"boolean": $_spFormat = ($workArray[$i] == true) ? 'true': 'false'; break;
# Make sure sprintf has a good datatype to work with
case "integer": $_spFormat = '%i'; break;
case
"double": $_spFormat = '%0.2f'; break;
case
"string": $_spFormat = '%s'; break;
# Unknown or invalid items for a csv - note: the datatype of array is already handled above, assuming the data is nested
case "object":
case
"resource":
default:
$_spFormat = ''; break;
}
$returnString .= sprintf('%2$s'.$_spFormat.'%2$s', $workArray[$i], $enclosure);
$returnString .= ($i < ($arraySize-1)) ? $delimiter : $terminator;
}
}
# Done the workload, return the output information
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 檔案的第一行將被視為標題(欄位名稱)。

待辦事項
- 處理括號
- 處理跳脫字元
- 其他所需的功能/增強功能

使用範例
/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] => Andorra
)
[1] => 陣列
(
[CODE] => AE
[COUNTRY] => United Arab Emirates
)
[2] => 陣列
(
[CODE] => AF
[國家] => 阿富汗
)
[3] => 陣列
(
[代碼] => AG
[國家] => 安地瓜和巴布達
)
)

<?php
$obj_csv
= parse_csv("/path/to/file.csv", array("to_object" => true));
print_r($obj_csv);
?>
// 輸出
陣列
(
[0] => stdClass 物件
(
[CODE] => AD
[COUNTRY] => Andorra
)
[1] => stdClass 物件
(
[CODE] => AE
[COUNTRY] => United Arab Emirates
)
[2] => stdClass 物件
(
[CODE] => AF
[國家] => 阿富汗
)
[3] => stdClass 物件
(
[代碼] => AG
[國家] => 安地瓜和巴布達
)
[4] => stdClass 物件
(
[代碼] =>
[國家] =>
)
)

// 如果您在 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);

/*
* Validating against pesky extra line breaks creating false rows.
*/
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 處理的 POST 資料)。
khelibert at gmail dot com
12 年前
我寫了這段程式碼來處理
- 有或沒有引號的欄位;
- 使用相同字元作為跳脫字元和引號字元(例如 Excel 中的 <<">>)

<?php
/**
* Converts a csv file into an array of lines and columns.
* khelibert@gmail.com
* @param $fileContent String
* @param string $escape String
* @param string $enclosure String
* @param string $delimiter String
* @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
{
//end of the field
$fields[] = $field;
$field = '';
}
}
else if(
$c === "\n")
{
$fields[] = $field;
$field = '';
$lines[] = $fields;
$fields = array();
}
else
$field .= $c;
$previous = $c;
}
//we add the last element
if(true || $field !== '')
{
$fields[] = $field;
$lines[] = $fields;
}
return
$lines;
}
?>
peter dot mlich at volny dot cz
9 年前
> 49 durik at 3ilab dot net / 4 年前
$rows = str_getcsv($csv_data, "\n");
- 錯誤,csv 中的資料可以包含 "\n"
'aaa','bb
b','ccc'
hans at loltek dot net
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 旗標。
php at richardneill dot org
10 個月前
為了與標準 (RFC-4180) CSV 檔案達到最大相容性,請記住應停用專有的跳脫機制。 也就是說,將第五個可選參數設定為 ""(空字串)。
Wade Rossmann
2 年前
為了完整性,這裡有一個使用者空間的 str_putcsv(),它與 fgetcsv() 和 fputcsv() 的參數完全相容。 也就是 $escape 和 $eol,其他所有函式似乎都省略了這些參數。

<?php

函數 str_putcsv(
陣列
$fields,
字串 $separator = ",",
字串 $enclosure = "\"",
字串 $escape = "\\",
字串 $eol = "\n"
) {
返回
implode($separator,
array_map(
函數(
$a) 使用($enclosure, $escape) {
$type = gettype($a);
根據(
$type) {
案例
'integer': 返回 sprintf('%d', $a);
案例
'double': 返回 rtrim(sprintf('%0.'.ini_get('precision').'f', $a), '0');
案例
'boolean': 返回 ( $a ? 'true' : 'false' );
案例
'NULL': 返回 '';
案例
'string':
返回
sprintf('"%s"', str_replace(
[
$escape, $enclosure],
[
$escape.$escape, $escape.$enclosure],
$a
));
預設: 拋出新的
TypeError("無法字串化類型: $type");
}
},
$fields
)
) .
$eol;
}
ivijan dot stefan at gmail dot com
3 年前
想像一下,您需要一個同時適用於網址和逗號分隔文字的函數。

這正是使用 str_getcsv() 的函數。只需插入 CSV 網址或逗號分隔的文字,它就能正常運作。

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

如果(
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');

對每個(
str_getcsv($csv, $linebreak, $enclosure) 作為 $row){
$col = str_getcsv($row, $delimiter, $enclosure);
$col = array_map('trim', $col);
$return[] = $col;
}
}
否則
{
拋出新的
\Exception('無法開啟檔案。');
$return = false;
}

返回
$return;
}
?>
txxllm at hotmail dot com
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 字串。

函式 csv_to_array($string='', $row_delimiter=PHP_EOL, $delimiter = "," , $enclosure = '"' , $escape = "\\" )
{
$rows = array_filter(explode($row_delimiter, $string));
$header = NULL;
$data = 陣列();

對每個($rows 作為 $row)
{
$row = str_getcsv ($row, $delimiter, $enclosure , $escape);

如果( !$header )
$header = $row;
否則
$data[] = array_combine($header, $row);
}

返回 $data;
}
V.Krishn
11 年前
注意:與 str_getcsv (v5.3) 不同,此函式會修剪所有值。
/**
* @link https://github.com/insteps/phputils (更新後的程式碼)
* 將 CSV 字串解析為 PHP 4+ 的陣列。
* @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 "我的檔案描述。" filename.jpg

這些行可以像這樣解析

<?php

$line
= 'AddDescription "我的檔案描述。" filename.jpg';

$parsed = str_getcsv(
$line, # 輸入行
' ', # 分隔符號
'"', # 包含符號
'\\' # 跳脫字元
);

var_dump( $parsed );

?>

輸出

array(3) {
[0]=>
string(14) "AddDescription"
[1]=>
string(27) "我的檔案描述。"
[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 是一個數字,表示 csv 檔案中最長一行的長度,這是 fgetcsv() 所需的。 fgetcsv() 的說明頁面說最長一行可以設定為 0 或省略,但我沒辦法這樣做。當我必須使用它時,我只是將它設定得非常大。
To Top