如何在Perl替换运算符的替换端使用变量?

时间:2008-12-25 08:47:42

标签: regex perl substitution

我想做以下事情:

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

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

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

$replace='foo \1 bar';

不知何故,我遗漏了有关逃跑的事情。


我修复了缺失的''

9 个答案:

答案 0 :(得分:74)

在替换方面,您必须使用$ 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,请在此处查看双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(...))仍然证明了为什么你需要做你需要做的事情才能让e ee按照需要工作。)

答案 1 :(得分:12)

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

干得好。

此外,由于这个原因,不会使用taints。

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

然而,更谨慎的技术是理智,安全,安全,不会失败。 (请放心,它发出的字符串仍然受到污染,所以你不会失去任何安全性。)

答案 2 :(得分:6)

# 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

答案 3 :(得分:5)

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

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

答案 4 :(得分:1)

我会建议像:

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

它非常易读,似乎很安全。如果需要多次更换,很容易:

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

答案 5 :(得分:1)

请参阅THIS上一篇关于在Perl中s///的替换面上使用变量的SO帖子。查看accepted answerrebuttal答案。

使用s///ee表单在右侧字符串上执行双eval表示您可以尝试执行的操作。有关更多示例,请参阅perlop quote like operators

警告eval存在安全隐患,这在污点模式下无效。

答案 6 :(得分:0)

#!/usr/bin/perl

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

print $res

这让我得到了'1234'。

答案 7 :(得分:0)

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

  • 当我的替换字符串包含多个连续的反向引用时,ee方法抱怨。
  • 肯特·弗雷德里克(Kent Fredric)的答案仅替换了第一局,我需要进行搜索和替换以使其具有全局性。我没有找到一种方法来替代所有未引起其他问题的比赛。例如,我尝试递归运行该方法,直到它不再导致字符串更改,但是如果替换字符串包含搜索字符串,则这将导致无限循环,而常规的全局替换则不会这样做。

我尝试使用简单的旧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;';

我不是防止代码注入的专家,但是我是唯一使用脚本的人,因此我很满意使用此解决方案,而完全不知道它是否容易受到攻击。但是据我所知,可能是这样,所以如果有人知道是否有办法向其中注入代码,请在注释中提供您的见识。

答案 8 :(得分:-5)

我不确定你想要实现的是什么。但也许你可以使用它:

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

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