Perl正则表达式用于提取多行块

8

我有下面这样的文本:

00:00 stuff
00:01 more stuff
multi line
  and going
00:02 still 
    have

因此,我没有块结束标记,只有一个新的块开始标记。

我想递归地获取所有的块:

1 = 00:00 stuff
2 = 00:01 more stuff
multi line
  and going
下面的代码只会给我这个结果:
$VAR1 = '00:00';
$VAR2 = '';
$VAR3 = '00:01';
$VAR4 = '';
$VAR5 = '00:02';
$VAR6 = '';

我做错了什么?
my $text = '00:00 stuff
00:01 more stuff
multi line
 and going
00:02 still 
have
    ';
my @array = $text =~ m/^([0-9]{2}:[0-9]{2})(.*?)/gms;
print Dumper(@array);
3个回答

4

版本5.10.0引入了命名捕获组,用于匹配非平凡模式。

(?'NAME'pattern)
(?<NAME>pattern)

一个命名捕获组。与普通捕获括号()在每个方面都相同,但额外增加了通过名称在各种正则表达式结构(例如\g{NAME})中引用该组的功能,并且可以在成功匹配后通过名称访问该组,方法是使用%+%-。有关perlvar%+%-哈希的更多详细信息,请参见。

如果多个不同的捕获组具有相同的名称,则$+{NAME}将引用匹配中最左侧定义的组。

形式(?'NAME'pattern)(?<NAME>pattern)是等效的。

命名捕获组允许我们在正则表达式中命名子模式,如下所示。

use 5.10.0;  # named capture buffers

my $block_pattern = qr/
  (?<time>(?&_time)) (?&_sp) (?<desc>(?&_desc))

  (?(DEFINE)
    # timestamp at logical beginning-of-line
    (?<_time> (?m:^) [0-9][0-9]:[0-9][0-9])

    # runs of spaces or tabs
    (?<_sp> [ \t]+)

    # description is everything through the end of the record
    (?<_desc>
      # s switch makes . match newline too
      (?s: .+?)

      # terminate before optional whitespace (which we remove) followed
      # by either end-of-string or the start of another block
      (?= (?&_sp)? (?: $ | (?&_time)))
    )
  )
/x;

将其用于如下方式

my $text = '00:00 stuff
00:01 more stuff
multi line
 and going
00:02 still
have
    ';

while ($text =~ /$block_pattern/g) {
  print "time=[$+{time}]\n",
        "desc=[[[\n",
        $+{desc},
        "]]]\n\n";
}

输出:

$ ./blocks-demo
时间=[00:00]
描述=[[[
一些东西
]]]
时间=[00:01] 描述=[[[ 更多的东西 多行 并且继续 ]]]
时间=[00:02] 描述=[[[ 仍然有 ]]]

1
真正优秀的现代 Perl5 例子 :) - XoR

3

这应该能解决问题。下一个\d\d:\d\d的开始被视为块结束。

use strict;

my $Str = '00:00 stuff
00:01 more stuff
multi line
  and going
00:02 still 
    have
00:03 still 
    have' ;

my @Blocks = ($Str =~ m#(\d\d:\d\d.+?(?:(?=\d\d:\d\d)|$))#gs);

print join "--\n", @Blocks;

2
你的非捕获括号 (?: ... ) 在这里是多余的,因为 (?= ...) 也可以使用交替。另外,我注意到你仍然没有编写符合 strict 标准的代码,在我的书中这是不好的,因为它鼓励了不良实践。 - TLP
1
我在另一个帖子中已经充分解释了"use strict"的用法。你介意停止这样做吗? - tuxuday
2
是的,我介意。你介意停止发布非严格代码吗?这是一个学习环境,发布鼓励良好实践的代码对你来说不会有任何成本,对吧? - TLP
2
没有人会在另一个线程中关心你说了什么。他们怎么知道要去那里看呢?请记住,StackOverflow会将其内容进行转载,因此您的答案很可能会单独显示在其他网站上。 - brian d foy
太棒了!这是在线运行的答案 https://regex101.com/r/TwEsxU/1... 我已经用它来解决自己的大问题 https://regex101.com/r/G1ZrU8/1... 非常感谢! - Marcello DeSales

0
你的问题在于.*?.*一样是非贪婪的。当它没有被强制时,它会尽可能地匹配最少的内容,而在这种情况下就是空字符串。
因此,在非贪婪匹配后面需要有一些东西来锚定你的捕获。我想出了这个正则表达式:
my @array = $text =~ m/\n?([0-9]{2}:[0-9]{2}.*?)(?=\n[0-9]{2}:|$)/gs;

正如您所看到的,我删除了/m选项,以便能够准确地匹配预测断言中的字符串结尾。

您也可以考虑这个解决方案:

my @array = split /(?=[0-9]{2}:[0-9]{2})/, $text;

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