【问题标题】:How to print only the capture groups of all matches in perl?如何仅打印 perl 中所有匹配项的捕获组?
【发布时间】:2021-02-11 21:37:30
【问题描述】:

如何在 perl 中仅打印所有匹配项的捕获组? /g 似乎不起作用。

我认为我没有正确地使用if 语句,这就是我问的原因。正确的方法是怎样做的?(我在互联网上找不到任何有用的东西,我努力了好几个小时才让它最终起作用。)

$LONG_REGEX_WITH_TWO_CAPTURING_GROUPS="";
$file1="file1.html";

/* This part is complicated, this is why I said nothing 
 * about the two, but here is the result:
 *
 * Basically $2 (a letter) + whitespace + $1 (a filename)
 * a file.txt
 * b anotherfile.txt
 * c 3rdfile.txt
 * d 4thfile.txt
 * 
 * I want it to become>
 * a - (A specific part of the text in file.txt)
 * b - (A specific part of the text in anotherfile.txt)
 * etc.
*/

my $content1 = do { open my $fh, '<', $file1 or die $!; local $/; <$fh>; };

if ( $content1 =~ /$LONG_REGEX_WITH_TWO_CAPTURING_GROUPS/g ) {
    # Print the letter first ($2).
    print "$2 - ";
    # Open the corresponding file (it's name is $1).
    my $content2 = do { open my $fh, '<', $1 or die $!; local $/; <$fh>; };
    # Try to complete the task.
    if ( $content2 =~ /$SECOND_REGEX/g ) {
        print "$1\n"; # There is just one capturing group.
    }
}

但是,这只会打印第一个匹配项,即使它有一个全局标志。

如:

a - The desired text.

别管代码,问题很简单:如何只打印捕获组中的内容,但从所有匹配项中打印(或使其匹配文件中的所有内容)?

谢谢!

我正在编辑,所以我可以把代码放在这里:

#!/usr/bin/perl

$file1="file1.html";
my $content1 = do { open my $fh, '<', $file1 or die $!; local $/; <$fh>; };

foreach ( $content1 =~ m/LONG_REGEX_WITH_TWO_CAPTURING_GROUPS/g ) {
    # If I were to put a print "$content1"; here, the program would have
    # no output. Here is the problem, the question still remains.
    print "$2 - ";
    my $content2 = do { open my $fh, '<', $1 or die $!; local $/; <$fh>; };
    foreach ( $content2 =~ m/SECOND_REGEX>/g ) {
        print "$1\n"; # There is just one capturing group.
    }
}

这对我有用:

#!/usr/bin/perl

$file1="file1.html";
my $content1 = do { open my $fh, '<', $file1 or die $!; local $/; <$fh>; };
while ( $content1 =~ /LONG_REGEX_WITH_TWO_CAPTURING_GROUPS/g ) 
    print "$2 - ";
    my $content2 = do { open my $fh, '<', "../../VT/$1" or die $!; local $/; <$fh>; };
    while ( $content2 =~ /SECOND_REGEX/g ) {
        print "$1\n\n<br/>"; # There is just one capturing group.
    }
}

【问题讨论】:

  • 我的意思是,这工作 perl -0777 -ne 'while(m/LONG_REGEX_WITH_TWO_CAPTURING_GROUPS/g";}' 但我有两个文件,我需要更多代码才能获得所需的结果。
  • 我建议在匹配成功后立即添加一行my ($filename, $letter) = ($1,$2);。当您添加修改$1$2 的代码时,这可以避免出现意外错误。在你做任何其他事情之前保存这两个值,以确保它们不会被破坏。当然,open $fh, '&lt;', $filename也一样会更清楚。
  • @doqx 你能展示你匹配的样本(字符串)吗?它会减少这里的可能性:)(见我的回答——有很多方法可以做到这一点)

标签: regex perl


【解决方案1】:

您想遍历匹配项,但没有循环。 g 修饰符使您成为一个包含所有匹配项的数组,您必须遍历该数组。

@matches = ( 'foo' =~ m{o}g );

这将使您成为一个包含两个 "o" 字符串的数组。

您可以使用如下代码迭代匹配项:

foreach ( 'foo' =~ m{o}g ) { ... }

如果您想迭代并且只需要匹配组,则必须在标量上下文中调用匹配运算符。下面是一个例子:

$str="a m7 bcd 9 m2 cde m3";
while ($str =~ m{m(\d)}g) {
   print "$1\n";
}

这会打印 7、2 和 3,每一个都排成一行。

【讨论】:

  • 但是如何使数组只包含捕获组,而不是完全匹配,而不是它们之间的内容?
  • 我已经更新了我的问题以将代码粘贴到那里,它仍然不起作用,你能帮帮我吗?
【解决方案2】:

我不清楚该正则表达式究竟是如何工作的,但这里有两种可能的情况。

您似乎有一个匹配一个大模式中的多个(两个)子模式的正则表达式。那么你就不需要/g 修饰符了;当大模式匹配时,子模式也匹配(根据需要捕获)。然后你可以在列表上下文中使用m operator,以便它返回这些捕获,而不是返回真/假,它在标量上下文中的作用

my $string = q(73 name);

my @matches = $string =~ /([0-9]+) \s* ([a-z]+)/xi;

if (@matches) {
    # it matched, process the two captures
}

这可以在if 语句的条件内完成

if (my @matches = $string =~ /([0-9]+)\s*([a-z]+)/i) { 
    # getting here only means that there were *some* matches
    # check @matches as suitable, process
}

现在整个事情的范围是if 语句;外面没有@matches 变量。

或者,在这种情况下,您可以简单地使用捕获变量,例如

if ( $string =~ /([0-9]+) \s* ([a-z]+)/xi ) {
    # use $1 and $2 (check whether they were both defined)
}

regex operators in perlop 上查看更多信息,并查看参考perlre


另一种可能性是,一个正则表达式模式需要在字符串中匹配多次,因为引擎会沿着字符串进行解析。为此,您确实需要 \g 修饰符。

上面写着"Global matching" in perlretut

[...] 修饰符/g 代表全局匹配,允许匹配运算符在一个字符串内尽可能多地匹配。在标量上下文中,对字符串的连续调用将使/g 从一个匹配跳到另一个匹配,同时跟踪字符串中的位置。 [...]

由于您需要同时处理两个匹配项,因此您需要在list context 中进行匹配并将返回的匹配项捕获到一个数组中,然后处理该数组。例如

my $string = '1 one 2 two';
my @matches = $string =~ /([a-z]+)/gi;  # @matches has elements: ('one', 'two')
# check how many @matches, etc

或者也许像上面一样在if 里面

if (my @matches = $string =~ /([a-z]+/gi) { 
    # check, process...
}

在处理之前为@matches 中捕获的内容添加适当的检查。


对 Q 中贴出的代码的评论

当您将正则表达式作为if 语句的条件时,它位于“标量”上下文中。这意味着,正如上面文档中的引用所示,如果重复调用,它将一一返回匹配项。在您的 if 中,它运行一次,因此您只能获得第一场比赛。所以$2undef

当您在foreach 下(在编辑中)拥有它时,它确实在列表条件下 - 但foreach 从语句中获取列表(因此两者都匹配)并且然后它通过它迭代。所以每次通过你手头只有一个的比赛!又不行了。

【讨论】:

    猜你喜欢
    • 2014-11-03
    • 2015-09-04
    • 2019-08-30
    • 1970-01-01
    • 2019-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多