Perl: 我可以在用于文本替换正则表达式中的变量中使用捕获组吗?

3

仅仅是为了好玩,我很新手Perl,正在尝试编写一个简单的文本处理工具,但是我卡在了一个简单的问题上。该工具的规则从一个简单的文本文件中读取(不是从脚本中读取,这可能是至关重要的),是一个简单的模式/替换对数组,用于处理文本文件(类似于每行处理每个规则)。以下是应用转换的子程序:

my ($text, @rules) = @_;
my @lines = split(/\n/, $text);
foreach ( @rules ) {
    my $pattern = $_->{"pattern"};
    my $replace = $_->{"replace"};
    $lines = map {
        $_ =~ s/$pattern/$replace/g;
    } @lines;
}
return join("\n", @lines);

例如,如果有一个规则,如pattern=[aeiou]+replace=*,那么文本Foo bar会被处理成F** b*r。这就是我想要的。
但是我不知道为什么不能使用捕获组替换文本内容。比如说,pattern=([fF])+replace=<$1>的结果是<$1>oo bar,但我期望得到的是<F>oo bar。我猜我漏掉了一个非常简单的东西。我错过了什么?
更新:
经过一些实验,我的最终结果是:
sub escapeSubstLiteral {
    my ($literal) = @_;
    $literal =~ s/\//\\\//g;
    $literal;
}

sub subst {
    my ($pattern, $replace, $modifiers) = @_;
    $modifiers ||= '';
    my $expression = '$text =~ s/' . escapeSubstLiteral($pattern) . '/' . escapeSubstLiteral($replace) . '/' . $modifiers;
    return sub {
        my ($text) = @_;
        eval $expression;
        $text;
    };
}

$customSubst = subst($pattern, $replace, $modifiersToken);
$foo = $customSubst->($foo);
$bar = $customSubst->($bar);

https://dev59.com/BXRC5IYBdhLWcg3wKtv2 - Ashalynd
@Ashalynd 是的,我看到了那篇帖子。/e 似乎没有效果,并且会重复上面的行为。将 e 修饰符加倍后变成了 oo bar(没有 F)。 - Lyubomyr Shaydariv
@jm666,我还是有点困惑,不确定是否理解你的意思。如果我说错了,请纠正:$1(以此类推)在文本替换范围内是一种特殊的文本模式,可能无法替换为一个变量?因此,例如,my $REPLACE='"(beep: $1)"'; return $INPUT =~ s/$PATTERN/$REPLACE/eegr;可以正确地返回替换结果,因为它只是作为脚本执行两次(就像我可以使用s/$PATTERN/(beep: $1)/gr一样)? - Lyubomyr Shaydariv
你可以在规则的每个替换部分前后添加 ",并使用 /geer,但如果替换规则中已经有 ",那么它将无法工作。my $replace = '"' . $_->{"replace"} . '"'; - hmatt1
2个回答

2
如果您的替换字符串包含捕获变量,则需要将其作为字符串进行“评估”,因此它需要用双引号括起来,并且替换需要进行双重评估。如果您首先转义任何已经存在的双引号,那么无论其中是否有任何捕获变量,它都会以这种方式工作。
像这样的东西应该适合您。顺便说一下,在进行替换之前将字符串拆分成行可能没有多大用处,因为没有使用“/s”修饰符,它只对非常晦涩的模式产生影响。
use strict;
use warnings;
use 5.010;

my @rules = (
  {
    pattern => '[aeiou]',
    replace => '*', 
  },
  {
    pattern => '([fF])',
    replace => '<$1>',
  },
);

say replace('then text Foo bar is processed into F** b*r', @rules);


sub replace {
  my ($text, @rules) = @_;

  my @lines = split /\n/, $text;

  for my $rule (@rules) {
    my ($pattern, $replace) = @{$rule}{qw/ pattern replace /};
    $replace =~ s/"/\\"/g;
    s/$pattern/'"'.$replace.'"'/gee for @lines;
  }

  join "\n", @lines;
}

输出

th*n t*xt <F>** b*r *s pr*c*ss*d *nt* <F>** b*r

@chilemagic: 我刚刚注意到你上面的评论,提出了一个非常相似的建议。我向你保证,这只是“英雄所见略同”,而不是剽窃。 - Borodin
非常短小的代码,而且正好符合我的需求。谢谢! - Lyubomyr Shaydariv

1
我将我的解决方案作为评论发布,因为我不确定是否有更好的解决方案。由于@Borodin提出了基本相同的解决方案(自己想出来的),所以我想发布一些处理这个问题的代码以及我的想法。
这是我写的代码:
use strict;
use warnings;

my @rules = ({pattern => '[aeiou]', replace => '*'},
             {pattern => 't', replace => 'T'},
             {pattern => '([fF])', replace => '<$1>'});

my $text = "Foo bar\nLine two";
print $text . "\n\n";
my @lines = split("\n", $text);

foreach ( @rules ) {
    my $pattern = $_->{"pattern"};
    my $replace = '"' . $_->{"replace"} . '"';
    print "Replacing $pattern with $replace\n";
    @lines = map {
        $_ =~ s/$pattern/$replace/geer;
    } @lines;
}

print "\nOutput: \n". join("\n", @lines);

输出:

Foo bar
Line two

Replacing [aeiou] with "*"
Replacing t with "T"
Replacing ([fF]) with "<$1>"

Output: 
<F>** b*r
L*n* Tw*

基本上,当您替换包含"的内容时(例如{pattern => 'L', replace => '"l'}),就会出现问题。然后我们会得到一些错误:
Bareword found where operator expected at (eval 7) line 1, near """l"
    (Missing operator before l?)
String found where operator expected at (eval 7) line 1, at end of line
    (Missing semicolon on previous line?)
Use of uninitialized value in substitution iterator at test11.pl line 15.

当你使用\"时,这个部分就解决了:{pattern => 'L', replace => '\"l'} 然后我们的输出将变成:
<F>** b*r
"l*n* tw*

然而,如果你有三个斜杠{pattern => 'L', replace => '\\\"l'},这种方法就会再次出错。

这似乎是一种脆弱的解决方案,因为你不能盲目地在规则中用"替换\"。我希望有更好的解决方案,这也是我发布评论的原因。


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