Сопоставление строк в нескольких файлах CSV и объединение определенного поля - PullRequest
2 голосов
/ 29 июля 2010

У меня есть около 20 CSV, которые все выглядят так:

"[email]","[fname]","[lname]","[prefix]","[suffix]","[fax]","[phone]","[business]","[address1]","[address2]","[city]","[state]","[zip]","[setdate]","[email_type]","[start_code]"

То, что мне сказали, что мне нужно произвести, - это одно и то же, но теперь каждый файл содержит стартовый код каждого другогофайл, в котором совпадает электронная почта

Не имеет значения, не совпадают ли какие-либо другие поля, важно только поле электронной почты, и единственным изменением для каждого файла будет добавление любых других значений start_code из других файлов, в которых электронная почтасовпадает.

Например, если одно и то же электронное письмо появилось в wicq.csv, oota.csv и itos.csv, в каждом файле было бы следующее:

"anon@yahoo.com","anon",,,,,,,,,,,,01/16/08 08:05 PM,,"WIQC PDX"
"anon@yahoo.com","anon",,,,,,,,,,,,01/16/08 08:05 PM,,"OOTA"
"anon@yahoo.com","anon",,,,,,,,,,,,01/16/08 08:05 PM,,"ITOS"

"anon@yahoo.com","anon",,,,,,,,,,,,01/16/08 08:05 PM,,"WIQC PDX, OOTA, ITOS"

для всех трех файлов (wicq.csv, oota.csv и itos.csv)

Доступными инструментами будет командная строка OS X (awk, sed и т. д.)), а также perl - хотя я не слишком знаком с этим, и, возможно, есть лучший способ сделать это.

Ответы [ 3 ]

1 голос
/ 30 июля 2010
use strict;
use warnings;
use Text::CSV_XS;

# Supply csv files as command line arguments.
my @csv_files = @ARGV;
my $parser    = Text::CSV_XS->new;

# In my test data, the email is the first field. The field
# to be merged is the second. Adjust accordingly.
my $EMAIL_i   = 0;
my $MERGE_i   = 1;

# Process all files, creating a set of key-value pairs:
#    $sc{EMAIL} = [ LIST OF VALUES OBSERVED IN THE MERGE FIELD ]
my %sc;
for my $cf (@csv_files){
    open(my $fh_in, '<', $cf) or die $!;

    while (my $line = <$fh_in>){
        die "Failed parse : $cf : $.\n" unless $parser->parse($line);
        my @fields = $parser->fields;
        push @{ $sc{$fields[$EMAIL_i]} }, $fields[$MERGE_i];
    }
}

# Process the files again, writing new output.
for my $cf (@csv_files){
    open(my $fh_in,  '<', $cf)             or die $!;
    open(my $fh_out, '>', "${cf}_new.csv") or die $!;

    while (my $line = <$fh_in>){
        die "Failed parse : $cf : $.\n" unless $parser->parse($line);
        my @fields = $parser->fields;

        $fields[$MERGE_i] = join ', ', @{ $sc{$fields[$EMAIL_i]} };

        $parser->print($fh_out, \@fields);
        print $fh_out "\n";
    }
}
0 голосов
/ 30 июля 2010

Вот простая Perl-программа для достижения того, что вам нужно.Он выполняет однократную передачу вашего ввода, полагаясь на тот факт, что он отсортирован заранее.

Он читает строки и добавляет код, пока электронное письмо не изменится.Когда электронное письмо изменяется, оно печатает запись (и исправляет дополнительные двойные кавычки в поле кода).

#!/usr/bin/perl -l

use strict;
use warnings;

my $last_email = undef;
my @current_record = ();
my @fields = ();

sub print_record {
   # Remove repeated double quotes introduced when we appended the code
  $current_record[15] =~ s/""/, /g;
  print join ",", @current_record;
  @current_record = ();
} 

while (my $input_line = <>) {
  chomp $input_line;
  @fields = split ",", $input_line;

  # Print a record when the email we read changes. Avoid printing on the first
  # loop by checking we have read at least one email ($last_email is defined).
  defined $last_email && ($fields[0] ne $last_email) && print_record;

  if (!@current_record)  {
    # We are starting to process a new email. Grab all fields.
    @current_record = @fields;
  }
  else {
    # We have consecutive records with the same email. Append the code.
    $current_record[15] .= $fields[15];
  }

  # Remember the last processed email. When it changes we will print @current_record.
  $last_email = $fields[0];
}

# Print the last record
print_record

Ключ -l для печати автоматически добавляет символ новой строки (какой бы ни была ОС).

Назовите это так:

sort *.csv | ./script.pl
0 голосов
/ 29 июля 2010

Я бы подошел к этому, сделав что-то вроде:

cut -d ',' -f1,16 *.csv | 
    sort |
    awk -F, '{d=""; if (array[$1]) d=","; array[$1] = array[$1] d $2} END { for (i in array) print i "," array[i]}' |
    while IFS="," read -r email start; do sed -i "/^$email,/ s/,[^,]*\$/,$start/" *.csv; done

Это создает список всех писем (cut / sort) и start_codes и объединяет их (awk) Затем он заменяет (sed) начальный_код для каждого соответствующего электронного письма в каждом файле (while).

Но я чувствую, что должен быть более эффективный способ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...