【问题标题】:Detect the line ending character of a csv file检测csv文件的行结束符
【发布时间】:2017-07-24 20:37:13
【问题描述】:

我需要检测 csv 文件中的行尾类型:

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

为了获得分隔符、包围和转义字符,我使用了SplFileObject::getCsvControl - 如果有类似的东西作为行尾字符,那就太好了。

打开文件

【问题讨论】:

  • 一般情况下 - 你无法检测到它,例如文件可能有混合行结尾,你真正的问题是什么?
  • 不确定所有函数都使用它,但是:ini_set("auto_detect_line_endings", true); 至于SplFileObject::getCsvControl请注意,此函数不会神奇地从给定文件中猜测 CSV 控件,而是返回具有之前已使用 SplFileObject::setCsvControl() 设置。
  • @IlyaBursov 我需要使用 LOAD DATA INFILE 将 csv 导入我的 MySQL 数据库,但该查询必须明确定义行尾字符。
  • 我会将带有\r\n 的文件标准化为\n,然后您根本不需要为LOAD DATA INFILE 指定LINES TERMINATED BY。假设大文件并希望避免 PHP 中的迭代 - 也许使用 d2u/dos2unix。否则一些 perl/sed/tr 等应该可以工作。
  • 顺便说一句,因为很多人都不知道:mysql 有一个内置的 csv-storage-engine。因此在某些用例中,您可以跳过 LOAD DATA INFILE 并将文件复制到某些位置(是的 db 目录中的链接有效)dev.mysql.com/doc/refman/5.7/en/csv-storage-engine.html

标签: php csv line-endings


【解决方案1】:

我还没有尝试过,但我认为这是一个有趣的问题,所以这是我对可能解决方案的破解:

// 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 文件的结构,那么这可能是(我承认这是肮脏的)朝着正确方向迈出的一步。

【讨论】:

    【解决方案2】:

    这是一个有趣的问题 - 没有人可以在这里为您提供完整的解决方案。显而易见的方法是:

    1) 继续读取文件,直到第一次出现 \r 或 \n。前者的情况,再读一个字符,看后面是否跟\n。

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

    2) 分析文件中字符出现的频率。如果您可以忽略空格、alhpa 字符和数字,那么其余最常见的应该是 CSV 元字符。但这些不适用于非常短的文件。

    3) 在文件中创建数据字符串的表示形式,并寻找重复的模式,例如如果你找到 number, space, alpha, space, number, punctuation, number, spache, alpha, punctuation, alpha, space, number, punctuation, number, space, alpha, space, number, punctuation 那么你可能会认为字段分隔符是一个空格,记录由标点分隔,标点也可以显示为嵌入字符。

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

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

    【讨论】:

      【解决方案3】:

      我使用了@rickdenhaan 解决方案,发现 arsort() 和 PHP 版本存在问题。

      如果 eol 为 "\r\n",则 $eols 数组将为:

      数组("\r\n" => 1, "\r" => 1, "\n" => 1);

      (因为除了1个“\r\n”还发现了1个“\r”和1个“\n”)

      并且对于 PHP 7,在 arsort($eols) 之后,键顺序是相同的:

      数组("\r\n" => 1, "\r" => 1, "\n" => 1);

      在“$eol = key($eols);”之后$eol 将是“\r\n”

      但是对于 PHP 5.6,在 arsort($eols) 之后,键的顺序是:

      数组("\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";
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-02-13
        • 2012-05-06
        • 1970-01-01
        • 2010-09-07
        • 2021-10-10
        • 1970-01-01
        • 1970-01-01
        • 2021-12-17
        相关资源
        最近更新 更多