Используя Perl или Powershell, как сравнить 2 CSV-файла и получить только новые строки? - PullRequest
0 голосов
/ 29 мая 2018

Я сравниваю два больших CSV-файла с разделителями-запятыми File1.csv и File2.csv с использованием Perl-модуля Text::Diff.Программа Perl вызывается из файла .bat, и я помещаю результат в третий файл Diff.csv

Perl

#!/usr/bin/env perl

use strict;
use warnings;

use Text::Diff;

my $diffs = diff $ARGV[0] => $ARGV[1];

$diffs =~ s/^(?:[^\n]*+\n){2}//;
$diffs =~ s/^(?:[\@ ][^\n]*+)?+\n//mg;

print $diffs;

Вот как я называю скрипт Perl:

perl "C:\diffBetweenTwoFiles.pl" "C:\File1.csv" "C:\File2.csv" > "C:\Diff.csv"

Один из столбцов в CSV-файле - Name.

В настоящее время в результате отображаются все строки, значения которых в любых столбцах изменяются, но я хочу только перечислить новые Name строки.

Например:

File1.csv

"Name","DOB","Address"
"One","1/1/01","5 Stock Rd"
"Two","1/2/02","1 Research Rd"

File2.csv

"Name","DOB","Address"
"One","1/1/01","5 Stock Rd"
"Two","1/2/02","111 Research Rd"
"Three","1/3/03","3 Bold Rd"

В настоящее время список результатов этих (он включает в себя "Два ", потому что его адрес изменился):

"Name","DOB","Address"
"Two","1/2/02","111 Research Rd"
"Three","1/3/03","3 Bold Rd"

Но я только хочу, чтобы результат перечислил новое" Имя "следующим образом:

"Name","DOB","Address"
"Three","1/3/03","3 Bold Rd"

Как я могу сделать это в Perlили скрипт Powershell?

Ответы [ 2 ]

0 голосов
/ 29 мая 2018

Поскольку вы работаете с большими файлами, которые ограничивают ваш объем памяти, вы можете попробовать:

  1. Считать первый CSV-файл по одной строке за раз и использовать хеш-таблицу для хранения файла.Записи имени.
  2. Читайте второй CSV-файл по одной строке за раз и сравнивайте его записи имени с первой.

( ОБНОВЛЕНО на основе комментариев) Aпростой пример в PowerShell:

$output = New-Object System.Text.StringBuilder;
$file1 = @{};
$header = $null;

# $filePaths is two-element array with full path to CSV files
for ($i = 0; $i -lt $filePaths.Length; ++$i) {
    $reader = New-Object System.IO.StreamReader($filePaths[$i]);
    while (($line = $reader.ReadLine()) -ne $null) {
        if ($line -match '\S') {
            if ($header -eq $null) { 
                $header = $line;
                $output.AppendLine($line) | Out-Null; 
            }
            $name = ($line -split ',')[0];
            switch ($i) {
                0 { $file1.Add($name, $null); }
                1 { 
                    if (!$file1.ContainsKey($name)) { 
                        $output.AppendLine($line) | Out-Null; 
                    } 
                }
            }
        }
    }
    $reader.Dispose();
}
$output.ToString() | Out-File -FilePath $outPath;
0 голосов
/ 29 мая 2018

Использование Text :: CSV в Perl

use warnings;
use strict;
use feature 'say';
use Text::CSV;

my ($file_old, $file_new, $file_diff) = 
    map { $_ . '.csv' } qw(File1 File2 Diff);

my $csv = Text::CSV->new ( { binary => 1 } )
    or die "Cannot use CSV: ".Text::CSV->error_diag();

my ($old, $header) = get_lines($csv, $file_old, 1);
my $new            = get_lines($csv, $file_new);

my @lines_with_new_names = @{ new_names($old, $new) };

open my $fh, '>', $file_diff  or die "Can't open $file_diff: $!";
$csv->say($fh, $header);
$csv->say($fh, $_) for @lines_with_new_names;  # or print with eol set

sub new_names {
    my ($old, $new) = @_;
    my %old = map { $_->[0] => 1 } @$old;
    return [ map { (!exists $old{$_->[0]}) ? $_ : () } @$new ];
}

sub get_lines {
    my ($csv, $file, $return_header) = @_;
    open my $fh, '<', $file or die "Can't open $file $!";
    my $header = $csv->getline($fh);  # remove the header line
    return ($return_header) 
        ? ( $csv->getline_all($fh), $header )
        :   $csv->getline_all($fh);
}

Это печатает правильную разницу с предоставленными образцами.

Связанные имена, помеченные old, связаныв файл с меньшим количеством строк, другой является new.Столбец «Имя» считается первым.

Комментарии

  • Метод getline_all возвращает arrayref для всех строк, где каждая представляет собой arrayref свсе поля.Это делается из подпрограммы, с возможностью также вернуть строку заголовка.

  • Необязательный возврат другой переменной здесь делает разницу в том, возвращается ли один скаляр или список, поэтому он также может быть обработан с помощью wantarray встроенный

    return wantarray ? ( LIST ) : scalar;
    

    , который возвращает true, если sub вызывается в контексте списка.Таким образом, вызывающая сторона решает, вызывая подпрограмму либо в списке, либо в скалярном контексте, my ($v1, $v2) = f(...) или my $v = f(...), и в этом случае флаг не требуется при вызове.Я выбрал более явный способ.

  • Разница в списке имен производится в подпункте new_names.Сначала создается хеш поиска со всеми именами из «старого» массива.Затем строки в «новом» arrayref фильтруются, принимая те, у которых нет имени в «старом» (такого ключа нет в хэше), и возвращаются в массиве ref [].

    Такое использование хэша является стандартной техникой для поиска различий между массивами.

Документированный метод say, используемый для печати, не работает в моей старой версиимодуля, с которым это проверено.В этом случае используйте print и установите eol в конструкторе.

...