\n
(UNIX默认)\r
(Mac Excel)\r\n
(Windows)- 或其他任何字符
为了获取分隔符、封闭符和转义字符,我使用了SplFileObject::getCsvControl - 希望能够有类似于该函数的用于换行符的函数。
打开文件
\n
(UNIX默认)\r
(Mac Excel)\r\n
(Windows)为了获取分隔符、封闭符和转义字符,我使用了SplFileObject::getCsvControl - 希望能够有类似于该函数的用于换行符的函数。
打开文件
我没有尝试过这个,但我认为这是一个有趣的问题,所以我在这里给出了可能解决方案的尝试:
// first, have PHP auto-detect the line endings, like @AbraCadaver suggested:
ini_set("auto_detect_line_endings", true);
// now open the file and read a single line from it
$file = fopen('/path/to/file.csv', 'r');
fgets($file);
// fgets() moves the pointer, so get the current position
$position = ftell($file);
// now get a couple bytes (here: 10) from around that position
fseek($file, $position - 5);
$data = fread($file, 10);
// we no longer need the file
fclose($file);
// now find out how many of each type EOL there are in those 10 bytes
// expected result is that two of these will be 0 and one will be 1
$eols = array(
"\r\n" => substr_count($data, "\r\n"),
"\r" => substr_count($data, "\r"),
"\n" => substr_count($data, "\n"),
);
// sort the EOL count in reverse order, so that the EOL with the highest
// count (expected: 1) will be the first item
arsort($eols);
// get the first item's key
$eol = key($eols);
// $eol will now be "\r\n", "\r" or "\n"
可能有更好的方法来完成这个任务,需要注意我在这里对你的CSV文件做了一些假设:
如果这些条件不能保证,你需要添加一些验证步骤,例如检查fgets()
的结果是否实际上是多个字符的字符串。如果行比5个字节短,你还需要考虑到行尾可能是\r\n
,但是通过寻找原始字节,我们得到的字符串可能会像"abcde\r\nfg\r"
这样,错过了第二个\n
,导致结果不正确。
但是,如果你可以确定CSV文件的结构,这可能是朝着正确方向迈出的(虽然有点“脏”)一步。
这是一个有趣的问题 - 没有人能够在此给出完整的解决方案。明显的方法包括:
1)读取文件直到第一次出现 \r 或 \n。在前者的情况下,再读取一个字符以检查它是否后跟 \n。
这听起来非常简单 - 但您需要实现引号处理以确定 EOL 是否嵌入在带引号的数据字段中 - 而且您不知道数据如何被引用。除了检测开放和关闭引号之外,您还需要能够确定引号字符是否被转义 - 转义引号字符至少有两种不同的方法。
2)分析文件中字符的频率。如果您可以忽略空格、字母和数字,则剩余字符中最常见的应该是 CSV 元字符。但是对于非常短的文件,这些元字符将无法使用。
3)创建文件中数据字符串的表示,并查找重复模式,例如如果您找到数字、空格、字母、空格、数字、标点符号、数字、空格、字母、标点符号、字母、空格、数字、标点符号、数字、空格、字母、空格、数字、标点符号,则可以推断字段分隔符是空格,记录由标点符号分隔,标点符号可能也出现为嵌入字符。
但这需要一些非常复杂的代码。
如果是我,我会直接询问提供文件的人提供文件格式的详细信息。或者,如果没有该信息,则使用十六进制编辑器打开文件。
我使用了@rickdenhaan的解决方案,发现arsort()和PHP版本存在问题。
如果eol是"\r\n",则$eols数组将为:
array("\r\n" => 1, "\r" => 1, "\n" => 1);
(因为除了1个"\r\n"之外,还找到了1个"\r"和1个"\n")
在PHP 7中,arsort($eols)后,键的顺序相同:
array("\r\n" => 1, "\r" => 1, "\n" => 1);
在"$eol = key($eols);"之后,$eol将是"\r\n"
但是,在PHP 5.6中,arsort($eols)后,键的顺序为:
array("\n" => 1, "\r" => 1, "\r\n" => 1);
在"$eol = key($eols);"之后,$eol将是"\n"
在"$eol = key($eols);"之后,我用这个检查解决了问题:
if (($eols["\r\n"] == $eols["\r"]) AND ($eols["\r\n"] == $eols["\n"])) {
$line_separator = "\r\n";
}
ini_set("auto_detect_line_endings",true);
至于SplFileObject :: getCsvControl
,请注意,此函数并不会从给定的文件中自动猜测CSV控件,而是返回之前使用SplFileObject :: setCsvControl
设置的内容。 - AbraCadaver\r\n
的文件规范化为\n
,这样在使用LOAD DATA INFILE时就不需要指定LINES TERMINATED BY
了。假设文件很大,并且希望避免在PHP中进行迭代 - 可以考虑使用d2u/dos2unix。否则,一些perl/sed/tr等工具也可以实现。 - ficuscr