【问题标题】:Why does `word (.(?!another-word))*?` match only a character?为什么 `word (.(?!another-word))*?` 只匹配一个字符?
【发布时间】:2017-03-27 12:19:41
【问题描述】:

我有一个字符串

my $text = 
  "abc " .
  "VVV foo III " .                # <- here
  "pqr hash def " .
  "VVV bar hash baz III " .       # <- here
  "stu hash ghi " .
  "VVV bbb, ccc hash ddd III " .  # <- here
  "vwx";

在本文中,如果出现在单词VVVIII 之间,我想替换单词hash。用于替换的潜在片段在上面的 sn-p 中用&lt;- here 标记。第一个片段没有单词hash,因此不能在那里进行替换。

我想出了以下正则表达式:

$text =~ s/ VVV (.(?!III))*? hash (.*?)(?=III)/ VVV $1 HASH $2/g;

有了这个正则表达式,$text 就变成了

abc VVV foo III pqr hash def VVV r HASH baz III stu hash ghi VVV c HASH ddd III vwx

替换是在正确的位置进行的,然而hash 之前的文本仅替换为一个字符(VVV r HASH 而不是 VVV bar HASHVVV c HASH 而不是 @987654335 @)。

我不明白为什么会这样。据我了解正则表达式,VVV 应该充当锚,从而确保匹配它之后的每个字符。

【问题讨论】:

  • 我认为你需要使用VVV ((?:(?!III).)*?) hash (.*?)(?=III) 模式。甚至VVV ((?:(?!VVV|III).)*?) hash (.*?)(?=III)
  • 您提出的模式似乎确实有效。
  • 这看起来有点像XY problem - 你到底想完成什么?

标签: regex perl


【解决方案1】:

您正在尝试在您的模式中使用tempered greedy token。要正确编写它,您需要将前瞻放在要“调整”的点之前,使用非捕获组将前瞻与点包裹起来,然后捕获 整个子字符串与另一个捕获组中的构造相匹配。

/VVV ((?:(?!III).)*?) hash (.*?III)/
     ^              ^

请注意,将III 放入正向前瞻中是没有意义的,您不妨匹配并捕获它以便稍后使用$2 进行恢复。

现在,((?:(?!III).)*?) 将在第 1 组中捕获任何不以 III 字符序列开头的字符(换行符除外)、0 次或多次出现的字符。

【讨论】:

  • 好的,我现在明白为什么我必须将(?!III) 放在. 前面。但我仍然不明白我的(错误)正则表达式的哪一部分“吃掉”或“覆盖”了barbabbb, cccbbb, cc
  • 当您不将整个回火的贪婪令牌包装在捕获组中时,(.(?!III))*? 子模式匹配任何不跟 III 子字符串和 捕获 的字符char 到第 1 组。由于量词 *? 设置为整个 分组构造 (.(?!III)),因此每次匹配 char 时重新编写捕获组值并被俘虏。它被称为重复捕获组。阅读Repeating a Capturing Group vs. Capturing a Repeated Group,那你应该清楚了。
  • 非常感谢您提供此链接。这很有帮助。事实上,我并没有意识到“重复捕获组”和“捕获重复组”之间的区别。
【解决方案2】:

因为:(.(?!III))*? - 星号在括号外,而不是在括号内,所以你没有捕获所有的事件 - 只是一个。 (最后一个)。

正如Wiktor Stribiżew cmets 中的注释:

/VVV ((?:(?!III).)*?) hash (.*?)(?=III)/

做你想做的。

但我建议你只是试图让你的正则表达式变得过于复杂,而你应该真正做的是进一步分解问题 - 多重匹配如果有必要获得包含;排除集。

类似:

#!/usr/bin/env perl
use strict;
use warnings;

my $text = "abc " . 
  "VVV foo III " .                  # <- here
  "pqr hash def " . 
  "VVV bar hash baz III " .         # <- here
  "stu hash ghi " . 
  "VVV bbb, ccc hash ddd III " .    # <- here
  "vwx";

#split your block on VVV, without removal
for ( split /(?=VVV)/, $text ) {
   #replace 'this chunk' so between 'VVV' and 'III' 
   s/hash (.*) III/HASH $1 III/g;
   #print the line. Or you could insert this back into your primary text
   print;

}

【讨论】:

    【解决方案3】:

    我会通过在空格上拆分字符串并遍历结果列表来做到这一点,使用 范围运算符 来确定任何给定的 hash 是否符合更改的条件

    看起来像这样。请注意,split 还捕获中间的空白,以便可以按原样替换制表符或多个空格

    use strict;
    use warnings 'all';
    use feature 'say';
    
    my $text =
        "abc " .
        "VVV foo III " .                  # <- here
        "pqr hash def " .
        "VVV bar hash baz III " .         # <- here
        "stu hash ghi " .
        "VVV bbb, ccc hash ddd III " .    # <- here
        "vwx"
    ;
    
    my @text = split /(\s+)/, $text;
    
    for ( @text ) {
        my $in_range = $_ eq 'VVV' .. $_ eq 'III';
        $_ = 'HASH' if $_ eq 'hash' and $in_range;
    }
    
    say join '', @text;
    

    输出

    abc VVV foo III pqr hash def VVV bar HASH baz III stu hash ghi VVV bbb, ccc HASH ddd III vwx
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-25
      • 1970-01-01
      相关资源
      最近更新 更多