繁体   English   中英

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

[英]How can I use a variable in the replacement side of the Perl substitution operator?

我想做以下事情:

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

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

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

$replace = 'foo \1 bar';

不知何故,我遗漏了一些关于逃避的东西。

在替换方面,您必须使用 $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

(尽管池上指出,单个 /e 或双 e 的第一个 /e 并不是真正的eval() ;相反,它告诉编译器替换是要编译的代码,而不是字符串。尽管如此, eval(eval(...))仍然展示了为什么你需要做你需要做的事情来让 /ee 按需要工作。)

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技术的反驳:

正如我在回答中所说,我避免 evals 是有原因的。

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

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

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

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

我会建议这样的:

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

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

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

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

print $res

这让我得到了“1234”。

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

您正在尝试使用s///ee形式对右侧字符串执行双eval是可能的。 有关更多示例,请参阅perlop quote like operators

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

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

  • 当我的替换字符串包含几个连续的反向引用时,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;';

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

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

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

即只保留中间部分并替换开始和结束。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM