3

例如,我有两个 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 -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 脚本的解决方案,但实际上任何可行的方法都可以。

4

6 回答 6

2

如果您可以为每个文件添加标题,那么您可以使用制表符来解决问题。例子:

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

请注意(与纯 unixjoin命令相比),输入文件不需要排序;此外,您不必担心内存消耗,因为实现是基于 unix 排序的,并且将对大文件采用基于文件的合并排序。

于 2015-04-04T07:42:14.937 回答
1

您也可以使用awk.

确定两个文件中最宽行的长度并将其保存到max0and 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在显示之前。

于 2012-08-20T22:10:15.513 回答
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未排序,则用于对文件进行排序。

该脚本在线性时间内工作(当已经排序时)并且仅使用内存来存储每个文件中的一行,即“恒定”大小。

您可以使用从脚本中对文件进行排序

$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: $!";
于 2014-10-23T18:11:43.690 回答
-1

csvkit是一种处理 csv 文件并允许此类连接(以及其他功能)的工具。

csvjoin。它的命令行界面很紧凑,可以处理多种 csv 格式(tsv、其他分隔符、编码、转义字符等)

您要求的可以使用:

csvjoin --columns 0 0.csv 1.csv
于 2014-10-23T08:20:15.857 回答
-1

如果您的文件足够小以适合 RAM,这应该很容易。您可以使用 Perl 读取文件、解析它们,然后使用公共行中的值作为散列键将行推入数组散列中。然后通过其键迭代散列的内容并打印出数组。

于 2012-08-20T18:36:48.947 回答
-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;
于 2012-08-20T18:37:07.277 回答