基于列值拆分大型csv文本文件

时间:2012-03-30 23:00:20

标签: csv text split large-data

我的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解决方案都可以,他们只需要能够处理庞大的文件而不会占用过多的内存。

6 个答案:

答案 0 :(得分:32)

这里有一个老式的学校班轮(只需用>>替换>来截断每次运行的输出文件):

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文件

答案 1 :(得分:1)

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

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

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

飞到我的裤子伪码的座位上:

  1. 创建一个列表来保存输出文件缓冲区
  2. 在文件上打开流并一次开始一行读取内容
  3. 我们是否遇到过针对其内容类型打开文件流的记录?
    • 是的 -
      • 获取存储的文件流
      • 将记录存储到该文件中
      • 刷新流
    • 不 -
      • 创建一个流并将其保存到我们的流列表中
      • 将记录存储在流
      • 刷新流
  4. 冲洗重复......
  5. 基本上继续这个处理,直到我们在文件的末尾。

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

答案 2 :(得分:1)

perl -F, -ane '`echo $_ >> $F[2].csv`' < file

使用以下命令行选项:

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

@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

答案 3 :(得分:0)

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

这是一个基本的HOWTO:

Create report and upload to server for download

答案 4 :(得分:0)

如果你文件的前三列没有逗号,那么简单的单行是:

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}}'

它不会消耗太多内存(只存储了不同的关联(3rd_column) - &gt;文件句柄)并且行可以按任何顺序排列。

如果列更复杂(例如包含带引号的逗号),请使用Text::CSV

答案 5 :(得分:0)

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

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