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

Posted

技术标签:

【中文标题】如何在 Perl 替换运算符的替换端使用变量?【英文标题】:How can I use a variable in the replacement side of the Perl substitution operator? 【发布时间】:2010-09-28 09:41:27 【问题描述】:

我想做以下事情:

$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,而不是 \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

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

【讨论】:

双重评估的好例子! 这是对双重评估的一个非常好的解释:) 当然要注意 eval 对于 Web 应用程序来说确实很危险,尤其是考虑到无法过滤的任意字符串。请查看我的 cmets,了解为什么我看到了 eval 方法,然后决定不告诉用户它!。 @Kent Fredric:是的,如果 $foo 或 $replace 来自用户输入,那么绝对存在危险,但从这个问题来看,这对我来说似乎不太可能。而且(正如我看到你指出的那样)污点模式将阻止使用未经审查的 $replace。 我尝试使用 $find=shift; $replace=shift; s/$find/$replace/e for @ARGV; 进行此操作,但有一些变化:在分配给 $replaces/$find/'"$replace"'/ee 和其他一些时引用(附加或 sprintf)。第 2 个有效,第 3 个无效……为什么?【参考方案2】:

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

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

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

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

【讨论】:

简单的方法是 ysth 的答案。 :) 这取决于评估数据的来源。避免 eval 通常是个好主意。 不,避免 eval 通常不是一个好主意。小心使用它。 告诉新用户使用 eval 是不可取的。 感谢分享repl 子程序!这对我有帮助.. 我假设您在 reverse 0 .. $#items 中使用 reverse 以处理混合的一位数和两位数,例如 $12$1【参考方案3】:

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

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】:
# perl -de 0
$match="hi(.*)"
$sub='$1'
$res="hi1234"
$res =~ s/$match/$sub/gee
p $res
  1234

不过要小心。这会导致出现两层eval,每个e 位于正则表达式的末尾:

    $sub --> $1 $1 --> 最终值,在示例中为 1234

【讨论】:

与您的示例一样,请注意$sub='$1' 的分配必须完全一样。 $sub='\1' 被解释为引用,$sub="$1" 尝试执行变量插值。恕我直言,在一天结束时,某种形式的模板库可能会更好地切断 OP,但仍然是一个有趣的例子。谢谢。 这只是偶然发生,因为 $sub 不包含任何干扰 Perl 语法的内容。但是假设例如我希望 $sub 包含一些恰好看起来像赋值的字符串,例如“result=$1”(即试图打印出“result=1234”)。然后你会得到一个警告'未引用的字符串“结果”可能与未来的保留字在...发生冲突'加上一个错误'在...的替换迭代器中使用未初始化的值'并且你的程序将崩溃。因此,仍然缺少允许在任意位置定义包含占位符 $1 的任意 $sub 的解决方案!【参考方案5】:

我建议如下:

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

它的可读性很强,而且似乎很安全。如果需要多次替换,很简单:

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

【讨论】:

这似乎非常缓慢且耗费资源,特别是如果您的文本很长。 @Manu,它不会比其他任何选项使用更多的内存。【参考方案6】:
#!/usr/bin/perl

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

print $res

这让我得到了“1234”。

【讨论】:

重点是我希望 $match 和 $sub 是任意字符串,以便 $sub 可以包含具有相同含义的 \1 你能再解释一下你的问题吗?目前还不清楚你想在这里实现什么......【参考方案7】:

请参阅THIS 之前的 SO 帖子,了解在 Perl 的 s/// 的替换端使用变量。查看accepted answer 和rebuttal 的答案。

您可以使用s///ee 表单来实现您想要做的事情,该表单在右侧字符串上执行双重eval。有关更多示例,请参阅perlop quote like operators。

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

【讨论】:

+1:很酷,我没看到 dup。你是对的,这应该被关闭和整理...... 我错过了什么?链接似乎指向这个问题及其一些答案。是否确实有一个更早的问题不再存在? (我很容易在事后六年问,对吧?;))【参考方案8】:

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

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

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

【讨论】:

【参考方案9】:

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

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

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

【讨论】:

是时候赢取你的 Peer Pressure 徽章了 :) 圣诞快乐。 是的,用户似乎想要在用户空间执行任意正则表达式并将整个正则表达式传递给 Perl。

以上是关于如何在 Perl 替换运算符的替换端使用变量?的主要内容,如果未能解决你的问题,请参考以下文章

如何在后端应用程序等前端 js 应用程序中使用变量替换?

如何在perl中用'*'替换字符串中的所有字符

如何使用 Perl 进行批量搜索和替换?

如何在 perl 正则表达式替换命令中使用 unicode 字符?

如何使用 Perl 搜索和替换多行?

Perl 正则表达式替换,环境变量评估