Jak użyć zmiennej po stronie zastępczej operatora zastępczego Perla?

Chciałbym wykonać następujące czynności:

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

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

Spodziewałbym się, że $var będzie zawierać "Foo middle bar", ale to nie działa. Też nie:

$replace='foo \1 bar';

Jakoś czegoś mi brakuje odnośnie ucieczki.


Naprawiłem brakujące " s "

Author: Jonathan Leffler, 2008-12-25

8 answers

Po stronie zastępczej, musisz użyć $1, nie \1.

I możesz zrobić tylko to, co chcesz, zamieniając ewalowalne wyrażenie, które daje pożądany wynik i każąc s / / / ewalować je modyfikatorem / ee w ten sposób:

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

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

print "var: $var\n";

Aby zobaczyć, dlaczego "" i podwójne / e są potrzebne, zobacz efekt podwójnego evalu tutaj:

$ perl
$foo = "middle";
$replace='"foo $foo bar"';
print eval('$replace'), "\n";
print eval(eval('$replace')), "\n";
__END__
"foo $foo bar"
foo middle bar
 69
Author: ysth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-25 10:38:46

Deparse mówi nam, że to jest to, co jest wykonywane:

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

Jednakże

 /$find/foo \1 bar/

Jest interpretowane jako:

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

Niestety wygląda na to, że nie ma łatwego sposobu, aby to zrobić.

Można to zrobić za pomocą sznurka eval, ale to niebezpieczne.

Najbardziej rozsądne rozwiązanie, które działa dla mnie było to:

$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; 

Obalenie techniki ee:

Jak powiedziałem w mojej odpowiedzi, unikam oceny z jakiegoś powodu.

$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";

Ten kod robi dokładnie to, co myślisz.

Jeśli twój łańcuch zastępczy znajduje się w aplikacji internetowej, po prostu otworzyłeś drzwi do dowolnego wykonania kodu.

Dobra Robota.

Poza tym, to nie będzie działać z włączonymi skażeniami z tego właśnie powodu.

$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.

Jednak bardziej ostrożna technika jest zdrowa, bezpieczna, bezpieczna, i nie zawodzi. (Zapewnij sobie, że emitowany ciąg jest nadal skażony, więc nie stracisz żadnego zabezpieczenia. )

 12
Author: Kent Fredric,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-26 04:39:30
# perl -de 0
$match="hi(.*)"
$sub='$1'
$res="hi1234"
$res =~ s/$match/$sub/gee
p $res
  1234
Bądź ostrożny. Powoduje to wystąpienie dwóch warstw eval, po jednej dla każdej e na końcu wyrażenia regularnego:
  1. $sub -- > $1
  2. $1 -- > Wartość końcowa, w przykładzie, 1234
 6
Author: eruciform,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-17 01:17:06

Jak zasugerowali inni, możesz użyć następującego:

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;

Powyższe jest skrótem od:

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

Wolę drugą od pierwszej, ponieważ nie ukrywa to faktu, że eval(EXPR) jest używany. Jednak oba powyższe błędy milczenia, więc lepiej byłoby:

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;

Ale jak widzisz, wszystkie powyższe pozwalają na wykonanie dowolnego kodu Perla. O wiele bezpieczniej byłoby:

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);
 4
Author: ikegami,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-24 18:32:19

Proponuję coś w stylu:

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

Jest dość czytelny i wydaje się być bezpieczny. Jeśli potrzebna jest wielokrotna wymiana, jest to łatwe:

while ($text =~ m{(.*)$find(.*)}){
     $text = $1 . $replace . $2;
}
 1
Author: Pavel Coodan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-20 02:16:47

Zobacz THIS previous so post on using a variable on the replacement side of s///in Perl. Spójrz zarówno na zaakceptowaną ODPOWIEDŹ i odrzuconą odpowiedź .

To, co próbujesz zrobić, jest możliwe dzięki formie s///ee, która wykonuje Podwójny eval na prawym łańcuchu. Zobacz cytat perlop jak operatory aby uzyskać więcej przykładów.

Ostrzegam, że istnieją zabezpieczenia impilcations eval i to nie będzie działać w trybie skażenia.

 1
Author: dawg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:17:58
#!/usr/bin/perl

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

print $res
To dało mi "1234".
 0
Author: rmk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-17 00:01:19

Nie jestem pewien, co chcesz osiągnąć. Ale może możesz użyć tego:

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

Tzn. po prostu zostaw środek w spokoju i wymień początek i koniec.

 -5
Author: PEZ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-25 10:47:26