regex 如果字符串可以出现一次或多次,则无法提取跨多行匹配某些字符串的完整文本块

qhhrdooz  于 2023-05-01  发布在  其他
关注(0)|答案(1)|浏览(117)

我需要从文本文件中提取信息块以便以后重新处理它们。我需要找到并提取的块看起来像这样:

Group NAME

Description DESCRIPTION

Group member MEMBER

不存在没有成员的组,说明并不总是存在。
然而,组的成员可以是一个或多个。成员名称可以是姓名,也可以是数字。
我试着写了一个Perl脚本:

#!/usr/bin/perl

use warnings 'all';
use strict;
use feature qw(say);

my $path="input.txt";

open my $fh, "<", $path or die "Can't open $path: $!";
#local $/;
my $accumulated_text = '';
while (my $line = <$fh>)
{
    $accumulated_text .= $line;
    #Match against accumulated text
    if ($accumulated_text =~ /group.*\s*(description.*\s*)?\s*(member.*\s*)+/)
 {
        say $&;
        $accumulated_text = '';
    }
}
close $fh;

但它只返回一个组成员,而不返回所有其他成员(如果有的话)。如果我尝试使用'{1,}'而不是'+',我会得到相同的结果,如果我使用'{2,}'或任何其他数字,它会返回确切的数字,但不会更多。如何提取包含所有组成员的整个块?
期望输出为:

Group GROUP_NAME
Member MEMBER_1
Member MEMBER_2
Member MEMBER_3
Member MEMBER_4
......

或者

Group GROUP_NAME
Description DESCRIPTION
Member MEMBER_1
Member MEMBER_2
Member MEMBER_3
Member MEMBER_4
......

但我总是得到:

Group GROUP_NAME
Member MEMBER_1

Group GROUP_NAME
Description DESCRIPTION
Member MEMBER_1

不超过一名成员。..如何处理多个组成员?
谢谢你

trnvg8h3

trnvg8h31#

通过以Group开头且后面没有member的行标识新组的开头,并分别存储每个组的行

use warnings;
use strict;
use feature 'say';
use Data::Dump qw(dd);  # or use core Data::Dumper

my @groups;

while (<>) { 
    chomp;

    if (/^\s*Group\s+(?!member)/) { 
        # Start collecting lines for a new group
        push @groups, [$_];
    }   
    else { 
        push @{$groups[-1]}, $_; 
    }   
}
    
dd \@groups;

菱形运算符<>逐行读取文件,文件名在命令行中给出,因此将其用作perl progname filename(或progname filename,如果它是可执行的)。这在我的测试中表现得很好。
匿名数组与组的行存储为数组的元素在这里。将它们存储在散列中也是有意义的,也许密钥是组的名称。这应该是对上述内容的简单修改。
在上面的内容中,我忘记了您希望存储来自组标题以外的行的特定信息。编辑得到的数组很容易,但这也带来了另一种更简单的方法:先拆开生产线,然后再测试零件

while (<>) { 
    chomp;

    my ($first_word, $rest) = split ' ', $_, 2;

    if ($first_word eq 'Group' and $rest !~ /^member/) { 
        # Start collecting lines for a new group
        push @groups, [$_];
    }   
    elsif ($first_word ne 'Group') {
        push @{$groups[-1]}, "$first_word $rest";
    }
    else {
        push @{$groups[-1]}, $rest;
    }   
}

在这里,这一行只被分成两部分(split中的2),所以在if条件下,我们需要一个正则表达式来检查它是否以member * 开始 *-我这样做是为了至少为字符串的“其余部分”保留空格。
如果空格不重要,则将行拆分为

my ($first, $second, @rest) = split;

if ($first eq 'Group' and $second ne 'member') {
    ...
}
elsif ($first_word ne 'Group') {
    push @{$groups[-1]}, join ' ', $first, $second, @rest;
}
else {
    push @{$groups[-1]}, join ' ', $second, @rest;
}

相关问题