#perl #unix #awk #nawk
#perl #unix #awk #nawk
Вопрос:
Мне нужно сравнить 2 файла по столбцам на основе первичных столбцов (это может быть 1 или несколько столбцов в качестве основного ключа). И в качестве выходных данных должно быть сгенерировано 3 CSV-файла — различия, дополнительные записи в file1, дополнительные записи в file2
Примечание: пробовал с sdiff
, но он не выдает желаемый результат
Пример :
Здесь первый столбец является первичным ключом
file1 :
abc 234 123
bcd 567 890
cde 678 789
file2 :
abc 234 012
bcd 532 890
cdf 678 789
Output files
differences file :
abc,234,123::012
bcd,567::532,890
extra records in file1 :
cde,678,789
extra records in file2
cdf,678,789
Комментарии:
1. проверьте
comm
binary, стандартный инструмент командной строки unixcomm --help
2. comm выдаст согласованные записи, но не выделит различия в том, в каком столбце
3. вы можете преобразовать данные в «длинный формат», сначала ознакомьтесь с документацией R-tidyverse для получения подробной информации.
Ответ №1:
Если файлы удобно помещаются в памяти, это довольно легко сделать с помощью хэшей в Perl. Например:
#!/bin/bash
# create test data files
>cmp.d1 cat <<'EOD'
abc 234 123
bcd 567 890
cde 678 789
EOD
>cmp.d2 cat <<'EOD'
abc 234 012
bcd 532 890
cdf 678 789
EOD
# create script
>dif.pl cat <<'EOD'
#!/usr/bin/perl -w
if ( $#ARGV!=0 or ! -f "$ARGV[0]" ) {
die "Usage: <file2 filter file1n";
}
@KEYS = ( 0 ); # list of columns to use for primary key
# read file1 from filename given on commandline
while (<<>>) {
chomp;
@a1 = (split); # split line into individual fields
$k = join "", @a1[ @KEYS ];
# if $k is not unique, only final line is kept
warn "duplicate key: $kn" if exists $h1{$k};
# store line in %h1 for later use
$h1{$k} = [ @a1 ];
}
# now read file2 from stdin
# process each line as we read it
while (<<>>) {
chomp;
@a2 = (split); # split line into individual fields
$k = join "", @a2[ @KEYS ];
if ( exists $h1{$k} ) {
# record exists in both files
# calculate differences
@a1 = @{ $h1{$k} }; # retrieve file1 version
# overwrite any difference fields in @a2
map {
$a1 = shift @a1;
$_ = "${a1}::$_" if $a1 ne $_;
} @a2;
# save difference records in %hd
$hd{$k} = [ @a2 ];
# this will not be an extra file1 record
delete $h1{$k};
}
else {
# this record only exists in file2
$h2{$k} = [ @a2 ];
}
}
# format record as csv line
sub print_csv {
print join(",", @{ $_ }), "n";
}
print "differences file :n";
print_csv for values %hd;
print "n";
print "extra records in file1 :n";
print_csv for values %h1;
print "n";
print "extra records in file2n";
print_csv for values %h2;
EOD
# try it out
perl dif.pl cmp.d1 <cmp.d2
Вывод:
differences file :
bcd,567::532,890
abc,234,123::012
extra records in file1 :
cde,678,789
extra records in file2
cdf,678,789
Примечание: вывод csv обычно не требуется упорядочивать, поэтому этот код не выполняет никакой сортировки.
Ответ №2:
Попробуйте эту командную строку Perl
perl -lane ' @t=@{$kv1{$F[0]}}; push(@t,$_); $kv1{$F[0]} = [@t];
if( defined($kv2{$F[0]}) ) { $kv2{$F[0]} = "Both" } else { $kv2{$F[0]} =$ARGV; $kv3{$F[0]}=$_; }
END {
for my $c (keys %kv2)
{
if($kv2{$c} eq "Both") { $d1 or print "differences file :";
@t=@{$kv1{$c}}; @s1=split(" ",$t[0]); @s2=split(" ",$t[1]);
$a2= $s1[1] eq $s2[1] ? $s1[1] : $s1[1]. "::". $s2[1];
$a3= $s1[2] eq $s2[2] ? $s1[2] : $s1[2]. "::". $s2[2];
print $s1[0],",",$a2,",",$a3;
}
}
for my $c (keys %kv2)
{
if($kv2{$c} eq "file1") { $d2 or print "nextra records in file1 :"; print $kv3{$c} }
}
for my $c (keys %kv2)
{
if($kv2{$c} eq "file2") { $d3 or print "nextra records in file2 :"; print $kv3{$c} }
}
}
' file1 file2
Результаты:
differences file :
bcd,567::532,890
abc,234,123::012
extra records in file1 :
cde 678 789
extra records in file2 :
cdf 678 789