【问题标题】:Two csv files: Change one csv by the other and pull out that line两个 csv 文件:将一个 csv 更改为另一个并拉出该行
【发布时间】:2015-11-07 16:35:58
【问题描述】:

我有两个 CSV 文件。第一个是列表文件,它包含 ID 和名称。例如

1127100,Acanthocolla cruciata  
1127103,Acanthocyrta haeckeli  
1127108,Acanthometra fusca 

第二个是我要交换的内容,如果找到匹配项,则按第一个数字提取该行。每个文件中的第一列数字对应。例如

1127108,1,0.60042  
1127103,1,0.819671  
1127100,2,0.50421,0.527007  
10207,3,0.530422,0.624466   

所以我想最终得到这样的 CSV 文件

Acanthometra fusca,1,0.60042  
Acanthocyrta haeckeli,1,0.819671  
Acanthocolla cruciata,2,0.50421,0.527007

我尝试了 Perl,但一次打开两个文件被证明是一团糟。所以我尝试将其中一个 CSV 文件转换为字符串并以这种方式解析它,但没有奏效。但后来我读到了grep 和其他单行词,但我不熟悉它。用 grep 可以吗?

这是我试过的 Perl 代码

use strict;
use warnings;

 open my $csv_score, '<', "$ARGV[0]" or die qq{Failed to open "$ARGV[0]" for input: $!\n};
 open my $csv_list,  '<', "$ARGV[1]" or die qq{Failed to open "$ARGV[1]" for input: $!\n};
 open my $out, ">$ARGV[0]_final.txt" or die qq{Failed to open for output: $!\n};

  my $string = <$csv_score>;

  while ( <$csv_list> ) {

    my ($find, $replace) = split /,/; 
    $string =~ s/$find/$replace/g;

         if ($string =~ m/^$replace/){
         print $out $string;
      }
  }

close $csv_score;
close $csv_list;
close $out;

【问题讨论】:

  • 请显示您遇到问题的代码。如果没有,我怀疑,那么你应该离开并写一些,因为 Stack Overflow 是一个知识库,而不是完成工作的免费方式。我们将帮助您解决代码的特定问题,但我们通常不会针对您的规范实施解决方案
  • @Borodin 编辑战! :D
  • 乍一看实际上是相当清晰的 Perl 代码。你自己写的吗?
  • 我包含了我正在谈论的 perl 代码。
  • 这些文件有多大?

标签: perl csv awk grep


【解决方案1】:

所有 UNIX 安装附带的通用文本处理工具名为 awk:

$ awk -F, -v OFS=, 'NR==FNR{m[$1]=$2;next} $1=m[$1]' file1 file2
Acanthometra fusca,1,0.60042
Acanthocyrta haeckeli,1,0.819671
Acanthocolla cruciata,2,0.50421,0.527007

【讨论】:

    【解决方案2】:

    您的代码失败了,因为您只读取了$csv_score 文件的第一行,并且每次更改时都尝试打印$string。您也未能从 $csv_list 文件的行尾删除换行符。如果你修复这些东西,那么它看起来像这样

    use strict;
    use warnings;
    
    open my $csv_score, '<', "$ARGV[0]" or die qq{Failed to open "$ARGV[0]" for input: $!\n};
    open my $csv_list, '<', "$ARGV[1]" or die qq{Failed to open "$ARGV[1]" for input: $!\n};
    open my $out, ">$ARGV[0]_final.txt" or die qq{Failed to open for output: $!\n};
    
    my $string = do {
        local $/;
        <$csv_score>;
    };
    
    while ( <$csv_list> ) {
        chomp;
    
        my ( $find, $replace ) = split /,/;
        $string =~ s/$find/$replace/g;
    }
    
    print $out $string;
    
    close $csv_score;
    close $csv_list;
    close $out;
    

    输出

    Acanthometra fusca,1,0.60042
    Acanthocyrta haeckeli,1,0.819671
    Acanthocolla cruciata,2,0.50421,0.527007
    10207,3,0.530422,0.624466
    

    但这不是一种安全的做事方式,因为 ID 可能在行首以外的其他地方找到

    我会像这样从$csv_list 文件中构建一个哈希,这也使程序更简洁

    use strict;
    use warnings;
    use v5.10.1;
    use autodie;
    
    my %ids;
    {
        open my $fh, '<', $ARGV[1];
        while ( <$fh> ) {
            chomp;
            my ($id, $name) = split /,/;
            $ids{$id} = $name;
        }
    }
    
    open my $in_fh,  '<',  $ARGV[0];
    open my $out_fh, '>', "$ARGV[0]_final.txt";
    
    while ( <$in_fh> ) {
        s{^(\d+)}{$ids{$1} // $1}e;
        print $out_fh $_;
    }
    

    输出与上面第一个程序的输出相同

    【讨论】:

    • 打败我!我可能会添加一个警告,以防 ID 出现在一个文件中而不是另一个文件中 - 但可能是不必要的或对 OP 的练习;)
    【解决方案3】:

    编写的代码的问题是您只执行此操作一次:

    my $string = <$csv_score>;
    

    这从$csv_score 读取一行,而您永远不会使用其余的。

    我建议您需要:

    • 将第一个文件读入散列
    • 迭代第二个文件,并对第一列进行替换。
    • 使用Text::CSV 通常是处理它的好主意,但对于您的示例而言,似乎不是必需的。

    所以:

    #!/usr/bin/env perl
    use strict;
    use warnings;
    use Text::CSV;
    use Data::Dumper;
    
    my $csv = Text::CSV->new( { binary => 1 } );
    
    my %replace;
    
    while ( my $row = $csv->getline( \*DATA ) ) {
        last if $row->[0] =~ m/NEXT/;
        $replace{ $row->[0] } = $row->[1];
    }
    
    print Dumper \%replace;
    
    my $search = join( "|", map {quotemeta} keys %replace );
    $search =~ qr/($search)/;
    
    while ( my $row = $csv->getline( \*DATA ) ) {
        $row->[0] =~ s/^($search)$/$replace{$1}/;
        $csv->print( \*STDOUT, $row );
        print "\n";
    }
    
    __DATA__
    1127100,Acanthocolla cruciata  
    1127103,Acanthocyrta haeckeli  
    1127108,Acanthometra fusca 
    NEXT
    1127108,1,0.60042  
    1127103,1,0.819671  
    1127100,2,0.50421,0.527007  
    10207,3,0.530422,0.624466 
    

    注意 - 这仍然会打印源内容的最后一行:

    "Acanthometra fusca ",1,"0.60042  "
    "Acanthocyrta haeckeli  ",1,"0.819671  "
    "Acanthocolla cruciata  ",2,0.50421,"0.527007  "
    

    (您的数据包含空格,因此Text::CSV 将其括在引号中)

    如果你想放弃它,那么你可以测试替换是否真的发生了:

    if ( $row->[0] =~ s/^($search)$/$replace{$1}/ ) {
        $csv->print( \*STDOUT, $row );
        print "\n";
    }
    

    (当然,如果您确定不会遇到CSV 正常支持的任何奇怪的东西,您当然可以继续使用split /,/)。

    【讨论】:

      【解决方案4】:

      我想提供一种非常不同的方法。

      假设您使用数据库比使用 Perl 的数据结构更舒服。您可以使用DBD::CSV 将您的 CSV 文件转换为某种关系数据库。它在引擎盖下使用 Text::CSV (@Sobrique 的帽子提示)。您需要从 CPAN 安装它,因为它没有捆绑在默认的 DBI 分发版中。

      use strict;
      use warnings;
      use Data::Printer; # for p
      use DBI;
      
      my $dbh = DBI->connect( "dbi:CSV:", undef, undef, { f_ext => '.csv' } );
      $dbh->{csv_tables}->{names}   = { col_names => [qw/id name/] };
      $dbh->{csv_tables}->{numbers} = { col_names => [qw/id int float/] };
      
      my $sth_select = $dbh->prepare(<<'SQL');
      SELECT names.name, numbers.int, numbers.float
      FROM names
      JOIN numbers ON names.id = numbers.id
      SQL
      
      # column types will be silently discarded
      $dbh->do('CREATE TABLE result ( name CHAR(255), int INTEGER, float INTEGER )');
      my $sth_insert = 
        $dbh->prepare('INSERT INTO result ( name, int, float ) VALUES ( ?, ?, ? ) ');
      
      $sth_select->execute;
      while (my @res = $sth_select->fetchrow_array ) {
        p @res;
        $sth_insert->execute(@res);
      }
      

      这样做是为两个表(您的 CSV 文件)设置列名,因为它们的第一行没有名称。我根据数据类型命名。然后它将创建一个名为result 的新表(CSV 文件)并通过一次写入一行来填充它。

      同时它将数据(用于调试目的)输出到STDERR到Data::Printer。

      [
          [0] "Acanthocolla cruciata",
          [1] 2,
          [2] 0.50421
      ]
      [
          [0] "Acanthocyrta haeckeli",
          [1] 1,
          [2] 0.819671
      ]
      [
          [0] "Acanthometra fusca",
          [1] 1,
          [2] 0.60042
      ]
      

      生成的文件如下所示:

      $ cat scratch/result.csv 
      name,int,float
      "Acanthocolla cruciata",2,0.50421
      "Acanthocyrta haeckeli",1,0.819671
      "Acanthometra fusca",1,0.60042
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-05-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-10-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多