【问题标题】:Perl regex substitution using external parameters使用外部参数的 Perl 正则表达式替换
【发布时间】:2015-07-07 12:08:22
【问题描述】:

考虑以下示例:

my $text = "some_strange_thing";
$text =~ s/some_(\w+)_thing/no_$1_stuff/;
print "Result: $text\n";  

打印出来

“结果:没有_奇怪_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_stuff”。

怎么了?如何使用外部提供的模式获得相同的结果?

【问题讨论】:

  • 你为什么在$repl 中逃脱$?试试这个:my $repl = "no_$1_stuff";.
  • @VladimirSerykh - 如果你仔细想想,为什么会产生no__stuff 应该很明显。
  • @DanielMartin 我对 Perl 中的字符串一无所知。但我知道一些正则表达式。
  • 试过了。 Perl 说“在连接 (.) 或字符串中使用未初始化的值”

标签: regex perl substitution


【解决方案1】:

解决方案 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在下面的cmets中提到的解决方案。

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:一种快速的'n'dirty 方式(如果替换不包含双引号且不考虑安全性)

免责声明我提供了这个解决方案,因为这种方法可以在网上找到,但没有解释它的警告。 不要在生产中使用它

使用这种方法,你不能有一个包含" 双引号的替换字符串,因为这相当于让编写配置文件的人直接访问代码,它不应该暴露给 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 demo

结果:

Result: no_strange_stuff

你必须

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

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

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

【讨论】:

  • 谢谢!确实 /ee 和 'quoting' 一起解决了我的问题。不能说我完全理解为什么“引用”技巧有效。我尝试了其他的东西,比如逃跑,但显然我错过了一些东西。你能解释一下这背后的逻辑吗?
  • 好的,现在很清楚了。 /e 需要“引用”字符串,而 ' ' 提供了这个。再次感谢! :)
  • 请注意,为了以这种方式引用,您不能拥有包含 " 字符的 repl。另外请注意,这相当于让编写配置文件的人直接访问代码,因此不要将其暴露给网络用户。
  • String::Substitution 绝对是要走的路。 -1 种植短而甜的/ee 是一种选择的想法。
  • 对于这个用例,我还推荐 replace from Data::Munge 作为比 /ee 更好的选择。
【解决方案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 由不受信任的用户提供时安全地进行这种替换,如果您的用例包含该问题,则应作为不同的问题提出。

【讨论】:

  • 谢谢!不,来源不是网络用户。我对不同的项目有多个过滤选项,并且希望对所有人都有相同的脚本。区别主要在于“模式”,这就是为什么我想让它们保持在外部。
  • /ee 非常危险。不要使用任意 Perl 代码作为模板;使用实际模板! String::Substitution 提供了解决方案。
猜你喜欢
  • 2013-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-28
相关资源
最近更新 更多