【问题标题】:Predefining a complex replacement for substitution为替换预定义一个复杂的替换
【发布时间】:2016-07-28 21:25:10
【问题描述】:

我正在尝试在s/// 中使用变量。此示例代码按预期工作:

my $regex1 = "e";
my $regex2 = "2";

my @array = ("one two three", "green blue red");

$_ =~ s/$regex1/$regex2/gee foreach (@array);

print $_ foreach (@array);

但是,如果我尝试做一个更复杂的正则表达式,例如:

my $regex1 = "^(\w)";
my $regex2 = "\u$1";

然后替换根本不起作用。我感觉 Perl 确实在寻找“插入符号反斜杠”等等,而不是将其解释为正则表达式。

【问题讨论】:

  • 这是实现ucfirst 的一种非常复杂且(正如您所发现的)容易出错的方法。
  • 这只是一个更复杂的脚本的简化示例,通常涉及可变复杂的正则表达式。

标签: regex perl variables substitution evaluation


【解决方案1】:

您需要防止插入元字符:

my $regex1 = '^(\w)';
my $regex2 = '"\u$1"';

(根据@ThisSuitIsBlackNot 的评论更新)

原因是 Perl 会插入双引号字符串,因此您的变量 $regex1$regex2 不包含您需要的内容:

my $regex1 = "^(\w)";
my $regex2 = "\u$1";
print "$regex1\n"; # ^(w)
print "$regex2\n"; # empty line

因此,替换运算符作为s/^(w)//gee 工作,当然,找不到任何东西。

【讨论】:

  • my $regex2 = '\u$1'; 不够,你需要my $regex2 = q{"\u$1"};
  • 假设 $regex 变量是从用户输入的字符串中拆分出来的,所以他们键入“第一部分/第二部分”,然后在“/”上拆分以生成 $regex1 和 $regex2 s/// 行。在这种情况下如何防止插值?谢谢!
  • 用引号括起来是有效的,至少在你给出的例子中:my ($regex1, $regex2) = split '/', $line; $regex2 = '"' . $regex2 . '"'; 顺便说一句,不要忘记chomp 输入行,否则换行符将被视为正则表达式的一部分也是。
【解决方案2】:

真的不想这样做,因为允许人们将 Perl 代码传递到您的程序中,这些代码将被提供给eval,这不是一件好事。除了非常复杂之外,它还会让你在没有仔细检查的情况下陷入恶意。如果有人输入了aaa/"unlink *",那么必要的/ee 将删除您当前的文件夹

让我们先弄清楚一些事情。在s/PATTERN/REPLACEMENT/ 中,只有PATTERN 是一个正则表达式。 REPLACEMENT 是一个简单的字符串,就像它在双引号中一样

所以让我们像这样编写你的程序。我已将所有字符串放在单引号中,因为您不想使用转义序列或变量插值。我还将您的 /eeg 修饰符更改为 /g。看起来你在到处喷/e,希望它能起作用,这不是写软件的方法

use strict;
use warnings 'all';

my $regex       = 'e';
my $replacement = '2';

my @array = ('one two three', 'green blue red');

s/$regex/$replacement/g for @array;

print "$_\n" for @array;

输出

on2 two thr22
gr22n blu2 r2d

现在你想把它改成

my $regex       = "^(\w)";
my $replacement = "\u$1";

这就是为什么我把你的双引号扔掉了。 Perl 尝试编译 "^(\w)" 并将 \w 视为它无法识别的转义序列,因此您得到

Unrecognized escape \w passed through

它假定您的意思只是w。除非您想转义像 "^(\\w)" 这样的反斜杠,否则您需要单引号来表示字符串 ^(\w)

$replacement 也有类似的情况。 = "\u$1";

您将看到的第一件事是 Perl 试图将 $1 的当前值插入双引号字符串。它目前是未定义的,所以你得到

Use of uninitialized value $1 in ucfirst

但即便如此,它还是强制使用空字符串作为$1,然后将其大写以留给您......空字符串

现在你已经设置好了

$regex       = '^(w)';
$replacement = '';

所以没什么用也就不足为奇了

让我们再次执行您的程序,但这次使用单引号,这样就不会弄乱了

use strict;
use warnings 'all';

my $regex       = '^(\w)';
my $replacement = '\u$1';

my @array = ('one two three', 'green blue red');

s/$regex/$replacement/g for @array;

print "$_\n" for @array;

现在$regex 真的是^(\w)$replacement 真的是\u$1。会出什么问题?

它工作正常。我们得到

\u$1ne two three
\u$1reen blue red

这正是我们所要求的

但现在你的/e 修饰符派上了用场。 single /eREPLACEMENT 评估为 表达式。如果我们想在其中粘贴 $1 . 'xxx' 或类似内容,这将很有用,但由于表达式是 $replacement,我们根本没有优势:表达式 $replacement 与插值 $replacement 相同

我们需要另一个/e 吗?这将在第一个/e 的结果上调用eval,所以我们要求eval '\u$1',由于\u$1 不是一个可行的Perl 程序,所以eval 返回@ 987654363@,我们得到

Use of uninitialized value in substitution iterator

解决办法是把$replacement做成一个可编译的程序。在它周围加上双引号,比如 "\u$1" 将它变成一个非常短的 Perl 程序,它返回 $1 的当前值,第一个字符大写

我们需要将$replacement 设置为该字符串,包括双引号并避免像以前一样处理转义字符和$1。如果我写

my $replacement = '"\u$1"';

然后我得到确切的字符串"\u$1",包括双引号

现在让我们试试

use strict;
use warnings 'all';

my $regex       = '^(\w)';
my $replacement = '"\u$1"';

my @array = ('one two three', 'green blue red');

s/$regex/$replacement/eeg for @array;

print "$_\n" for @array;

输出

One two three
Green blue red

正如我所说,你真的不想这样做!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-04-28
    • 1970-01-01
    • 2020-10-29
    • 1970-01-01
    • 1970-01-01
    • 2017-09-17
    • 2023-03-18
    • 1970-01-01
    相关资源
    最近更新 更多