【问题标题】:How to perform search-and-replace within given $start-$end ranges?如何在给定的 $start-$end 范围内执行搜索和替换?
【发布时间】:2016-05-31 21:57:15
【问题描述】:

比如说,一个文本文件有很多 $start-$end 对,每对中都有一些文本。我希望 Perl 用 $start-$end 对查找并替换所有 $patterns;如果 $pattern 不在这对中,则不要替换它。例如文本:

xx START xx bingo xx bingo xx END xx bingo xx START xx bingo xx END bingo

文本中可能有换行符(此处未显示); $pattern 在一对中可能出现多次。预期结果是:

xx START xx okyes xx okyes xx END xx bingo xx START xx okyes xx END bingo

这项工作看起来很简单,但我只是想不出一个 Perl 正则表达式来完成它。有人能帮忙吗?

【问题讨论】:

  • 你的模式总是单字吗?
  • 惰性量词:(?:START)(.+?)(?:END) - 并在$1 中搜索/替换宾果游戏。尽管更真实的输入可能会有所帮助,尤其是换行符等。
  • @Sobrique 目前,是的,应该只是单个单词。
  • @WiktorStribiżew 是的!我不明白令人生畏的正则表达式,但它确实有效!我想我会非常非常仔细地研究这个表达方式。

标签: regex perl replace


【解决方案1】:

查看您的“来源”,我建议这里的技巧是设置 $/ - 记录分隔符。

如果将其设置为单个空格,则可以逐字迭代。 然后使用range operator 来确定您是否在分隔符内。

例子:

#!/usr/bin/env perl

use strict;
use warnings;

local $/ = ' ';

while ( <DATA> ) {
   if (  m/START/ .. /END/ ) {
       s/bingo/okyes/g;
   } 
   print;
}

__DATA__
xx START xx bingo xx bingo xx END xx bingo xx START xx bingo xx END bingo

打印出来:

xx START xx okyes xx okyes xx END xx bingo xx START xx okyes xx END bingo

您可以使用单个正则表达式来完成此操作。我将建议你不要,因为它会很复杂,以后很难理解。

【讨论】:

  • 我也只是在破解范围运算符解决方案。 :)
【解决方案2】:

我发现使用@-@+ 内置数组以及substr 作为左值可以最简单地完成此类操作

$-[1] 包含字符串中第一次捕获开始的偏移量,而$+[1] 包含它结束的偏移量。因此$+[1]-$-[1] 是捕获部分的长度

此程序查找所有出现的/START(.+?)END/ 并编辑捕获的部分—​​—STARTEND 之间的区域——通过对该子字符串应用正则表达式替换

您可能需要根据您正在使用的实际数据稍微改变这一点

use strict;
use warnings 'all';
use feature 'say';

my $str = 'xx START xx bingo xx bingo xx END xx bingo xx START xx bingo xx END bingo';
my ($start, $end, $pattern, $replacement) = qw/ START END bingo okyes /;

while ( $str =~ /\b$start\b(.+?)\b$end\b/gs ) {
     substr($str, $-[1], $+[1]-$-[1]) =~ s/$pattern/$replacement/g;
}

say $str;

输出

xx START xx okyes xx okyes xx END xx bingo xx START xx okyes xx END bingo

【讨论】:

    【解决方案3】:

    在 START 和 END 上分割每一行,保留一个标志,告诉你是否在一个范围内。

    #!/usr/bin/perl
    use warnings;
    use strict;
    
    my $inside;
    while (<>) {
        my @strings = split /(START|END)/;
        for my $string (@strings) {
            if ('START' eq $string) {
                $inside = 1;
    
            } elsif ('END' eq $string) {
                undef $inside;
    
            } elsif ($inside) {
                $string =~ s/bingo/okyes/g;
    
            }
    
            print $string;
        }
    }
    

    或者使用哈希作为开关更短:

    #!/usr/bin/perl
    use warnings;
    use strict;
    use Syntax::Construct qw{ // };
    
    my $inside;
    while (<>) {
        my @strings = split /(START|END)/;
        for my $string (@strings) {
            $inside = { START => 1,
                        END   => 0,
                      }->{$string} // $inside;
    
            $string =~ s/bingo/okyes/g if $inside;
            print $string;
        }
    }
    

    【讨论】:

      【解决方案4】:

      最终使用以下代码来完成我的意图:

      $_ = "xx START xx bingo xx bingo xx END xx bingo xx START xx bingo xx END bingo";
      print;
      print "\n";
      $_ =~ s/START.*?END/($s=$&) =~ s,bingo,okyes,g; $s/ge;
      print;
      

      这是一个单一的正则表达式解决方案,在s///g 正则表达式中使用嵌入表达式,并嵌套s///g 正则表达式。

      很抱歉这篇迟到的帖子,但我非常感谢@Sobrique、@Borodin 和@choroba 的回复,这些回复很有启发性和帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-09-04
        • 2013-04-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-04-16
        • 2018-09-28
        • 2017-10-29
        相关资源
        最近更新 更多