使用外部参数进行 Perl 正则表达式替换

6
请看下面的例子:
my $text = "some_strange_thing";
$text =~ s/some_(\w+)_thing/no_$1_stuff/;
print "Result: $text\n";  

它打印输出

“结果:no_strange_stuff”

到目前为止一切顺利。

现在,我需要从外部来源(用户输入、配置文件等)获取匹配和替换模式。一个朴素的解决方案看起来像这样:

my $match = "some_(\\w+)_thing";
my $repl = "no_\$1_stuff";

my $text = "some_strange_thing";
$text =~ s/$match/$repl/;
print "Result: $text\n";  

然而:

"结果:没有$1的东西"。

出了什么问题?我如何使用外部提供的模式获得相同的结果?


1
你为什么在 $repl 中使用了转义符 $?试试这个:my $repl = "no_$1_stuff"; - Vlad DX
1
@VladimirSerykh - 如果你仔细思考一下,就会明显知道为什么会得到no__stuff - Daniel Martin
@DanielMartin 我不知道Perl中的字符串。但我知道一些正则表达式。 - Vlad DX
尝试了,Perl 说“在连接(.)或字符串中使用未初始化的值”。 - Noob
2个回答

9

解决方案1: String::Substitution

使用String::Substitution:

use String::Substitution qw(gsub_modify);

my $find = 'some_(\w+)_thing';
my $repl = 'no_$1_stuff';
my $text = "some_strange_thing";
gsub_modify($text, $find, $repl);
print $text,"\n";

替换字符串仅插入(宽泛使用的术语)编号匹配变量(如$1${12})。有关更多信息,请参见"interpolate_match_vars"
此模块不保存或插入$&以避免“相当大的性能惩罚”(请参见perlvar)。

解决方案2:Data::Munge

这是Grinnz在下面的评论中提到的解决方案。

Data::Munge 可以以下面的方式使用:

use Data::Munge;

my $find = qr/some_(\w+)_thing/;
my $repl = 'no_$1_stuff';
my $text = 'some_strange_thing';
my $flags = 'g';
print replace($text, $find, $repl, $flags);
# => no_strange_stuff

解决方案3:一种快速而不太规范的方法(如果替换内容不包含双引号且不考虑安全性)

免责声明我提供这个解决方案是因为这种方法可以在网上找到,但它的注意事项没有被解释。请不要在生产环境中使用

使用此方法,您不能有包含"双引号的替换字符串,因为这相当于将编写配置文件的人直接访问代码,不应暴露给Web用户(如Daniel Martin所述)。

您可以使用以下代码:

#!/usr/bin/perl
my $match = qr"some_(\w+)_thing";
my $repl = '"no_$1_stuff"';
my $text = "some_strange_thing";
$text =~ s/$match/$repl/ee;
print "Result: $text\n";

查看 IDEONE演示

结果:

Result: no_strange_stuff

您需要:
  1. '"..."' 中声明替换,以便稍后可以评估 $1
  2. 使用 /ee 强制替换中的变量进行双重评估。

专门用于搜索和替换的修饰符是 s///e 评估修饰符。 s///e 将替换文本视为 Perl 代码,而不是双引号字符串。代码返回的值将替换匹配的子字符串。如果您需要在替换文本过程中进行一些计算,则可以使用 s///e

您可以使用 qr 实例化正则表达式的模式(qr"some_(\w+)_thing")。


1
谢谢!确实,/ee 和 '引用' 结合使用解决了我的问题。我不能说我完全理解为什么 '引用' 技巧会起作用。我尝试过其他的方法,比如转义,但显然我漏掉了一些东西。你能否请稍微解释一下这背后的逻辑呢? - Noob
1
好的,现在清楚了。/e需要“引用”的字符串,而' '提供了这个功能。再次感谢! :) - Noob
2
请注意,为了以这种方式引用,您不能有包含"字符的repl。另外,请注意,这相当于直接向编写配置文件的人提供代码访问权限,因此不要将其暴露给Web用户。 - Daniel Martin
1
String::Substitution绝对是最好的选择。-1种植了短小精悍的/ee是一个选项的想法。 - ikegami
1
我还建议使用Data::Munge中的replace作为此用例的更好选择,而不是使用/ee - Grinnz
显示剩余2条评论

2

本质上与已接受的解决方案相同,但我保留了初始行,因为我认为这可能会使其更容易适用于更多情况:

my $match = "some_(\\w+)_thing";
my $repl = "no_\$1_stuff";

my $qrmatch = qr($match);
my $code = $repl;

$code =~ s/([^"\\]*)(["\\])/$1\\$2/g;
$code = qq["$code"];

if (!defined($code)) {
  die "Couldn't find appropriate quote marks";
}

my $text = "some_strange_thing";
$text =~ s/$qrmatch/$code/ee;
print "Result: $text\n";

请注意,这个解决方案可以处理$repl中的任何内容,而天真的解决方案在$repl包含双引号字符或以反斜杠结尾时有问题。
此外,请确保不要跳过qr行,因为假如你打算在循环中运行最后三行代码(或类似的代码),这将会产生巨大的性能差异。如果你跳过了qr,只使用s/$match/$code/ee,那么结果将相差很大。
虽然使用此解决方案进行任意代码执行并不像使用被接受的解决方案那样简单,但我不会感到惊讶如果仍然可以实现。通常,如果$match$repl来自不可信用户,则应避免基于s///ee的解决方案。(例如,不要基于此构建 Web 服务)
假如你的用例包括由不可信用户提供$match$repl的安全替换,那么应该提出一个不同的问题。

谢谢!不,源不是webuser。我有多个过滤选项用于不同的项目,并希望拥有相同的脚本。区别主要在于“模式”,这就是为什么我希望将它们保留在外部的原因。 - Noob
/ee非常危险。不要将任意Perl代码用作模板;使用实际的模板!String::Substitution提供了解决方案。 - ikegami

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接