web-dev-qa-db-ja.com

Perl置換演算子の置換側で変数を使用する方法は?

私は次のことをしたいと思います:

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

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

$ varには「foo middle bar」が含まれていると予想されますが、機能しません。どちらもしません:

$replace='foo \1 bar';

どういうわけか、私は逃げることに関する何かを見逃しています。


不足している「s」を修正しました

44
Manu

置換側では、\ 1ではなく$ 1を使用する必要があります。

そして、あなたが望む結果を与える評価可能な式を置き換えて、s ///に/ ee修飾子を付けて評価するように伝えることによって、あなたが望むことだけをすることができます:

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

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

print "var: $var\n";
_

""とdouble/eが必要な理由を確認するには、ここでdouble 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を希望どおりに動作させるために必要なことを行う必要がある理由を示しています。)

75
ysth

Deparseは、これが実行されていることを示しています。

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

このコードは、あなたが思っているとおりに動作します。

置換文字列がWebアプリケーションにある場合、任意のコード実行の扉を開いただけです。

よくやった。

また、それはWO N'Tまさにこの理由で汚染をオンにして動作します。

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

ただし、より慎重な手法は、健全で安全、安全であり、andは失敗に失敗しません。 (出力される文字列はまだ汚染されているので、セキュリティを失うことはありません。)

12
Kent Fredric
# Perl -de 0
$match="hi(.*)"
$sub='$1'
$res="hi1234"
$res =~ s/$match/$sub/gee
p $res
  1234

ただし、注意してください。これにより、正規表現の末尾にあるevalごとに1つずつ、2つのeの層が発生します。

  1. $ sub-> $ 1
  2. $ 1->最終値、例では1234
6
eruciform

他の人が提案したように、次を使用できます。

_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) が使用されているという事実を隠していないので、最初よりも2番目の方が好きです。ただし、上記の両方の沈黙エラーなので、次の方が適切です。

_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);
_
5
ikegami

[〜#〜] this [〜#〜] previous SO Perlのs///の置換側で変数を使用する投稿。両方を見てください。 受け入れられた答え および 反証 答え。

あなたがしようとしていることは、右側の文字列で二重のevalを実行するs///eeフォームで可能です。他の例については、 演算子のような引用符 を参照してください。

evalにはセキュリティ上の問題があり、これは汚染モードでは機能しないことに注意してください。

1
dawg

私は次のようなものを提案します:

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

これは非常に読みやすく、安全なようです。複数の交換が必要な場合、簡単です。

while ($text =~ m{(.*)$find(.*)}){
     $text = $1 . $replace . $2;
}
1
Pavel Coodan
#!/usr/bin/Perl

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

print $res

これにより、「1234」が取得されました。

0
rmk