【问题标题】:Matching lines across multiple csv files and merging a particular field跨多个 csv 文件匹配行并合并特定字段
【发布时间】:2011-03-22 21:03:56
【问题描述】:

我有大约 20 个 CSV,看起来都像这样:

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

我被告知我需要生成的是完全相同的东西,但现在每个文件都包含来自电子邮件匹配的每个其他文件的 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——虽然我对这两个都不太熟悉,但可能有更好的方法来做到这一点。

【问题讨论】:

  • 所以这些修改 WIQC, PDX, OOTA, ITOS 会在这三个 csv 文件中的每个人中出现?
  • @安德斯,是的。 (虽然 WICQ PDX 是一个单一的修改,而不是您评论中提到的两个)。

标签: perl bash scripting csv


【解决方案1】:
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";
    }
}

【讨论】:

  • 这很好用!我不得不输入 "binmode $fh_in, ":utf8";"并手动清除每个文件中的一些空白行(:g/^$/d),但这有效。谢谢。
【解决方案2】:

我会通过以下方式来解决这个问题:

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) 中每个匹配电子邮件的 start_code。

但我觉得必须有更有效的方法。

【讨论】:

  • 我将所有文件重命名为以小写字符开头,因为任何带有大写字符的文件都会出现此错误:"sed: 1: "R2R.csv": invalid command code R" 我现在是收到此错误:“sed: 1: "bwtl.csv": undefined label 'wtl.csv'" 我认为这是由相同的初始问题导致的,即 sed 将文件名作为命令。
  • @alex:仔细检查以确保您没有遗漏星号之前的空格,或者您有任何放错位置的引号。您在基于 GNU(例如 Linux)的系统上吗?您的文件在数据中有斜线吗?您可以尝试将 sed 命令中的分隔符更改为管道 ('s|old|new|') 或其他一些不在您的数据中的字符。
【解决方案3】:

这是一个简单的 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-08-21
    • 2021-04-25
    • 2018-03-27
    • 2019-11-29
    • 2012-03-28
    • 2021-04-28
    • 1970-01-01
    相关资源
    最近更新 更多