Perl中的正则表达式被括号搞砸了

时间:2013-06-20 06:13:36

标签: regex perl

我是perl的新手,最近遇到了以下问题。

我有一个格式为“$ num1 $ num2 $ num3 $ num4”的字符串,$ num1,$ num2,$ num3,$ num4是实数,可以是科学数字,也可以是常规格式。

现在我想使用正则表达式从字符串中提取4个数字。

$real_num = '\s*([+-]?[0-9]+\.?[0-9]*([eE][+-]?[0-9]+)?)'
while (<FP>) {
    if (/$real_num$real_num$real_num$real_num/) {
        print $1; print $2; print$3; print$4;
    }
}

如何从$ 1,$ 2,$ 3,$ 4获得$ num1,$ num2,$ num3,$ num4?由于$ real_num正则表达式中有必要的括号,所以$ 1,$ 2,$ 3,$ 4不是我现在所期望的。

感谢所有热烈的回复,非捕获组是我需要的答案!

4 个答案:

答案 0 :(得分:5)

只需在$real_num正则表达式中使用非捕获组,并将正则表达式本身设为捕获组:

$real_num = '\s*([+-]?[0-9]+\.?[0-9]*(?:[eE][+-]?[0-9]+)?)'

现在,问题是:如果有超过4个数字,/$real_num$real_num$real_num$real_num/很容易失败。可能现在情况并非如此。但是,你也应该照顾它。 拆分是更好的选择。

答案 1 :(得分:3)

如果您确定您的行包含数字,则可以使用split函数来避免该正则表达式:

while (<FP>) {
    my @numbers = split /\s+/; #<-- an array with the parsed numbers
}

如果你需要检查提取的字符串是否真的是数字,请使用Scalar::Util look_like_number。例如:

use strict;
use warnings;
use Scalar::Util qw/looks_like_number/;

while(<DATA>) {
    my @numbers = split /\s+/;
    @numbers = map { looks_like_number($_) ? $_ : undef } @numbers;
    say "@numbers";
}


__DATA__
1 2 NaN 4 -1.23
5 6 f 8 1.32e12

打印:

1 2 NaN 4 -1.23
5 6  8 1.32e12

答案 2 :(得分:1)

两个重要问题的答案将影响您是否需要使用正则表达式来匹配各种数字格式,或者您是否可以做一些更简单的事情:

  1. 您确定您的行仅包含数字,还是包含其他数据(或者某些行根本没有数字而只包含其他数据)?
  2. 您确定所有数字之间至少有一个空格是彼此分开的和/或其他数据吗?如果没有,他们是如何分开的? (例如,来自portsnap fetch的输出会生成大量数字,例如3690 .... 3700 ....带有小数点,并且根本没有用于分隔它们的空格。
  3. 如果你的行只包含数字而没有其他数据,并且数字用空格分隔,那么你甚至不需要检查结果是否是数字,而只是将行分开:

    my @numbers = split /\s+/;
    

    如果您不确定您的行是否包含数字,但您确定每个数字与其他数字或其他数据之间至少有一个空格,则下一行代码是正确提取数字的一种非常好的方法巧妙地允许Perl本身识别所有不同的合法数字格式。 (这假设您不希望将其他数据值转换为NaN。)@numbers中的结果将正确识别当前输入行中的所有数字。

    my @numbers = grep { 1*$_ eq $_ } m/(\S*\d\S*)/g;
    # we could do simply a split, but this is more efficient because when
    # non-numeric data is present, it will only perform the number
    # validation on data pieces that actually do contain at least one digit
    

    您可以通过检查表达式@numbers > 1的真值以及是否使用条件@numbers == 4等确实存在四个来确定是否存在至少一个数字

    如果您的号码相互碰撞,例如5.17e + 7-4.0e-1那么您将会遇到更困难的时间。这是你唯一需要复杂正则表达式的时候。

    注意:更新的代码更快/更好。

    注意2:由于在存储undef值时地图的工作方式很精细,因此最高投票答案存在问题。这可以通过该程序的输出来说明,当使用它从第一行数据(例如HTTP日志文件)中提取数字时。输出看起来是正确的,但是数组实际上有许多空元素,并且没有按预期找到$numbers[0]中存储的第一个数字。实际上,这是完整的输出:

    $ head -1 http | perl prog1.pl
    Use of uninitialized value $numbers[0] in join or string at prog1.pl line 8, <> line 1.
    Use of uninitialized value $numbers[1] in join or string at prog1.pl line 8, <> line 1.
    Use of uninitialized value $numbers[2] in join or string at prog1.pl line 8, <> line 1.
    Use of uninitialized value $numbers[3] in join or string at prog1.pl line 8, <> line 1.
    Use of uninitialized value $numbers[4] in join or string at prog1.pl line 8, <> line 1.
    Use of uninitialized value $numbers[5] in join or string at prog1.pl line 8, <> line 1.
    Use of uninitialized value $numbers[6] in join or string at prog1.pl line 8, <> line 1.
    Use of uninitialized value $numbers[7] in join or string at prog1.pl line 8, <> line 1.
    Use of uninitialized value $numbers[10] in join or string at prog1.pl line 8, <> line 1.
    Use of uninitialized value $numbers[11] in join or string at prog1.pl line 8, <> line 1.
    Use of uninitialized value $numbers[12] in join or string at prog1.pl line 8, <> line 1.
    Use of uninitialized value $numbers[13] in join or string at prog1.pl line 8, <> line 1.
    Use of uninitialized value $numbers[14] in join or string at prog1.pl line 8, <> line 1.
    Use of uninitialized value $numbers[15] in join or string at prog1.pl line 8, <> line 1.
    Use of uninitialized value $numbers[16] in join or string at prog1.pl line 8, <> line 1.
            200 2206
    

    (请注意,这些数字的缩进显示@numbers中存在多少个空数组元素,并且在数组转换为字符串时,在实际数字之前用空格连接在一起。)

    然而,我的解决方案在视觉上和实际数组内容中产生了正确的结果,即$ numbers [0],$ number [1]等实际上是包含在行中的第一个和第二个数字。数据文件。

    while (<>) {
    my @numbers = m/(\S*\d\S*)/g;
    @numbers = grep { $_ eq 1*$_ } @numbers;
    print "@numbers\n";
    }
    
      

    $ head -1 http | perl prog2.pl

         

    200 2206

    此外,使用慢库函数会使另一个解决方案的运行速度降低50%。在10,000行数据上运行程序时输出相同。

答案 3 :(得分:0)

我之前的回答没有解决非空格分隔数字的问题。在我看来,这需要一个单独的答案,因为输出可能与同一数据完全不同。

my $number = '([-+]?(?:\d+\.\d+|\.\d+|\d+)(?:[Ee][-+]\d+)?)';

my $type = shift;

if ($type eq 'all') {

while (<>) {
my @all_numbers = m/$number/g;
# finds legal numbers whether space separated or not
# this can be great, but it also means the string
# 120.120.120.120 (an IP address) will return
# 120.120, .120, and .120
print "@all_numbers\n";
}

} else {
while (<>) {
my @ss_numbers = grep { m/^$number$/ } split /\s+/;
# finds only space separated numbers
print "@ss_numbers\n";
}
}

<强>用法:

$ prog-jkm2.pl all < input # prints all numbers
$ prog-jkm2.pl < input # prints just space-separated numbers

OP可能需要的唯一代码:

my $number = '(-?(?:\d+\.\d+|\.\d+|\d+)(?:[Ee][-+]\d+)?)';
my @numbers = grep { m/^$number$/ } split /\s+/;

此时,$numbers[0]将是第一个数字,$numbers[1]是第二个数字等。

输出示例:

  $ head -1 http | perl prog-jkm2.pl
200 2206
  $ head -1 http | perl prog-jkm2.pl all
67.195 .114 .38 19 2011 01 20 31 -0400 1 1 1.0 200 2206 5.0