【问题标题】:Why doesn't the .* consume the entire string in this Perl regex?为什么 .* 不消耗这个 Perl 正则表达式中的整个字符串?
【发布时间】:2009-08-24 17:12:12
【问题描述】:

为什么第一个打印语句没有输出我所期望的:

first = This is a test string, sec = This is a test string 

既然 * 和 + 都是贪婪的,为什么内部 * 即第一个匹配中的 "((" 内部没有消耗整个字符串?

use strict;
use warnings;

my $string = "This is a test string";
$string =~ /((.*)*)/; 
print "first = $1, sec = $2\n";  #prints "first = This is a test string, sec ="

$string =~ /((.+)*)/;
print "first = $1, sec = $2\n";  #prints "first = This is a test string, sec = This is a test string"

【问题讨论】:

  • 更好的问题是“你为什么这样做?” :) 这仅仅是因为您对这种奇怪的边缘情况感到好奇,还是您想实际使用它?
  • 布莱恩,我只是好奇。 :-)

标签: perl regex


【解决方案1】:

在第一个正则表达式中,.* 匹配了两次。第一次匹配整个字符串。第二次匹配到最后的空字符串,因为.*在没有其他匹配的时候匹配空字符串。

其他正则表达式不会发生这种情况,因为.+ 无法匹配空字符串。

编辑:至于去哪里:$2 将包含上次应用 .* / .+ 时匹配的内容。 $1 将包含与(.*)* / (.+)* 匹配的内容,即整个字符串。

【讨论】:

  • 对。但由于括号围绕字符串 - 我希望括号内的整个内容为 $2(而不仅仅是 *)
  • 那么外层 () 匹配的结果在哪里呢?根据你的描述,我猜到 3 美元,但没有到那里。
  • @Anna 组由左括号计算,因此 $1 是整个字符串,其中 $2 是括号的内部集合。
  • 内部匹配实际上是$2,外部匹配是$1。当内部部分第二次匹配时,它会“覆盖”第一次匹配时捕获的输出。如果内部(.*) 匹配多次,则仅将最后一个匹配保留为$2
  • @sepp2k,由于正则表达式匹配从内部 * 开始,它必须消耗整个字符串。那么,为什么在第一种情况下 $2 是空的呢?
【解决方案2】:

使用“use re 'debug'”运行它会导致:

Compiling REx "((.*)*)"
Final program:
   1: OPEN1 (3)
   3:   CURLYX[0] {0,32767} (12)
   5:     OPEN2 (7)
   7:       STAR (9) # <====
   8:         REG_ANY (0)
   9:     CLOSE2 (11)
  11:   WHILEM[1/1] (0)
  12:   NOTHING (13)
  13: CLOSE1 (15)
  15: END (0)
minlen 0 

Matching REx "((.*)*)" against "This is a test string"
   0 <> <This is a >         |  1:OPEN1(3)
   0 <> <This is a >         |  3:CURLYX[0] {0,32767}(12)
   0 <> <This is a >         | 11:  WHILEM[1/1](0)
                                    whilem: matched 0 out of 0..32767
   0 <> <This is a >         |  5:    OPEN2(7)
   0 <> <This is a >         |  7:    STAR(9) # <====
                                      REG_ANY can match 21 times out of 2147483647...
  21 < test string> <>       |  9:      CLOSE2(11)
  21 < test string> <>       | 11:      WHILEM[1/1](0)
                                        whilem: matched 1 out of 0..32767
  21 < test string> <>       |  5:        OPEN2(7)
  21 < test string> <>       |  7:        STAR(9) # <====

  # This is where the outputs really start to diverge
  # --------------------------------------------------------------------------------------------
                                          REG_ANY can match 0 times out of 2147483647...
  21 < test string> <>       |  9:          CLOSE2(11) # <==== Succeeded
  21 < test string> <>       | 11:          WHILEM[1/1](0)
                                            whilem: matched 2 out of 0..32767
                                            whilem: empty match detected, trying continuation...
  # --------------------------------------------------------------------------------------------

  21 < test string> <>       | 12:            NOTHING(13)
  21 < test string> <>       | 13:            CLOSE1(15)
  21 < test string> <>       | 15:            END(0)
Match successful!

Compiling REx "((.+)*)"
Final program:
   1: OPEN1 (3)
   3:   CURLYX[0] {0,32767} (12)
   5:     OPEN2 (7)
   7:       PLUS (9) # <====
   8:         REG_ANY (0)
   9:     CLOSE2 (11)
  11:   WHILEM[1/1] (0)
  12:   NOTHING (13)
  13: CLOSE1 (15)
  15: END (0)
minlen 0 

Matching REx "((.+)*)" against "This is a test string"
   0 <> <This is a >         |  1:OPEN1(3)
   0 <> <This is a >         |  3:CURLYX[0] {0,32767}(12)
   0 <> <This is a >         | 11:  WHILEM[1/1](0)
                                    whilem: matched 0 out of 0..32767
   0 <> <This is a >         |  5:    OPEN2(7)
   0 <> <This is a >         |  7:    PLUS(9) # <====
                                      REG_ANY can match 21 times out of 2147483647...
  21 < test string> <>       |  9:      CLOSE2(11)
  21 < test string> <>       | 11:      WHILEM[1/1](0)
                                        whilem: matched 1 out of 0..32767
  21 < test string> <>       |  5:        OPEN2(7)
  21 < test string> <>       |  7:        PLUS(9) # <====

  # This is where the outputs really start to diverge
  # ------------------------------------------------------------------------------------
                                          REG_ANY can match 0 times out of 2147483647...
                                          failed... # <==== Failed
                                        whilem: failed, trying continuation...
  # ------------------------------------------------------------------------------------

  21 < test string> <>       | 12:        NOTHING(13)
  21 < test string> <>       | 13:        CLOSE1(15)
  21 < test string> <>       | 15:        END(0)
Match successful!

【讨论】:

  • 请参阅“perldoc re”或 perldoc.perl.org/re.html,这会将您引导至“perldoc perldebug”和perldoc.perl.org/perldebug.html#Debugging-regular-expressions
  • @Brad & Ether:是的,我会说实话,我希望我更酷并理解输出,但我几乎无法理解它。 Ether 指向的链接说明了这一点,我只能说,阿门:“为了理解这种典型的大量输出,人们不仅必须了解正则表达式匹配的一般工作原理,还要知道 Perl 的正则表达式是如何工作的在内部编译成自动机。”
  • 如果它包含一些关于我们应该注意这两个输出的内容的评论,这将是一个更好的答案。
  • 我强调了不同之处,希望它更容易弄清楚。
【解决方案3】:

第一个正则表达式的问题是()* 只保存最后一个匹配项和.* 匹配一个空字符串(即什么都没有)。所以,给定

"aaab" =~ /(.)*/;

$1 将是 "b"。如果将该行为与.* 匹配空字符串这一事实结合起来,您可以看到内部捕获有两个匹配项:“这是一个测试字符串”和“”。由于空字符串最后出现,它被保存到$2$1 是整个捕获,所以它相当于"This is a test string" . ""。第二种情况如您所愿,因为.+ 不会匹配空字符串。

【讨论】:

    【解决方案4】:

    我没有答案,但我确实有不同的方式来解决这个问题,使用更简单,也许更现实的正则表达式。

    前两个示例的行为完全符合我的预期:.* 使用整个字符串,正则表达式返回一个只有一个元素的列表。但是第三个正则表达式返回一个包含 2 个元素的列表。

    use strict;
    use warnings;
    use Data::Dumper;
    
    $_ = "foo";
    print Dumper( [ /^(.*)/g ] ); # ('foo')     As expected.
    print Dumper( [ /.(.*)/g ] ); # ('oo')      As expected.
    print Dumper( [ /(.*)/g  ] ); # ('foo', '') Why?
    

    到目前为止,许多答案都强调.* 将匹配任何内容。虽然是真的,但这个回应并没有触及问题的核心,那就是:为什么正则表达式引擎仍然在.* 消耗了整个字符串之后进行搜索?在其他情况下(例如前两个示例),.* 不会抛出额外的空字符串。

    在 Chas 有用的 cmets 之后更新。欧文斯。这三个示例中的任何一个的第一次评估都会导致.* 匹配整个字符串。如果我们可以干预并在那个时候调用pos(),引擎确实会位于字符串的末尾(至少在我们感知字符串时;请参阅 Chas. 的 cmets 以获得更多信息)。然而,/g 选项告诉 Perl 尝试再次匹配 整个 正则表达式。对于示例 #1 和 #2,第二次尝试将失败,并且该失败将导致引擎停止搜索。但是,使用 regex #3,引擎会得到另一个匹配:一个空字符串。然后/g 选项告诉引擎再次尝试整个模式。现在真的没有什么可以匹配了——既不是常规字符也不是尾随的空字符串——所以这个过程停止了。

    【讨论】:

    • 想象你是正则表达式引擎。你被指示匹配任何东西,所以你从“F”开始,你看到你可以添加“o”并且仍然匹配,你看到你可以添加“o”并且仍然匹配,没有更多的字符可以匹配所以当您完成匹配时,g 选项会导致您查看第一个匹配之后是否还有另一个匹配,因此您查看剩下的空字符串。空字符串匹配,所以你返回它然后停止。
    • @Chas。我可能很密集,但为什么 /g 选项不会对前两个示例产生相同的效果?
    • 是的,确实如此,但是因为第一个示例锚定在字符串的开头,所以末尾的空字符串无法匹配(因此不返回)。第二种情况,匹配要求匹配中至少存在一个字符,所以不能匹配空字符串。
    • 未锚定的零个或多个匹配项通常会令人困惑,直到您掌握它们:perl -le 'print for "ababa" =~ /a*/g',这里我们得到六个匹配项,第一个匹配一个,然后另一个匹配两个之间的空字符串a和b。然后是第二个 a,然后是另一个空字符串。最后它将匹配第三个 a,然后是 a 和字符串末尾之间的空字符串。这就是为什么不锚定零个或多个匹配通常是一个坏主意的原因。
    • 当我们查看"aa" =~ /a.*a/ 时,我们需要匹配空字符串行为的原因更加明显。为了匹配,第一个 a 必须匹配第一个 a,第二个 a 必须匹配第二个 a,.* 必须匹配它们之间的空字符串。一个字符串(如正则表达式引擎所见)实际上只是一个由空字符串分隔的字符列表,正则表达式 /ab/ 匹配一个 a 后跟一个空字符串,然后是一个 b。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多