检测CSV文件的行结束符

3
我需要检测csv文件中使用的换行符类型:

  • \n(UNIX默认)
  • \r(Mac Excel)
  • \r\n(Windows)
  • 或其他任何字符

为了获取分隔符、封闭符和转义字符,我使用了SplFileObject::getCsvControl - 希望能够有类似于该函数的用于换行符的函数。

打开文件


2
一般情况下,你无法检测到它,例如文件可能具有混合的行尾,那么你真正的问题是什么? - Iłya Bursov
3
不确定所有功能都使用它,但:ini_set("auto_detect_line_endings",true); 至于 SplFileObject :: getCsvControl,请注意,此函数并不会从给定的文件中自动猜测CSV控件,而是返回之前使用 SplFileObject :: setCsvControl 设置的内容。 - AbraCadaver
1
@IlyaBursov 我需要使用LOAD DATA INFILE将CSV文件导入到MySQL数据库中,但是该查询必须明确定义行结束字符。 - PeterInvincible
1
我会将带有\r\n的文件规范化为\n,这样在使用LOAD DATA INFILE时就不需要指定LINES TERMINATED BY了。假设文件很大,并且希望避免在PHP中进行迭代 - 可以考虑使用d2u/dos2unix。否则,一些perl/sed/tr等工具也可以实现。 - ficuscr
1
顺便提一下,因为很多人不知道:MySQL有一个内置的CSV存储引擎。因此,在某些用例中,您可以跳过LOAD DATA INFILE,只需将文件复制到某些位置(是的,从数据库目录链接出去也可以)。https://dev.mysql.com/doc/refman/5.7/en/csv-storage-engine.html - Norman M
显示剩余2条评论
3个回答

2

我没有尝试过这个,但我认为这是一个有趣的问题,所以我在这里给出了可能解决方案的尝试:

// 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文件做了一些假设:

  • 文件不以空行开头;
  • 第一行至少有5个字节长;
  • 第二行不为空且至少有5个字节长;
  • 第一行的最后一列和最后一行的第一列内没有任何换行符;
  • 你处理的文件没有混合行尾。

如果这些条件不能保证,你需要添加一些验证步骤,例如检查fgets()的结果是否实际上是多个字符的字符串。如果行比5个字节短,你还需要考虑到行尾可能是\r\n,但是通过寻找原始字节,我们得到的字符串可能会像"abcde\r\nfg\r"这样,错过了第二个\n,导致结果不正确。

但是,如果你可以确定CSV文件的结构,这可能是朝着正确方向迈出的(虽然有点“脏”)一步。


0

这是一个有趣的问题 - 没有人能够在此给出完整的解决方案。明显的方法包括:

1)读取文件直到第一次出现 \r 或 \n。在前者的情况下,再读取一个字符以检查它是否后跟 \n。

这听起来非常简单 - 但您需要实现引号处理以确定 EOL 是否嵌入在带引号的数据字段中 - 而且您不知道数据如何被引用。除了检测开放和关闭引号之外,您还需要能够确定引号字符是否被转义 - 转义引号字符至少有两种不同的方法。

2)分析文件中字符的频率。如果您可以忽略空格、字母和数字,则剩余字符中最常见的应该是 CSV 元字符。但是对于非常短的文件,这些元字符将无法使用。

3)创建文件中数据字符串的表示,并查找重复模式,例如如果您找到数字、空格、字母、空格、数字、标点符号、数字、空格、字母、标点符号、字母、空格、数字、标点符号、数字、空格、字母、空格、数字、标点符号,则可以推断字段分隔符是空格,记录由标点符号分隔,标点符号可能也出现为嵌入字符。

但这需要一些非常复杂的代码。

如果是我,我会直接询问提供文件的人提供文件格式的详细信息。或者,如果没有该信息,则使用十六进制编辑器打开文件。


0

我使用了@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";
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接