【问题标题】:Why do I get the first capture group only?为什么我只得到第一个捕获组?
【发布时间】:2020-10-22 17:29:44
【问题描述】:

https://stackoverflow.com/a/2304626/6607497https://stackoverflow.com/a/37004214/6607497 没有帮助我)

在 Linux 中分析/proc/stat 的问题 我开始编写一个小实用程序,但我无法按照我想要的方式获取捕获组。 代码如下:

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

if (open(my $fh, '<', my $file = '/proc/stat')) {
    while (<$fh>) {
        if (my ($cpu, @vals) = /^cpu(\d*)(?:\s+(\d+))+$/) {
            print "$cpu $#vals\n";
        }
    }
    close($fh);
} else {
    die "$file: $!\n";
}

例如,通过这些输入行,我得到了输出:

> cat /proc/stat
cpu  2709779 13999 551920 11622773 135610 0 194680 0 0 0
cpu0 677679 3082 124900 11507188 134042 0 164081 0 0 0
cpu1 775182 3866 147044 38910 135 0 15026 0 0 0
cpu2 704411 3024 143057 37674 1272 0 8403 0 0 0
cpu3 552506 4025 136918 38999 160 0 7169 0 0 0
intr 176332106  ...
 0
0 0
1 0
2 0
3 0

所以匹配确实有效,但我没有将捕获组放入@vals(perls 5.18.2 和 5.26.1)。

【问题讨论】:

  • 总结到目前为止的所有解决方案(我自己的除外):看来你不能只用一个正则表达式来做到这一点;相反,您必须使用两步过程(例如匹配,然后拆分)。

标签: regex perl regex-group


【解决方案1】:

仅捕获单个模式中的最后一个重复匹配项。

相反,可以只拆分行,然后检查 -- 并调整 -- 第一个字段

while (<$fh>) {
    my ($cpu, @vals) = split;
    next if not $cpu =~ s/^cpu//;
    print "$cpu $#vals\n";
}

如果split 的返回的第一个元素不是以cpu 开头,则正则表达式替换失败,因此该行被跳过。否则,您将得到cpu 后面的数字(或空字符串),如 OP 中所示。

或者,可以使用您处理的行的特定结构

while (<$fh>) {
    if (my ($cpu, @vals) = map { split } /^cpu([0-9]*) \s+ (.*)/x) { 
        print "$cpu $#vals\n";
    }
}

正则表达式返回两个项目,每个项目都是map 中的split,除了第一个项目按原样传递给$cpu(是一个数字或一个空字符串),而另一个产生数字。

这两种方法都会在我的测试中产生所需的输出。

【讨论】:

  • 有趣的变体,但比我的原始代码恕我直言更难理解。 /x有什么用,为什么不用`/^cpu([0-9]*) (.*)$/``
  • @U.Windl “原始代码” ...实际上并不能满足您的需要?这样做。 /x 仅允许在内部(以及 cmets 和换行符)使用文字空格,以提高可读性。这不是必需的。我删除了$,因为它不需要,.* 无论如何都匹配到最后。
  • @U.Windl "比我的原始代码更难理解,恕我直言" --- 是的,完全同意,第二个选项有点棘手。我喜欢它作为一种古玩方式来做到这一点。我推荐第一个。
  • 在第一个代码示例中,您始终测试 $cpu 是预期值。由于您要对每一行进行测试,因此您可以先执行此操作,然后仅在成功时才拆分。
  • 假设一开始的匹配比拆分效率高,可以先next unless /^cpu/;,再split
【解决方案2】:

按照示例输入,while 循环中的以下内容应该可以工作。

if (/^cpu(\d*)/) {
    my $cpu = $1;
    my (@vals) = /(?:\s+(\d+))+/g;
    print "$cpu $#vals\n";
}

【讨论】:

【解决方案3】:

Learning Perl 的练习中,我们陈述了一个用两个简单的正则表达式很容易解决但用一个很难解决的问题(但是在Mastering Perl 中,我拿出了大手笔)。我们不会告诉人们这一点,因为我们想强调尝试在单个正则表达式中编写所有内容的自然行为。其他答案中的一些扭曲让我想起了这一点,我不想保留其中任何一个。

首先,存在仅处理有趣行的问题。然后,一旦我们有了那条线,抓住所有的数字。将该问题陈述翻译成代码非常简单明了。这里没有杂技,因为断言和锚点完成了大部分工作:

use v5.10;

while( <DATA> ) {
    next unless /\A cpu(\d*) \s /ax;
    my $cpu = $1;
    my @values = / \b (\d+) \b /agx;
    say "$cpu " . @values;
    }

__END__
cpu  2709779 13999 551920 11622773 135610 0 194680 0 0 0
cpu0 677679 3082 124900 11507188 134042 0 164081 0 0 0
cpu1 775182 3866 147044 38910 135 0 15026 0 0 0
cpu2 704411 3024 143057 37674 1272 0 8403 0 0 0
cpu3 552506 4025 136918 38999 160 0 7169 0 0 0
intr 176332106  ...

请注意,OP 仍然必须决定如何处理没有尾随数字的 cpu 案例。不知道你想对空字符串做什么。

【讨论】:

    【解决方案4】:

    Perl 的正则表达式引擎只会记住重复表达式中的 last 捕获组。如果您想在单独的捕获组中捕获每个数字,那么一种选择是使用显式正则表达式模式:

    if (open(my $fh, '<', my $file = '/proc/stat')) {
        while (<$fh>) {
            if (my ($cpu, @vals) = /^cpu(\d*)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)$/) {
                print "$cpu $#vals\n";
            }
        }
        close($fh);
    } else {
        die "$file: $!\n";
    }
    

    【讨论】:

    • 问题是`cpu`的数字数量随时间变化,可能会添加更多值。我觉得这是 perl 的不足,因为整行都匹配。
    • 这个怎么样:使用您当前的正则表达式断言每一行匹配,然后使用字符串拆分将每个数字项隔离为数组中的单独元素?
    【解决方案5】:

    更换

        while (<$fh>) {
            if (my ($cpu, @vals) = /^cpu(\d*)(?:\s+(\d+))+$/) {
    

        while (<$fh>) {
            my @vals;
            if (my ($cpu) = /^cpu(\d*)(?:\s+(\d+)(?{ push(@vals, $^N) }))+$/) {
    

    做我想做的事(需要 perl 5.8 或更高版本)。

    【讨论】:

    • 这对下一个不得不看的程序员来说是一件非常残酷的事情。当手铲可以完成工作时,您将获得 30 吨重的挖掘机。
    • 我有点不同意:我匹配以cpu\d* 开头的行,然后将所有数字添加到列表中(推入数组)。当然,您必须了解语法的作用。诚然,我没有检查正则表达式的性能。
    【解决方案6】:

    他是我的榜样。我想我会添加它,因为我喜欢简单的代码。它还允许“cpu7”没有尾随数字。

    #!/usr/bin/perl
    use strict;
    use warnings;
    
    my $file = "/proc/stat";
    open(my $fh, "<", $file) or die "$file: $!\n";
    while (<$fh>) 
    {
      if ( /^cpu(\d+)(\s+)?(.*)$/ ) 
      {
        my $cpu = $1; 
        my $vals = scalar split( /\s+/, $3 ) ;
        print "$cpu $vals\n";
      }
    }
    close($fh);
    

    【讨论】:

    • 原代码尝试将cpu#之后的数字收集为数组;您的代码只需将其添加为标量。
    【解决方案7】:

    只需添加到Tim's answer

    您可以使用一组捕获多个值(使用 g 修饰符),但是您必须拆分语句。

        if (my ($cpu) = /^cpu(\d*)(?:\s+(\d+))+$/) {
            my @vals= /(?:\s+(\d+))/g;
            print "$cpu $#vals\n";
        }
    

    【讨论】:

    • 这基本上就是@Tim Biegeleisen 在他对stackoverflow.com/a/62690982/6607497 的评论中所说的。
    • 他有固定数量的捕获组。您的解决方案是一种高效(但有点复杂)的解决方案,没有固定数量的捕获组。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-14
    • 1970-01-01
    • 2014-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多