例如,我有两个csv文件, 0.csv
100a,a,b,c,c
200a,b,c,c,c
300a,c,d,c,c
和 1.csv
100a,Emma,Thomas
200a,Alex,Jason
400a,Sanjay,Gupta
500a,Nisha,Singh
我希望输出像
100a,a,b,c,c,Emma,Thomas
200a,b,c,c,c,Alex,Jason
300a,c,d,c,c,0,0
400a,0,0,0,0,Sanjay,Gupta
500a,0,0,0,0,Nisha,Singh
我如何在Unix shell脚本或Perl中执行此操作?我知道unix“join”命令,这对小文件很有用。例如,为了获得我的结果,我可以做到
join -t , -a 1 -a 2 -1 1 -2 1 -o 0 1.2 1.3 1.4 1.5 2.2 2.3 -e "0" 0.csv 1.csv
但这对我的目的来说是不可行的,因为我的实际数据文件有超过一百万列(总数据大小为千兆字节),因此我的unix命令也将超过一百万个字符。这可能是最重要的问题,因为低效的代码会很快陷入困境。
另请注意,每当缺少数据时,我都需要占位符字符“0”。这使我无法简单地使用此
join -t , -a 1 -a 2 -1 1 -2 1 0.csv 1.csv
也是初学Perl程序员,所以一些细节真的很受欢迎。我更喜欢解决方案是perl或shell脚本,但实际上任何有效的方法都可以。
答案 0 :(得分:1)
您也可以使用awk
执行此操作。
确定两个文件中最宽行的长度,并将其保存到max0
和max1
:
awk -F, '
ARGIND == 1 && NF > max0 { max0 = NF }
ARGIND == 2 && NF > max1 { max1 = NF }
END { print max0, max1 }
' 0.csv 1.csv | read max0 max1
使用此awk脚本进行连接:
<强> foo.awk 强>
BEGIN {
max1--
FS = OFS = ","
}
ARGIND == 1 {
A[$1] = $2
# Copy columns from first file to key
for(i=3; i<=NF; i++)
A[$1] = A[$1] FS $i
# Pad until we have max0 columns
for( ; i<=max0; i++)
A[$1] = A[$1] FS "0"
}
ARGIND == 2 {
# Pad rows which are only in second file
if(A[$1] == "") {
A[$1] = 0
for(i=3; i<=max0; i++)
A[$1] = A[$1] FS "0"
}
# Copy columns from second file to key
for(i=2; i<=NF; i++)
A[$1] = A[$1] FS $i
# Pad until we have max1 columns
for( ; i<=max1; i++)
A[$1] = A[$1] FS "0"
}
END {
for(key in A) {
# Pad rows which are only in first file
split(A[key], fields, ",")
for(i=1; i <= max0+max1-length(fields)-1; i++)
A[key] = A[key] FS "0"
# Finally print key and accumulated column values
print key, A[key]
}
}
使用以下命令运行:
awk -f foo.awk -v max0=$max0 -v max1=$max1 0.csv 1.csv | sort -n
使用-v
传入最宽的行值。输出来自哈希并且未排序,因此在显示之前sort -n
。
答案 1 :(得分:1)
如果您可以为每个文件添加标题,则可以使用tabulator来解决问题。例如:
0.csv:
key,letter_1,letter_2,letter_3,letter_4
100a,a,b,c,c
200a,b,c,c,c
300a,c,d,c,c
1.csv:
key,name_1,name_2
100a,Emma,Thomas
200a,Alex,Jason
400a,Sanjay,Gupta
500a,Nisha,Singh
然后tbljoin -lr -n 0 0.csv 1.csv
生成
key,letter_1,letter_2,letter_3,letter_4,name_1,name_2
100a,a,b,c,c,Emma,Thomas
200a,b,c,c,c,Alex,Jason
300a,c,d,c,c,0,0
400a,0,0,0,0,Sanjay,Gupta
500a,0,0,0,0,Nisha,Singh
请注意(与纯unix join
命令相反),输入文件不需要排序;此外,您不必担心内存消耗,因为实现基于unix排序,并将采用基于文件的大型文件合并排序。
答案 2 :(得分:0)
当您处理大量数据并且两个源的大小大致相同时,合并连接是最佳选择。这是因为一旦两个(每个)源被排序,它就会使用恒定的内存量。合并连接也是完全外连接的一个很好的选择,它可以在Perl中非常优雅地编写。
对于使用以下Perl脚本,您必须在第一列中按键按字典顺序排序这两个文件,并且键必须是唯一的。它还假设两个文件中每行的列数完全相同。
#!/usr/bin/perl
use strict;
use warnings;
use Text::CSV_XS;
die "Usage $0 file1.csv file2.csv" unless @ARGV > 1;
my ( $file1, $file2 ) = @ARGV;
open my $fh1, '<', $file1 or die "Can't open $file1: $!";
open my $fh2, '<', $file2 or die "Can't open $file2: $!";
my $csv = Text::CSV_XS->new( { binary => 1, eol => "\n" } );
my $r1 = $csv->getline($fh1) or die "Missing data in $file1";
my $r2 = $csv->getline($fh2) or die "Missing data in $file2";
# same amount of zeros as number of fields in each file
my @cols1 = (0) x ( @$r1 - 1 );
my @cols2 = (0) x ( @$r2 - 1 );
while ( $r1 || $r2 ) { # there are some data
# compare keys only if there are rows in both files
# zero silences warnings in numeric comparisons below
my $cmp = $r1 && $r2 && ( $$r1[0] cmp $$r2[0] ) || 0;
# row is defined and has less or equal key than another one
my $le1 = $r1 && $cmp < 1;
my $le2 = $r2 && $cmp > -1;
$csv->print(
*STDOUT,
[ $le1 ? $$r1[0] : $$r2[0], # key
( $le1 ? @$r1[ 1 .. @cols1 ] : @cols1 ), # first file fields
( $le2 ? @$r2[ 1 .. @cols2 ] : @cols2 ) # second file fields
]
);
#read next rows
$r1 = $csv->getline($fh1) if $le1;
$r2 = $csv->getline($fh2) if $le2;
}
用法为script.pl 0.csv 1.csv > result.csv
。如果没有排序,请使用sort -u -d -t, -k1,1
对文件进行排序。
脚本以线性时间工作(当已经排序时)并且仅使用存储器来存储来自每个文件的一行,即&#34;常数&#34;大小
您可以使用
对脚本中的文件进行排序$ENV{LC_ALL} = 'C';
open my $fh1, "( sed '1!d' $file1; sed 1d $file1 | sort -u -d -t, -k1,1 ) |"
or die "Can't sort $file1: $!";
open my $fh2, "( sed '1!d' $file2; sed 1d $file2 | sort -u -d -t, -k1,1 ) |"
or die "Can't sort $file2: $!";
答案 3 :(得分:-1)
如果您的文件足够小以适应RAM,这应该很容易。您可以使用Perl读取文件,解析它们,并使用公共行中的值作为哈希键将行推送到数组哈希中。 然后通过其键遍历散列的内容并打印出数组。
答案 4 :(得分:-1)
这就是我提出的(perl):
my $output={};
open FILE1, '</path/to/file1';
while (<FILE1>){
chomp;
my @values=split(/,/, $_);
my $id=shift(@values);
if($output->{$id}){
my $temparray=$output->{$id};
push (@$temparray, @values);
}else{
$output->{$id}=@values;
}
}
close FILE1;
open FILE2, '</path/to/file2';
while (<FILE2>){
chomp;
my @values=split(/,/, $_);
my $id=shift(@values);
if($output->{$id}){
my $temparray=$output->{$id};
push (@$temparray, @values);
}else{
$output->{$id}=@values;
}
}
close FILE2;
答案 5 :(得分:-1)