Как я могу заменить одну подстроку на другую в Perl? - PullRequest
2 голосов
/ 22 сентября 2010

У меня есть файл и список пар строк, которые я получаю из другого файла.Мне нужно заменить первую строку пары второй, и сделать это для каждой пары.Есть ли более эффективный / простой способ сделать это (используя Perl, grep, sed или другие), затем выполнить отдельную замену регулярного выражения для каждой пары значений?

Ответы [ 4 ]

6 голосов
/ 22 сентября 2010
#! /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.

Бит @{[...]} может выглядеть странно.Это взломать интерполяцию сгенерированного контента внутри кавычек и операторов, подобных кавычкам .Результат join попадает внутрь анонимного конструктора ссылки на массив [] и немедленно разыменовывается благодаря @{}.

Если все это кажется слишком вонючим, это то же самое, что

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

минус временная переменная.

Обратите внимание на использование quotemeta - спасибо Ивану! - который экранирует первую строку каждой пары, поэтому механизм регулярных выражений будет обрабатывать ихв виде буквенных строк.

Вывод:

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/// замены, но является quotemeta на стороне замены хорошей идеей? -

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 редактор для редактирования на месте :

# 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";
  }
}
2 голосов
/ 22 сентября 2010

Есть два способа, оба они требуют от вас составить чередование регулярных выражений по ключам таблицы:

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;
}
0 голосов
/ 23 сентября 2010

Если eval не является проблемой безопасности:

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

Это создает длинную команду sed, состоящую из нескольких команд замещения. Это может привести к превышению вашей максимальной длины командной строки. Предполагается, что файл пар слов будет состоять из двух слов, разделенных пробелами в каждой строке. Подстановки будут производиться только для целых слов (без подстановок).

Он может подавиться, если файл пар слов содержит символы, значимые для sed.

Вы можете сделать это таким образом, если ваш sed настаивает на -e:

eval $(awk 'BEGIN { printf "sed"} {printf "%s", " -e \047s/\\<" $1 "\\>/" $2 "/g\047"} END{print " substtemplate"}' substwords)
0 голосов
/ 22 сентября 2010

Построить хэш пар. Затем разбейте целевую строку на жетоны слова и проверьте каждый жетон на соответствие ключам в хэше. Если он присутствует, замените его значением этого ключа.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...