【问题标题】:Perl find and replace multiple(huge) strings in one shotPerl 一次查找和替换多个(巨大的)字符串
【发布时间】:2014-03-06 16:46:37
【问题描述】:

基于映射文件,我需要搜索一个字符串,如果找到,则将替换字符串附加到行尾。 我正在逐行遍历映射文件,并使用下面的 perl 单行,附加字符串。

问题:

1.Huge find & replace Entries: 但问题是映射文件有大量的条目(约 7000 个条目),而 perl 单行器每个条目需要约 1 秒,这归结为到 ~ 1 小时完成整个更换。

2.不是简单的查找和替换:它不是简单的查找和替换。它是 - 如果找到字符串,则将替换字符串附加到 EOL。 如果没有有效的方法来处理这个,我什至会考虑替换而不是追加。

我的是在 Windows 7 64 位环境中,我使用的是活动 perl。不支持 *unix。

文件示例

Map.csv

findStr1,RplStr1

findStr2,RplStr2

findStr3,RplStr3

.....

findStr7000,RplStr7000

input.csv

col1,col2,col3,findStr1,....col-N

col1,col2,col3,findStr2,....col-N

col1,col2,col3,FIND-STR-NOT-EXIST,....col-N

output.csv(预期输出)

col1,col2,col3,findStr1,....col-N,**RplStr1**

col1,col2,col3,findStr1,....col-N,**RplStr2**

col1,col2,col3,FIND-STR-NOT-EXIST,....col-N

Perl 代码片段

单线

perl -pe '/findStr/ && s/$/RplStr/' file.csv


open( INFILE, $MarketMapFile ) or die "Error occured: $!";
    my @data = <INFILE>;


    my $cnt=1;  
    foreach $line (@data) {
        eval {          
            # Remove end of line character.
            $line =~ s/\n//g;
            my ( $eNodeBID, $MarketName ) = split( ',', $line );
            my $exeCmd = 'perl -i.bak -p -e "/'.$eNodeBID.'\(M\)/ && s/$/,'.$MarketName.'/;" '.$CSVFile;
            print "\n $cnt Repelacing $eNodeBID with $MarketName and cmd is $exeCmd";
            system($exeCmd);
            $cnt++;
        }
    }       
    close(INFILE);

【问题讨论】:

  • 花费这么长时间的部分原因是您正在为循环中的每一行创建一个新的 Perl 进程。你不应该那样做。
  • 您要匹配的字符串是否总是在 CSV 的第 4 列中?
  • @ThisSuitIsBlackNot 是。搜索字符串列位置始终固定
  • 您提到有大约 7000 个条目并且文件“巨大”;你能提供更多关于这意味着什么的信息吗?一个典型的条目是多长的字符?查找字符串有多长?替换字符串有多长?查找字符串真的只是一个字符串还是有任何正则表达式元字符,例如:“$.\”?所有这些因素都可能影响性能。
  • @benrifkah 不仅如此,OP 正在读取整个输入文件 7000 次,映射文件中的每个条目一次。

标签: perl replace bulk


【解决方案1】:

要一次性通过输入 CSV 执行此操作,最简单的方法是将映射存储在哈希中。 7000 个条目并不是特别多,但如果您担心将所有这些都存储在内存中,您可以使用 Tie::File::AsHash

#!/usr/bin/perl

use strict;
use warnings;

use Text::CSV;
use Tie::File::AsHash;

tie my %replace, 'Tie::File::AsHash', 'map.csv', split => ',' or die $!;

my $csv = Text::CSV->new({ binary => 1, auto_diag => 1, eol => $/ })
        or die Text::CSV->error_diag;

open my $in_fh, '<', 'input.csv' or die $!;
open my $out_fh, '>', 'output.csv' or die $!;

while (my $row = $csv->getline($in_fh)) {
    push @$row, $replace{$row->[3]};
    $csv->print($out_fh, $row);
}

untie %replace;
close $in_fh;
close $out_fh;

map.csv

foo,bar
apple,orange
pony,unicorn

input.csv

field1,field2,field3,pony,field5,field6
field1,field2,field3,banana,field5,field6
field1,field2,field3,apple,field5,field6

输出.csv

field1,field2,field3,pony,field5,field6,unicorn
field1,field2,field3,banana,field5,field6,
field1,field2,field3,apple,field5,field6,orange

我不建议通过仅将字段附加到匹配行来破坏您的 CSV 格式,因此如果未找到匹配项,我会添加一个空字段。

要使用常规哈希而不是 Tie::File::AsHash,只需将 tie 语句替换为

open my $map_fh, '<', 'map.csv' or die $!;

my %replace = map { chomp; split /,/ } <$map_fh>;

close $map_fh;

【讨论】:

  • 我需要在 310-120-483338(M) 上搜索部分搜索字符串,即 483338。我该怎么办?
  • 你的意思是483338在映射文件中,310-120-483338(M)在输入文件的第4个字段?
  • 是的...内存中的 btw 比 tie 快得多,感谢非匹配行上的空列。我之前没有考虑过!
  • 要仅匹配第 4 个字段的一部分,请执行以下操作:my ($search) = $row-&gt;[3] =~ /^\d+-\d+-(\d+)\(M\)$/; push @$row, $replace{$search};while 循环内,为您的输入使用适当的正则表达式。
  • @Siva 您可以使用quote_space 选项将这种行为更改为Text::CSV-&gt;newText::CSV-&gt;new({ binary =&gt; 1, auto_diag =&gt; 1, eol =&gt; $/, quote_space =&gt; 0 })Text::CSV 有许多其他选项可以让您微调引用、转义序列等,这就是为什么我总是使用它来解析 CSV 而不是正则表达式。
【解决方案2】:

这是未经测试的代码/伪 Perl,您需要对其进行润色(严格、警告等):

 # load the search and replace sreings into memeory
 open($mapfh, "<", mapfile);
 %maplines;
 while ( $mapline = <fh> ) {
   ($findstr, $replstr) = split(/,/, $mapline);
   %maplines{$findstr} = $replstr;
 }
 close $mapfh;

 open($ifh, "<", inputfile);
 while ($inputline = <$ifh>) {                 # read an input line
   @input = split(/,/, $inputline);           # split it into a list

   if (exists $maplines{$input[3]}) {        # does this line match
     chomp $input[-1];                       # remove the new line
     push @input, $maplines{$input[3]};      # add the replace str to the end
     last;                                   # done processing this line
   }
   print join(',', @input);  # or print or an output file 
 }

 close($ihf)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-07-01
    • 2022-01-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多