当在文件中找到'key'时,替换'key'的'value'

时间:2012-08-29 01:15:08

标签: regex perl replace

我在将一个文件中的某些字符串的值替换为另一个文件中找到的相同字符串时遇到问题。

test1.txt

porsche  430+turbo blue
ferrari  520+supercharged red
buggati  1001+supersport black
fiat     turbo white

此test1.txt中的某些字符串具有自己的值,如test2.txt

中所示
test2.txt
turbo=30
supercharged=50 
supersport=100

每当找到相应的字符串时,我想替换test1.txt中test2.txt的值。

output.txt
    porsche  460 blue
    ferrari  570 red
    buggati  1101 black
    fiat     30 white

在test1.txt中搜索来自test2.txt的Turbo,然后替换并汇总该值。

我做了大量的谷歌搜索,我没有找到继续这个的方法。 任何人都可以帮我解决这个问题 提前谢谢

4 个答案:

答案 0 :(得分:2)

amon的解决方案非常优雅。 +1(我还没有足够的声望点将此评论留在他的答案上)

如果您发现不需要运营商调度表的可扩展性,那么这是一个非常简单的替代方案:

(编辑2012/08/29:合并amon的处理+, - ,*,/,^运算符的调度表)

use strict;
use warnings;
use English qw( -no_match_vars );

setup_for_testing( );

open my $source_file, '<',  'text1.txt' || die "Couldn't open source file: $OS_ERROR";
open my $key_file,    '<',  'text2.txt' || die "Couldn't open key file: $OS_ERROR";

# Clear the output file
open my $output_file, '>',  'output.txt' || die "Couldn't open output file: $OS_ERROR";

# Now open output file for append
open $output_file,    '>>', 'output.txt' || die "Couldn't open output file: $OS_ERROR";

# Create translation map from text2.txt
my %translation_map = translation_map( $key_file );

# Process text1.txt and print to output.txt
while ( my $source_line = <$source_file> ) {
    my $transformed_line = transform( $source_line, \%translation_map );
    print $output_file $transformed_line ||
        die "Couldn't print to output file: $OS_ERROR";;
}

# Tidy up
close $source_file || die "Couldn't close source file: $OS_ERROR";
close $key_file    || die "Couldn't close key file: $OS_ERROR";
close $output_file || die "Couldn't close output file: $OS_ERROR";

###################
sub setup_for_testing {
   open my $textfile1, '>',  'text1.txt' || die "Couldn't open source file: $OS_ERROR";
   open my $textfile2, '>',  'text2.txt' || die "Couldn't open key file: $OS_ERROR";

   my $source_text =<<'END_TEXT';
porsche  430-turbo blue
ferrari  520*supercharged red
buggati  1001+supersport black
fiat     turbo white
END_TEXT

   my $key_file_text =<<'END_TEXT';
turbo=30
supercharged=50
supersport=100
END_TEXT

   print $textfile1 $source_text   || die "Couldn't print to text1.txt: $OS_ERROR";
   print $textfile2 $key_file_text || die "Couldn't print to text2.txt: $OS_ERROR";

   close $textfile1 || die "Couldn't close source file: $OS_ERROR";
   close $textfile2 || die "Couldn't close key file: $OS_ERROR";

   return; # intentional void return
}

sub translation_map {
    my $key_file = shift;

    my %translation_map;
    while ( my $key_mapping = <$key_file> ) {
        chomp $key_mapping;

        # The regex /x option allows whitespace in the regular expression for readability
        my ( $key, $value ) = split / \s* = \s* /x, $key_mapping;
        $translation_map{ $key } = $value;
    }

    return %translation_map;
}

sub transform {
    my $source_line = shift @_;
    my %value_for   = %{ shift @_ };

    my $transformed_line = $source_line;

    foreach my $key ( keys %value_for ) {
        # The regex /e option causes the rights side of a substitution to be evaluated as
        # a Perl expression.
        my $value = $value_for{ $key };
        my ( $before_expression, $lvalue, $operator, $rvalue_key, $after_expression ) =
            ( $transformed_line =~ m/ \A
                                      ( .*? )
                                      ( \d+ ) ([-+*\/^]?) ( $key )
                                      ( .* )
                                      \Z
                                    /x );

        if ( $operator  ) {
            my $rvalue = $value_for{ $rvalue_key };

            # Using the dispatch table from amon's answer
            my $value_of_expression = {
              '+' => sub {$_[0] +  $_[1]},
              '-' => sub {$_[0] -  $_[1]},
              '*' => sub {$_[0] *  $_[1]},
              '/' => sub {$_[0] /  $_[1]},
              '^' => sub {$_[0] ** $_[1]},
            }->{$operator}->($lvalue, $rvalue);

            $transformed_line =
                $before_expression . $value_of_expression . $after_expression . "\n";
        } else {
            $transformed_line =~ s/$key/$value/;
        }
    }

    return $transformed_line;
}

此脚本根据您的问题规范创建测试文件text1.txt和text2.txt,然后执行转换并输出到output.txt:

> ls
stackoverflow-12169648_replace_value_of_key.pl

> perl stackoverflow-12169648_replace_value_of_key.pl 

> ls
output.txt                  text1.txt
stackoverflow-12169648_replace_value_of_key.pl  text2.txt

> more text1.txt 
porsche  430+turbo blue
ferrari  520+supercharged red
buggati  1001+supersport black
fiat     turbo white

> more text2.txt 
turbo=30
supercharged=50
supersport=100

> more output.txt 
porsche  460 blue
ferrari  570 red
buggati  1101 black
fiat     30 white

希望这很有用。

.david

答案 1 :(得分:1)

我将假设第一个文件(称为 A )有三个列由一个或多个空格字符分隔。第二列可以包括具有十进制数字的aritmetic表达式和由基本(中缀)运算符分隔的变量。变量值在另一个文件中修复,此后称为 B

准备变量很简单:

my %variables = map {chomp; split /=/, $_, 2} do {
  open my $file, "<", $filename_B or die;
  <$file>;
};

解析其他文件更加困难。假设它被打开到文件句柄$fileA中,我们遍历这些行并将每行分成三个字段:

while (defined(my $line = <$fileA>)) {
   chomp $line;
   my ($model, $expression, $color) = split /\s+/, $line, 3;
   my $value = parseExpression($expression);
   print "\t$model $value $color\n"; # use printf to prettyprint if needed
}

我们然后打印出表达式值以及其他数据,假设您要打印到STDOUT。

我们的子parseExpression将在运算符处拆分表达式字符串。变量名将被替换。然后严格地以关联方式执行操作。虽然这使解析变得更容易,但这并不是很自然:3*4+1计算结果为15。我们使用递归,因为我更喜欢迭代,以便能够解决多个操作:

sub parseExpression {
  my ($string) = @_;
  my ($part, $operator, $rest) = ($string =~ /(\w+)([-+*\/^]?)(.*$)/g);
  if (not $operator) {
    # $part is the whole expression
    my $value = exists $variables{$part} ? $variables{$part} : $part;
    die if $value =~ /[a-z]/i; # The variable name was not substituted
    return $value;
  } else {
    my $rval = parseExpression($rest);
    my $lval = parseExpression($part); # you don't need this
                                       # if there are no variables on the left
    my $value = {
      '+' => sub {$_[0] +  $_[1]},
      '-' => sub {$_[0] -  $_[1]},
      '*' => sub {$_[0] *  $_[1]},
      '/' => sub {$_[0] /  $_[1]},
      '^' => sub {$_[0] ** $_[1]},
    }->{$operator}->($lval, $rval);
    return $value;
  }
}

我们使用一个可爱的小调度表来为每个操作员执行适当的计算。您始终可以增强运算符正则表达式和表以支持其他运算符。

请注意,当前实现允许将数字作为变量名称。不是你想要的东西,但它让生活更轻松。

未定义的值随机出现可能存在一些有趣的问题,但是这段代码应该为您提供指向正确方向的指针。 (如果只允许第二列中的一个操作,则可以删除递归)

答案 2 :(得分:1)

这可以通过分两步执行更换来完成。首先查找从%values文件派生的test2.txt哈希中存在的所有关键字。然后查找由arithemtic运算符连接的多个十进制数,并评估表达式以形成替换。

使用join动态构建查找哈希键的正则表达式,以使用正则表达式交替运算符|进行连接。

第二个正则表达式

expression ::= digits, operator, digits, { operator, digits }

并允许术语

之间的空格
use strict;
use warnings;

my %values = do {
  open my $fh, '<', 'test2.txt' or die $!;
  local $/;
  <$fh> =~ /\w+/g;
};

my $regex = join '|', keys %values;

open my $fh, '<', 'test1.txt' or die $!;

while (<$fh>) {
  s/\b($regex)\b/$values{$1}/g;
  s|([0-9]+(\s*[-+*/]\s*[0-9]+)+)|$1|eeg;
  print;
}

<强>输出

porsche  460 blue
ferrari  570 red
buggati  1101 black
fiat     30 white

答案 3 :(得分:0)

我根本不用perl编码,但我想我可能会提供一些整体提示......

首先,为了保持简单,您必须在某种程度上提交有限的模式格式,否则如评论中所建议的那样,这将成为语言解析器。所以你可能不得不说test1.txt中的一行可能包含或不包含[numbers][operator][characters]模式。

所以我可能建议将test2.txt解析为关联数组(hash),这样就可以了:

{
    "turbo"        =>  30,
    "supercharged" =>  50,
    "supersport"   => 100,
}

然后对于test1.txt中的每一行,您可以对如下模式执行正则表达式匹配:
\b((\d+)([+])(\w+))\b

从这种模式中,您可以检查+运算符和密钥。如果你得到它们,你可以在哈希中查找密钥,并在parsed number, operator, hash[key] => value上执行评估

如果您只想处理+,那么您只需将数字转换为int并添加即可。否则,为了支持多种运算符模式,您必须明确地处理它们,或者安全地评估该字符串。

您可以在第一个主要捕获组

上替换正则表达式