根据列值拆分大型 csv 文本文件
Posted
技术标签:
【中文标题】根据列值拆分大型 csv 文本文件【英文标题】:split large csv text file based on column value 【发布时间】: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:***.com csv split by value
或一些此类变体。祝你好运
【参考方案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:C++ 不是问题。将整个文件加载到内存中是个问题。以上是关于根据列值拆分大型 csv 文本文件的主要内容,如果未能解决你的问题,请参考以下文章
在 Powershell 中,按记录类型拆分大型文本文件的最有效方法是啥?