मैं दो बड़े कॉमा-सीमांकित सीएसवी फाइलों File1.csv और File2.csv की तुलना कर रहा हूं Text::Diff पर्ल मॉड्यूल। पर्ल प्रोग्राम को .bat फ़ाइल से कॉल किया जाता है और मैंने परिणाम को तीसरी फ़ाइल Diff.csv में डाल दिया है

पर्ल

#!/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 "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"

मैं इसे पर्ल या पावरहेल स्क्रिप्ट में कैसे कर सकता हूं?

2
faujong 29 मई 2018, 21:30

2 जवाब

सबसे बढ़िया उत्तर

पर्ल में Text::CSV का उपयोग करें

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 विधि सभी पंक्तियों के लिए एक सरणी देता है, जहां प्रत्येक सभी क्षेत्रों के साथ एक सरणी है। यह एक उप से किया जाता है, साथ ही हेडर लाइन को वापस करने के विकल्प के साथ।

  • यहां किसी अन्य चर के वैकल्पिक रिटर्न से फर्क पड़ता है कि क्या एकल स्केलर या सूची लौटाई गई है, इसलिए इसे wantarray बिलिन

    return wantarray ? ( LIST ) : scalar;
    

    जो सच हो जाता है अगर उप को सूची संदर्भ में बुलाया जाता है। इस प्रकार कॉलर सूची या स्केलर संदर्भ, my ($v1, $v2) = f(...) या my $v = f(...) में उप का आह्वान करके निर्णय लेता है, जिस स्थिति में कॉल में ध्वज की आवश्यकता नहीं होती है। मैंने अधिक स्पष्ट तरीका चुना।

  • नामों की सूची में अंतर new_names उप में उत्पन्न होता है। सबसे पहले "पुराने" सरणी से सभी नामों के साथ एक लुकअप हैश बनाया जाता है। फिर "नई" सरणी में पंक्तियों को फ़िल्टर किया जाता है, जो "पुराने" (हैश में ऐसी कोई कुंजी नहीं) में नाम नहीं रखते हैं, और एक सरणी [] में वापस आ जाते हैं।

    हैश का ऐसा उपयोग सरणियों के बीच अंतर खोजने के लिए एक मानक तकनीक है।

मुद्रण के लिए प्रयुक्त दस्तावेजी विधि say मॉड्यूल के मेरे पुराने संस्करण पर काम नहीं करती है जिसके साथ इसका परीक्षण किया जाता है। उस स्थिति में print का उपयोग करें और eol कंस्ट्रक्टर में।

1
zdim 3 जून 2018, 08:34

चूंकि आप बड़ी फ़ाइलों के साथ काम कर रहे हैं जो आपकी स्मृति सीमा पर जोर दे रही हैं, आप कोशिश कर सकते हैं:

  1. पहली CSV फ़ाइल को एक बार में एक पंक्ति पढ़ें, और फ़ाइल की नाम प्रविष्टियों को संग्रहीत करने के लिए हैशटेबल का उपयोग करें।
  2. दूसरी CSV फ़ाइल को एक बार में एक पंक्ति पढ़ें और पहली के साथ इसकी नाम प्रविष्टियों की तुलना करें।

(टिप्पणियों के आधार पर अद्यतन) 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;
1
kuujinbo 30 मई 2018, 01:10