56

我想做以下事情:

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

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

我希望 $var 包含“foo middle bar”,但它不起作用。也没有:

$replace = 'foo \1 bar';

不知何故,我错过了一些关于逃跑的东西。

4

9 回答 9

88

在替换方面,您必须使用 $1,而不是 \1。

你只能做你想做的事,方法是替换一个给出你想要的结果的可评估表达式,并告诉 s/// 用 /ee 修饰符来评估它,如下所示:

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

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

print "var: $var\n";

要了解为什么需要 "" 和双 /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

eval()(尽管正如ikegamieval(eval(...))所指出的,单个 /e 或双 e 的第一个 /e 并不是真正的做你需要做的事情来让 /ee 按需要工作。)

于 2008-12-25T10:38:46.673 回答
13

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 应用程序中,那么您只是打开了任意代码执行的大门。

做得好。

此外,出于这个原因,它不适用于打开污点。

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

然而,更谨慎的技术是理智的、安全的、可靠的,并且不会失败。(请放心,它发出的字符串仍然受到污染,因此您不会失去任何安全性。)

于 2008-12-25T08:55:30.423 回答
8

正如其他人所建议的那样,您可以使用以下内容:

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);
于 2015-04-15T22:02:37.743 回答
7
# 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
于 2010-07-17T01:05:19.900 回答
1

我会建议类似:

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

它的可读性很强,而且似乎很安全。如果需要多次更换,很容易:

while ($text =~ m{(.*)$find(.*)}){
     $text = $1 . $replace . $2;
}
于 2010-01-19T16:00:44.113 回答
1
#!/usr/bin/perl

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

print $res

这让我得到了'1234'。

于 2010-07-17T00:01:19.610 回答
1

请参阅s///一篇关于在 Perl的替换端使用变量的 SO 帖子。看看接受的答案反驳的答案。

您尝试使用在右手字符串上s///ee执行双精度的形式来实现。eval有关更多示例,请参见perlop quote like operator

请注意,存在安全隐患,eval这在污点模式下不起作用。

于 2010-07-17T01:24:38.433 回答
1

我没有设法使最受欢迎的答案起作用。

  • 当我的替换字符串包含几个连续的反向引用时,ee 方法会抱怨。
  • 肯特弗雷德里克的回答只替换了第一场比赛,我需要我的搜索和替换是全局的。我没有想出办法让它替换所有不会导致其他问题的匹配项。例如,我尝试递归地运行该方法,直到它不再导致字符串更改,但如果替换字符串包含搜索字符串,则会导致无限循环,而常规的全局替换不会这样做。

我尝试使用普通的旧 eval 提出自己的解决方案:

eval '$var =~ s/' . $find . '/' . $replace . '/gsu;';

当然,这允许代码注入。但据我所知,逃避正则表达式查询和注入代码的唯一方法是在 $find 中插入两个正斜杠或在 $replace 中插入一个斜杠,后跟一个分号,之后您可以添加添加代码。例如,如果我这样设置变量:

my $find = 'foo';
my $replace = 'bar/; print "You\'ve just been hacked!\n"; #';

评估的代码是这样的:

$var =~ s/foo/bar/; print "You've just been hacked!\n"; #/gsu;';

所以我要做的是确保字符串不包含任何未转义的正斜杠。

首先,我将字符串复制到虚拟字符串中。

my $findTest = $find;
my $replaceTest = $replace;

然后,我从虚拟字符串中删除所有转义的反斜杠(反斜杠对)。这使我可以找到未转义的正斜杠,而不会陷入考虑如果正斜杠前面有转义反斜杠的情况下已转义的陷阱。例如:\/包含转义的正斜杠,但\\/包含文字正斜杠,因为反斜杠已转义。

$findTest =~ s/\\\\//gmu;
$replaceTest =~ s/\\\\//gmu;

现在,如果任何前面没有反斜杠的正斜杠保留在字符串中,我会抛出一个致命错误,因为这将允许用户插入任意代码。

if ($findTest =~ /(?<!\\)\// || $replaceTest =~ /(?<!\\)\//)
{
  print "String must not contain unescaped slashes.\n";
  exit 1;
}

然后我评估。

eval '$var =~ s/' . $find . '/' . $replace . '/gsu;';

I'm not an expert at preventing code injection, but I'm the only one using my script, so I'm content using this solution without fully knowing if it's vulnerable. But as far as I know, it may be, so if anyone knows if there is or isn't any way to inject code into this, please provide your insight in a comment.

于 2020-01-16T04:49:53.457 回答
-6

我不确定您要达到的目标是什么。但也许你可以使用这个:

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

即只留下中间并替换开始和结束。

于 2008-12-25T10:47:26.703 回答