【问题标题】:How can I substitute the nth occurrence of a match in a Perl regex?如何替换 Perl 正则表达式中匹配的第 n 次出现?
【发布时间】:2010-03-31 18:54:12
【问题描述】:

extracting the n'th regex match 上的一个问题之后,我现在需要替换匹配项(如果找到)。

我认为我可以定义提取子例程并使用/e 修饰符在替换中调用它。我显然错了(诚然,我有一个XY problem)。

use strict;
use warnings;

sub extract_quoted { # à la codaddict

        my ($string, $index) = @_;
        while($string =~ /'(.*?)'/g) {
                $index--;
                return $1 if(! $index);
        }
        return;
}

my $string = "'How can I','use' 'PERL','to process this' 'line'";

extract_quoted ( $string, 3 );
$string =~ s/&extract_quoted($string,2)/'Perl'/e;

print $string; # Prints 'How can I','use' 'PERL','to process this' 'line'

当然,这种技术还有许多其他问题:

  • 如果不同位置有相同的匹配怎么办?
  • 如果找不到匹配项怎么办?

鉴于这种情况,我想知道这可以通过什么方式实现。

【问题讨论】:

  • 你不能匹配表达式n-1 次,然后在第 n 次匹配时进行替换吗?
  • @mmyers:您可能在这里有所了解,但是即使我将替换部分作为子例程的一部分,仍然存在要应对不同索引处的别名匹配问题。
  • 如果有多个匹配项,要全部替换吗?
  • @leonbloy:不,只有索引指定的那个。

标签: regex perl substitution


【解决方案1】:

编辑: leonbloy 首先提出了这个解决方案。如果您想点赞,请先点赞 leonbloy。

受 leonbloy (较早的)回答的启发:

$line = "'How can I','use' 'PERL' 'to process this';'line'";
$n = 3;
$replacement = "Perl";

print "Old line: $line\n";
$z = 0;
$line =~ s/'(.*?)'/++$z==$n ? "'$replacement'" : "'$1'"/ge;
print "New line: $line\n";

旧行:'How can I','use' 'PERL' 'to process this';'line' 新行:'How can I','use' 'Perl' 'to process this';'line'

【讨论】:

  • 哎呀,我在看到你的之前添加了我的修改版本,我发誓 :-) 它们基本上是一样的(不过我错过了引号)
  • @leonbloy 大智若愚。巧合的是,我们的也是!
  • +1 用于正确计数,并用单引号替换。
【解决方案2】:

或者你可以这样做

use strict;
use warnings;

my $string = "'How can I','use' .... 'perl','to process this' 'line'";

my $cont =0;
sub replacen { # auxiliar function: replaces string if incremented counter equals $index
        my ($index,$original,$replacement) = @_;
        $cont++;
        return $cont == $index ? $replacement: $original;
}

#replace the $index n'th match (1-based counting) from $string by $rep
sub replace_quoted {
        my ($string, $index,$replacement) = @_;
        $cont = 0; # initialize match counter
        $string =~ s/'(.*?)'/replacen($index,$1,$replacement)/eg;
        return $string;
}

my $result = replace_quoted ( $string, 3 ,"PERL");
print "RESULT: $result\n";

“全局”$cont 变量有点丑,可以改进,但你明白了。

更新:更紧凑的版本:

use strict;
my $string = "'How can I','use' .... 'perl','to process this' 'line'";

#replace the $index n'th match (1-based counting) from $string by $replacement
sub replace_quoted {
        my ($string, $index,$replacement) = @_;
        my $cont = 0; # initialize match counter
        $string =~ s/'(.*?)'/$cont++ == $index ? $replacement : $1/eg;
        return $string;
}

my $result = replace_quoted ( $string, 3 ,"PERL");
print "RESULT: $result\n";

【讨论】:

  • 有趣...我只需将 $ori 替换为 $original 并将 $rep 替换为 $replacement 以便其他人更清楚。
  • 目前不使用从 1 开始的计数,这是从 0 开始的计数。将 $cont 初始化为 1 或在正则表达式中预递增 $cont 以获得基于 1 的计数,如编码注释中所述并由问题暗示。
  • 是的,这是 mobrule 和 leonbloy 发布的解决方案之间的唯一区别。 @leonbloy:让它++$cont .
【解决方案3】:

如果正则表达式没有比您所拥有的复杂太多,您可以在 split 之后进行编辑和 join

$line = "'How can I','use' 'PERL','to process this' 'line'";

$n = 3;
$new_text = "'Perl'";
@f = split /('.*?')/, $line;
# odd fields of @f contain regex matches
# even fields contain the text between matches
$f[2*$n-1] = $new_text;
$new_line = join '', @f;

【讨论】:

    【解决方案4】:

    perldoc perlvar:

    use strict; use warnings;
    
    use Test::More tests => 5;
    
    my %src = (
        q{'I want to' 'extract the word' 'PERL','from this string'}
        => q{'I want to' 'extract the word' 'Perl','from this string'},
        q{'What about', 'getting','PERL','from','here','?'}
        => q{'What about', 'getting','Perl','from','here','?'},
        q{'How can I','use' 'PERL','to process this' 'line'}
        => q{'How can I','use' 'Perl','to process this' 'line'},
        q{Invalid} => q{Invalid},
        q{'Another invalid string'} => q{'Another invalid string'}
    );
    
    while ( my ($src, $target) = each %src ) {
        ok($target eq subst_n($src, 3, 'Perl'), $src)
    }
    
    sub subst_n {
        my ($src, $index, $replacement) = @_;
        return $src unless $index > 0;
        while ( $src =~ /'.*?'/g ) {
            -- $index or return join(q{'},
                substr($src, 0, $-[0]),
                $replacement,
                substr($src, $+[0])
            );
        }
        return $src;
    }
    

    输出:

    C:\Temp> pw
    1..5
    ok 1 - '另一个无效字符串'
    ok 2 - 'How can I','use' 'PERL','to process this' 'line'
    好的 3 - 无效
    好的 4 - '怎么样','得到','PERL','来自','这里','?
    ok 5 - 'I want to' 'extract the word' 'PERL','from this string'

    当然,如果传递了无效的$index 或未找到所需的匹配项,您需要决定会发生什么。我只是在上面的代码中返回原始字符串。

    【讨论】:

      【解决方案5】:

      重做 answer to an earlier question,匹配 n-1 次,然后替换下一个。记忆模式使可怜的 Perl 不必一遍又一遍地重新编译相同的模式。

      my $_quoted = qr/'[^']+'/; # ' fix Stack Overflow highlighting
      my %_cache;
      sub replace_nth_quoted { 
        my($string,$index,$replace) = @_;
        my $pat = $_cache{$index} ||=
          qr/ ^
              (                    # $1
                (?:.*?$_quoted.*?) # match quoted substrings...
                  {@{[$index-1]}}  # $index-1 times
              )
              $_quoted             # the ${index}th match
            /x;
      
        $string =~ s/$pat/$1$replace/;
        $string;
      }
      

      例如

      my $string = "'How can I','use' 'PERL','to process this' 'line'";
      print replace_nth_quoted($string, 3, "'Perl'"), "\n";
      

      输出

      'How can I','use' 'Perl','to process this' 'line'

      【讨论】:

        猜你喜欢
        • 2015-04-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-01-12
        • 1970-01-01
        • 2016-03-18
        相关资源
        最近更新 更多