这是另外两个问题的组合(如何按每行前缀分割文件和如何根据列拆分文件,包括标题)。我想从以下内容开始input.csv
:
id,first,second,third
1,a,b,c
333,b,b,b
1,d,e,f
2,d,e,f
1,c,d,e
333,a,a,a
[more lines in the same format]
到此内容1.csv
:
id,first,second,third
1,a,b,c
1,d,e,f
1,c,d,e
,此内容在2.csv
:
id,first,second,third
2,d,e,f
,以及此内容333.csv
:
id,first,second,third
333,b,b,b
333,a,a,a
, 那是:
- 将所有 ID 为氮进入
N.csv
。 - 保持行的顺序和原来一样。
- 包含标题来自所有输出文件中的原始文件。
这也必须非常快,所以while read
循环是不是要去砍它。
答案1
这个 GNU awk 命令可以解决这个问题:
awk -F ',' 'NR==1{h=$0; next};!seen[$1]++{f=$1".csv"; print h > f};{f=$1".csv"; print >> f; close(f)}' input.csv
警告:如果第一个字段中有转义逗号,这将不起作用。其他字段中的逗号应该可以正常工作。
解释:
-F ','
(字段分隔符)确保$1
等引用 CSV 列而不是空格分隔的值。NR==1{h=$0; next}
NR==1
通过将完整的标题行存储在变量h
(h=$0
) 中并跳过该行 ( ),特殊对待第一行 ( )next
。!seen[$1]++{f=$1".csv"; print h > f}
通过将后跟存储到文件名变量并将标头保存到该文件 ( ) 来处理任何$1
特殊 ( )的第一次出现。!seen[$1]
$1
.csv
f
print h > f
{f=$1".csv"; print >> f; close(f)}
将当前行添加到文件 (print >> f
) 并关闭文件描述符 (close(f)
) 以避免在处理完具有特定 ID 的所有行后保留它。
奖励:如果您替换$1
为另一个字段,它应该执行您期望的操作:为该列中的每个唯一值创建一个文件,其中包含给定列中包含该值的行。
答案2
(很抱歉用另一个答案向大家发送垃圾邮件)对于许多情况,所提供的优雅的 awk 版本是完美的。但除了俏皮话之外还有生活——我们常常需要更多:
- 添加额外的代码来处理复杂的 csv 文件;
- 添加额外的标准化、重新格式化、处理步骤。
在下面的框架中,我们使用 CSV 文件的解析器。这次我们避免使用一整型,甚至严格声明变量!
#!/usr/bin/perl
use strict;
use Parse::CSV;
my %dict=();
my $c = Parse::CSV->new(file => 'a1.csv');
while ( my $row = $c->fetch ) { ## for all records
$dict{$row->[0]} .= join(" :: ",@$row)."\n"; ## process and save
}
for my $k (keys %dict){ ## create the cvs files
open(F,">","$k.cvs") or die;
print F $dict{$k};
close F;
}
- 主要优点是我们可以处理更复杂的csv文件;这次 csv 输入可以包含带“;”的字符串,可以包含多行字段(csv 规范很复杂!):
1111,2,3
"3,3,3",a,"b, c, and d"
"a more, complex
multiline record",3,4
- 为了举例说明处理步骤,字段分隔符更改为“ :: ”
- 为了举例说明额外的步骤,我们添加了一些优化:由于我们使用了字典缓存,因此该脚本的运行速度比我的其他解决方案快 100 倍。
答案3
这不是一个答案,而只是 IObO 优秀答案的避免滚动变体......
awk -F, 'NR==1{h=$0; next} {print seen[$1]++ ? $0 : h "\n" $0 >$1 ".csv"}'
答案4
old school version using only pipes and no awk
:
警告:它的运行速度平均比上述awk
解决方案慢,速度是输入文件中键数量的一个因素
cut -d , -f 1 input.csv | fgrep -v id | sort | uniq | xargs -n1 sh -c '(head -n1 input.csv && egrep "^${0}," input.csv) > ${0}.csv'
它的作用是:
cut -d , -f 1 input.csv
按字符分割文件的每一行,
并抓取第一列 (-f 1
) 以仅保留键fgrep -v id
跳过标题sort | uniq
排序并仅保留每个键之一xargs -n1 sh -c '<sub shell>'
对于每个键执行一个子 shellhead -n1 input.csv
子 shell 的第一部分获取输入文件的标头- 然后
egrep "^${0}," input.csv
抓取与键匹配的行,它可能不明显,但这是每行循环,这就是它很慢的原因 - 最后
> ${0}.csv
将输出写入由密钥命名的文件中