Как использовать переменную на стороне замены оператора подстановки Perl? - PullRequest
44 голосов
/ 25 декабря 2008

Я бы хотел сделать следующее:

$find="start (.*) end";
$replace="foo \1 bar";

$var = "start middle end";
$var =~ s/$find/$replace/;

Я ожидал бы, что $ var будет содержать "foo middle bar", но он не работает. Ни один не делает:

$replace='foo \1 bar';

Почему-то я что-то упускаю из-за побега.


Я исправил пропавшие 's'

Ответы [ 8 ]

74 голосов
/ 25 декабря 2008

На стороне замены вы должны использовать $ 1, а не \ 1.

И вы можете делать только то, что вы хотите, сделав замену evalable выражением, которое дает желаемый результат, и сообщив s ///, чтобы он оценил его с помощью модификатора / ee, например:

$find="start (.*) end";
$replace='"foo $1 bar"';

$var = "start middle end";
$var =~ s/$find/$replace/ee;

print "var: $var\n";

Чтобы понять, зачем нужны "" и double / e, посмотрите эффект двойного eval здесь:

$ perl
$foo = "middle";
$replace='"foo $foo bar"';
print eval('$replace'), "\n";
print eval(eval('$replace')), "\n";
__END__
"foo $foo bar"
foo middle bar

(Хотя, как отмечает ikegami, одиночный / e или первый / e двойного e на самом деле не eval(); скорее, он говорит компилятору, что подстановка - это код для компиляции, а не строка. , eval(eval(...)) по-прежнему демонстрирует, почему вам нужно делать то, что вам нужно, чтобы / ee работал как нужно.)

12 голосов
/ 25 декабря 2008

Депарс говорит нам, что это то, что выполняется:

$find = 'start (.*) end';
$replace = "foo \cA bar";
$var = 'start middle end';
$var =~ s/$find/$replace/;

Однако

 /$find/foo \1 bar/

интерпретируется как:

$var =~ s/$find/foo $1 bar/;

К сожалению, похоже, что сделать это нелегко.

Вы можете сделать это с помощью строки eval, но это опасно.

Самым здравым решением, которое мне подходит, было следующее:

$find = "start (.*) end"; 
$replace = 'foo \1 bar';

$var = "start middle end"; 

sub repl { 
    my $find = shift; 
    my $replace = shift; 
    my $var = shift;

    # Capture first 
    my @items = ( $var =~ $find ); 
    $var =~ s/$find/$replace/; 
    for( reverse 0 .. $#items ){ 
        my $n = $_ + 1; 
        #  Many More Rules can go here, ie: \g matchers  and \{ } 
        $var =~ s/\\$n/${items[$_]}/g ;
        $var =~ s/\$$n/${items[$_]}/g ;
    }
    return $var; 
}

print repl $find, $replace, $var; 

Опровержение против техники ee:

Как я сказал в своем ответе, я избегаю зла по какой-то причине.

$find="start (.*) end";
$replace='do{ print "I am a dirty little hacker" while 1; "foo $1 bar" }';

$var = "start middle end";
$var =~ s/$find/$replace/ee;

print "var: $var\n";

этот код делает именно то, что вы думаете.

Если ваша строка подстановки находится в веб-приложении, вы просто открыли дверь для выполнения произвольного кода.

Хорошая работа.

Кроме того, он НЕ БУДЕТ работать с включенными портами по этой самой причине.

$find="start (.*) end";
$replace='"' . $ARGV[0] . '"';

$var = "start middle end";
$var =~ s/$find/$replace/ee;

print "var: $var\n"


$ perl /tmp/re.pl  'foo $1 bar'
var: foo middle bar
$ perl -T /tmp/re.pl 'foo $1 bar' 
Insecure dependency in eval while running with -T switch at /tmp/re.pl line 10.

Тем не менее, более осторожная техника - вменяемая, безопасная, надежная, и не ошибаются. (Будьте уверены, что испускаемая строка все еще испорчена, поэтому вы не потеряете безопасность.)

6 голосов
/ 17 июля 2010
# perl -de 0
$match="hi(.*)"
$sub='$1'
$res="hi1234"
$res =~ s/$match/$sub/gee
p $res
  1234

Будьте осторожны, хотя. Это приводит к появлению двух слоев eval, по одному для каждого e в конце регулярного выражения:

  1. $ sub -> $ 1
  2. $ 1 -> конечное значение, например, 1234
5 голосов
/ 16 апреля 2015

Как и предполагали другие, вы можете использовать следующее:

my $find = 'start (.*) end';
my $replace = 'foo $1 bar';   # 'foo \1 bar' is an error.
my $var = "start middle end";
$var =~ s/$find/$replace/ee;

Выше приведено краткое обозначение:

my $find = 'start (.*) end';
my $replace = 'foo $1 bar';
my $var = "start middle end";
$var =~ s/$find/ eval($replace) /e;

Я предпочитаю второе первому, поскольку оно не скрывает тот факт, что используется eval(EXPR). Тем не менее, оба из вышеупомянутых ошибок молчания, поэтому следующее будет лучше:

my $find = 'start (.*) end';
my $replace = 'foo $1 bar';
my $var = "start middle end";
$var =~ s/$find/ my $r = eval($replace); die $@ if $@; $r /e;

Но, как вы можете видеть, все вышеперечисленное допускает выполнение произвольного кода Perl. Следующее будет гораздо безопаснее:

use String::Substitution qw( sub_modify );

my $find = 'start (.*) end';
my $replace = 'foo $1 bar';
my $var = "start middle end";
sub_modify($var, $find, $replace);
1 голос
/ 17 июля 2010

См. ЭТО предыдущий пост SO об использовании переменной на стороне замены s/// в Perl. Посмотрите как принятый ответ , так и опровержение ответа.

То, что вы пытаетесь сделать, возможно с формой s///ee, которая выполняет двойное eval в правой строке. Смотрите perlop quote как операторы для большего количества примеров.

Имейте в виду, что eval имеет последствия для безопасности, и это не сработает в режиме taint.

1 голос
/ 19 января 2010

Я бы предложил что-то вроде:

$text =~ m{(.*)$find(.*)};
$text = $1 . $replace . $2;

Это вполне читабельно и кажется безопасным. Если требуется многократная замена, это легко:

while ($text =~ m{(.*)$find(.*)}){
     $text = $1 . $replace . $2;
}
0 голосов
/ 17 июля 2010
#!/usr/bin/perl

$sub = "\\1";
$str = "hi1234";
$res = $str;
$match = "hi(.*)";
$res =~ s/$match/$1/g;

print $res

Это принесло мне «1234».

0 голосов
/ 25 декабря 2008

Я не уверен, чего вы пытаетесь достичь. Но, возможно, вы можете использовать это:

$var =~ s/^start/foo/;
$var =~ s/end$/bar/;

т.е. просто оставьте середину в покое и замените начало и конец.

...