如何在Perl中用一个子字符串替换另一个子字符串?

时间:2010-09-22 16:33:18

标签: regex perl sed grep

我有一个文件和一个字符串对列表,我从另一个文件中获取。我需要用第二个字符串替换第一个字符串,并为每个字符串执行此操作。 是否有更有效/简单的方法(使用Perl,grep,sed或其他),然后为每对值运行单独的正则表达式替换?

4 个答案:

答案 0 :(得分:6)

#! /usr/bin/perl

use warnings;
use strict;

my %replace = (
  "foo" => "baz",
  "bar" => "quux",
);

my $to_replace = qr/@{["(" .
                       join("|" => map quotemeta($_), keys %replace) .
                       ")"]}/;

while (<DATA>) {
  s/$to_replace/$replace{$1}/g;
  print;
}

__DATA__
The food is under the bar in the barn.

@{[...]}位可能看起来很奇怪。在quote and quote-like operators内插入生成的内容是一种破解。 join的结果位于匿名数组引用构造函数[]内,并且由于@{}而立即取消引用。

如果所有这些看起来太令人兴奋,那就和

一样
my $search = join "|" => map quotemeta($_), keys %replace;
my $to_replace = qr/($search)/;

减去临时变量。

请注意使用quotemeta - 感谢Ivan!-which转义每对的第一个字符串,以便正则表达式引擎将它们视为文字字符串。

输出:

The bazd is under the quux in the quuxn.

元编程 - 也就是说,编写一个编写另一个程序的程序 - 也很不错。一开始看起来很熟悉:

#! /usr/bin/perl

use warnings;
use strict;

use File::Compare;

die "Usage: $0 path ..\n" unless @ARGV >= 1;

# stub
my @pairs = (
  ["foo"     => "baz"],
  ["bar"     => "quux"],
  ['foo$bar' => 'potrzebie\\'],
);

现在我们生成执行所有s///替换的程序 - 但is quotemeta on the replacement side a good idea? -

my $code =
  "sub { while (<>) { " .
  join(" " => map "s/" . quotemeta($_->[0]) .
                  "/"  . quotemeta($_->[1]) .
                  "/g;",
              @pairs) .
  "print; } }";
#print $code, "\n";

并使用eval编译:

my $replace = eval $code
  or die "$0: eval: $@\n";

要进行替换,我们使用Perl的ready-made in-place editing

# set up in-place editing
$^I = ".bak";
my @save_argv = @ARGV;

$replace->();

以下是恢复File::Compare模块认为不必要的备份的额外准确性:

# in-place editing is conservative: it creates backups
# regardless of whether it modifies the file
foreach my $new (@save_argv) {
  my $old = $new . $^I;
  if (compare($new, $old) == 0) {
    rename $old => $new
      or warn "$0: rename $old => $new: $!\n";
  }
}

答案 1 :(得分:2)

有两种方法,它们都要求你在表格的键上编译正则表达式替换:

my %table = qw<The A the a quick slow lazy dynamic brown pink . !>;
my $alt 
    = join( '|'
          , map  { quotemeta } keys %table 
            sort { ( length $b <=> length $a ) || $a cmp $b } 
          )
    ;
my $keyword_regex = qr/($alt)/;

然后你可以在替换中使用这个正则表达式:

my $text 
    = <<'END_TEXT';
The quick brown fox jumped over the lazy dog.  The quick brown fox jumped over the lazy dog. 
The quick brown fox jumped over the lazy dog.  The quick brown fox jumped over the lazy dog.  
END_TEXT

$text =~ s/$keyword_regex/$table{ $1 }/ge; # <- 'e' means execute code

或者您可以循环执行:

use English qw<@LAST_MATCH_START @LAST_MATCH_END>;
while ( $text =~ /$keyword_regex/g ) { 
    my $key = $1;
    my $rep = $table{ $key };
    # use the 4-arg form
    substr( $text, $LAST_MATCH_START[1]
          , $LAST_MATCH_END[1] - $LAST_MATCH_START[1], $rep 
          );
    # reset the position to start + new actual
    pos( $text ) = $LAST_MATCH_START[1] + length $rep;
}

答案 2 :(得分:0)

构建对的哈希值。然后将目标字符串拆分为单词标记,并根据散列中的键检查每个标记。如果它存在,请将其替换为该键的值。

答案 3 :(得分:-1)

如果eval不是安全问题:

eval $(awk 'BEGIN { printf "sed \047"} {printf "%s", "s/\\<" $1 "\\>/" $2 "/g;"} END{print "\047 substtemplate"}' substwords )

这构造了一个由多个替换命令组成的长sed命令。它可能会超出您的最大命令行长度。它希望单词对文件由每行上用空格分隔的两个单词组成。将仅对整个单词进行替换(不进行clbuttic替换)。

如果单词对文件包含对sed有重要意义的字符,则可能会阻塞。

如果sed坚持-e

,您可以这样做
eval $(awk 'BEGIN { printf "sed"} {printf "%s", " -e \047s/\\<" $1 "\\>/" $2 "/g\047"} END{print " substtemplate"}' substwords)
相关问题