【问题标题】:split large csv text file based on column value根据列值拆分大型 csv 文本文件
【发布时间】:2012-04-14 15:15:12
【问题描述】:

我的 CSV 文件有多个已排序的列。例如,我可能有这样的行:

19980102,,PLXS,10032,Q,A,,,15.12500,15.00000,15.12500,2
19980105,,PLXS,10032,Q,A,,,14.93750,14.75000,14.93750,2
19980106,,PLXS,10032,Q,A,,,14.56250,14.56250,14.87500,2
20111222,,PCP,63830,N,A,,,164.07001,164.09000,164.12000,1
20111223,,PCP,63830,N,A,,,164.53000,164.53000,164.55000,1
20111227,,PCP,63830,N,A,,,165.69000,165.61000,165.64000,1

我想根据第 3 列划分文件,例如将 PLXS 和 PCP 条目放入它们自己的名为 PLXS.csv 和 PCP.csv 的文件中。因为文件恰好是预先排序的,所以所有 PLXS 条目都在 PCP 条目之前,依此类推。

我通常最终会在 C++ 中做这样的事情,因为这是我最了解的语言,但在这种情况下,我的输入 CSV 文件有几个 GB 并且太大而无法在 C++ 中加载到内存中。

有人能说明如何做到这一点吗? Perl/Python/php/bash 解决方案都可以,它们只需要能够处理巨大的文件而不会占用过多的内存。

【问题讨论】:

  • 你浏览过吗?本网站上所有上述语言的几个相关问题等等。您可以搜索:site:stackoverflow.com csv split by value 或一些此类变体。祝你好运

标签: csv text split large-data


【解决方案1】:

这是一个适合您的老式单行代码(只需将 >> 替换为 > 以在每次运行时截断输出文件):

awk -F, '{print >> ($3".csv")}' input.csv

由于大众的需求(以及我刚刚的痒),我还编写了一个版本,可以将标题行复制到所有文件:

awk -F, 'NR==1 {h=$0; next} {f=$3".csv"} !($3 in p) {p[$3]; print h > f} {print >> f}' input.csv

但你可以从这个开始,然后以第一个 awk 结束:

HDR=$(head -1 input.csv); for fn in $(tail -n+2 input.csv | cut -f3 -d, | sort -u); do echo $HDR > $fn.csv; done

大多数现代系统都包含 awk 二进制文件,但如果您没有,您可以在 Gawk for Windows 找到一个 exe 文件

【讨论】:

  • 这太棒了 :) 如果我们能保留标题就更好了
  • 原始文件中没有标题。也许你可以问一个不同的问题?
  • 这对我有帮助 - 但要改进 - 可以使用 NR==1 {hdr=$0; next} 简化标题示例,然后您无需检查后续模式的 NR,因为您无法访问这些模式并且不是由于 Awk 的排序规则,NR>1。来自手册页:Each pattern in the program then shall be evaluated in the order of occurrence, and the action associated with each pattern that matches the current record executed.
  • 还有一个小的改进 - 文件名模式 {fn=$3".csv"} 应该出现在 NR==1 模式之后,因为它仅由遵循此模式的模式使用。
  • 感谢您的反馈,@Phil。我不相信探查器会在您提出建议后显示出任何更改或改进,并且我相信会混淆标准 awk 语言结构支持的明确意图(您很有帮助地包括在内,我觉得也很清楚)。 1.添加next不改变逻辑。删除有意定义代码块用途的显式 awk 指令将是您提供的报价的反模式。 2. 对程序中引用的模式重新排序不会更改代码执行计划,因此请随意编辑您的副本!
【解决方案2】:

如果输入文件中没有标题行

awk -F, '
{fn = $3".csv"
 print > fn}' bigfile.csv

如果有一个标题行应该传递给拆分的文件

awk -F, '
NR==1 {hdr=$0; next}
{fn = $3".csv"}
!seen[$3]++{print hdr > fn}
{print > fn}' bigfile.csv

【讨论】:

    【解决方案3】:
    perl -F, -ane '`echo $_ >> $F[2].csv`' < file
    

    使用这些命令行选项:

    • -n 循环输入文件的每一行
    • -l 在处理之前删除换行符,然后将它们添加回
    • -a 自动拆分模式 - 将输入行拆分为 @F 数组。默认为空格分割。
    • -e执行perl代码
    • -F 自动拆分修饰符,在这种情况下拆分 ,

    @F 是每行中的单词数组,索引以$F[0] 开头


    如果要保留标头,则需要更复杂的方法。

    perl splitintofiles.pl file

    splitintofiles.pl 的内容:

    open $fh, '<', $ARGV[0];
    while ($line = <$fh>) {
        print $line;
        if ($. == 1) {
            $header = $line;
        } else {
            # $fields[2] is the 3rd column
            @fields = split /,/, $line;
            # save line into hash %c
            $c{"$fields[2].csv"} .= $line;
        }
    }
    close $fh;
    for $file (keys %c) {
        print "$file\n";
        open $fh, '>', $file;
        print $fh $header;
        print $fh $c{$file};
        close $fh;
    }
    

    输入:

    a,b,c,d,e,f,g,h,i,j,k,l
    19980102,,PLXS,10032,Q,A,,,15.12500,15.00000,15.12500,2
    19980105,,PLXS,10032,Q,A,,,14.93750,14.75000,14.93750,2
    19980106,,PLXS,10032,Q,A,,,14.56250,14.56250,14.87500,2
    20111222,,PCP,63830,N,A,,,164.07001,164.09000,164.12000,1
    20111223,,PCP,63830,N,A,,,164.53000,164.53000,164.55000,1
    20111227,,PCP,63830,N,A,,,165.69000,165.61000,165.64000,1
    

    输出 PCP.csv

    a,b,c,d,e,f,g,h,i,j,k,l
    20111222,,PCP,63830,N,A,,,164.07001,164.09000,164.12000,1
    20111223,,PCP,63830,N,A,,,164.53000,164.53000,164.55000,1
    20111227,,PCP,63830,N,A,,,165.69000,165.61000,165.64000,1
    

    输出 PLXS.csv

    a,b,c,d,e,f,g,h,i,j,k,l
    19980102,,PLXS,10032,Q,A,,,15.12500,15.00000,15.12500,2
    19980105,,PLXS,10032,Q,A,,,14.93750,14.75000,14.93750,2
    19980106,,PLXS,10032,Q,A,,,14.56250,14.56250,14.87500,2
    

    【讨论】:

      【解决方案4】:

      如果文件的前三列没有引号,则简单的单行是:

      cat file | perl -e 'while(<>){@a=split(/,/,$_,4);$key=$a[2];open($f{$key},">$key.csv") unless $f{$key};print {$f{$key}} $_;} for $key (keys %f) {close $f{$key}}'
      

      它不会消耗太多内存(仅存储关联 distinct(3rd_column) --> 文件句柄)并且行可以按任何顺序排列。

      如果列更复杂(例如包含引号括起来的逗号),则使用Text::CSV

      【讨论】:

      • 实际上,我只是注意到这与下面 Sean Summers 的答案基本相同。
      【解决方案5】:

      另一种解决方案是将 CSV 加载到 Solr 索引中,然后根据您的自定义搜索条件生成 CSV 文件。

      这是一个基本的 HOWTO:

      Create report and upload to server for download

      【讨论】:

        【解决方案6】:

        如果你最了解 C++,它就很好。你为什么要尝试将整个文件加载到内存中?

        由于输出取决于正在读取的列,您可以轻松地为输出文件存储缓冲区,并在处理时将记录填充到适当的文件中,同时清理以保持内存占用相对较小。

        当需要从数据库中提取大量数据时,我会这样做(尽管是在 java 中)。记录被推送到文件缓冲区流中,并且内存中的任何内容都被清理,因此程序的占用空间永远不会超过它最初开始时的大小。

        坐以待毙伪代码:

        1. 创建一个列表来保存您的输出文件缓冲区
        2. 在文件中打开流并开始一次读取一行内容
        3. 我们是否遇到过具有针对其内容类型的打开文件流的记录?
          • 是的 -
            • 获取存储的文件流
            • 将记录存储到该文件中
            • 刷新流
          • 没有 -
            • 创建一个流并将其保存到我们的流列表中
            • 将记录存储在流中
            • 刷新流
        4. 冲洗重复...

        基本上会继续此处理,直到我们到达文件末尾。

        由于我们存储的不仅仅是指向流的指针,而且我们一写入流就会刷新,因此除了输入文件中的一条记录之外,我们不会在应用程序的内存中保存任何驻留的内容。因此,足迹保持可管理。

        【讨论】:

        • +1:C++ 不是问题。将整个文件加载到内存中是个问题。
        猜你喜欢
        • 1970-01-01
        • 2022-01-16
        • 1970-01-01
        • 2017-10-10
        • 2015-03-06
        • 2022-01-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多