【问题标题】:Regex in Perl messed up by bracketPerl 中的正则表达式被括号弄乱了
【发布时间】:2013-06-20 06:13:36
【问题描述】:

我是 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 个数字是如何分开的?您可以拆分并遍历它们
  • 当你说括号(即[])时,你的意思是括号()吗?无论如何,这不是“必要的”,您可以使括号不被捕获,如Rohit's answer 中所述。

标签: regex perl


【解决方案1】:

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

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

现在,问题是:/$real_num$real_num$real_num$real_num/ 很容易失败,如果有超过 4 个数字。可能现在情况并非如此。但是,您也应该注意这一点。 拆分会是更好的选择。

【讨论】:

  • 点击“勾号”图标,然后! :-D
  • 除了你提到的问题之外,这在像 0.3 这样简单的输入上也会失败。例如,“9 2. .3 6 7 8”将返回 3 6 7 8。
  • 谢谢迈尔斯,我会注意的 :)
【解决方案2】:

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

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

如果您需要检查提取的字符串是否真的是数字,请使用Scalar::Utillooks_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

【讨论】:

  • 为什么没有人意识到即使存在一个非数字数据,这段代码也会产生大量关于在连接或字符串中使用未初始化值的警告?您的回答原则上还不错,但您至少应该知道使用 grep 而不是 map 来完成这样的工作。
  • 我正在运行 perl 5.18 并且您说的警告没有出现。无论如何,这段代码试图展示一个想法;在这种情况下,更好的实现的具体细节不是重点。
  • 实际上,即使您在自己的 DATA 上运行程序,也会出现一个警告。在第二行中,f 导致出现警告。只需使用 grep 而不是 map,您的解决方案就可以正常工作,即 grep { looks_like_number($_) } @numbers,但由于使用较慢的looks_like_number 库子例程,它仍然会更慢。
  • 正如我所说,我的环境中没有警告,perl 5.18.0,它是你的什么?我不同意使用 grep,可能需要用 undef 标记,并且数组中有固定数量的项。
  • 使用 grep 是有意义的,因为第一个数字在 $numbers[0] 中,第二个在 $numbers[1] 中,等等。使用 map 是不切实际的,因为 OP 不能引用数字无法知道它们是否存储在 $numbers[0] 或 $numbers[5] 或数组的任何其他随机部分中。
【解决方案3】:

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

  1. 您确定您的行仅包含数字还是它们还包含其他数据(或者可能某些行根本没有数字而只有其他数据)?
  2. 您确定所有数字之间和/或其他数据之间至少用一个空格分隔吗?如果不是,它们是如何分开的? (例如,portsnap fetch 的输出会生成许多类似 3690....3700.... 的数字,带有小数点,根本没有用于分隔它们的空格。

如果你的行只包含数字而没有其他数据,并且数字用空格分隔,那么你甚至不需要检查结果是否为数字,而只需将行分开:

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 &gt; 1 的真值来确定是否存在至少一个数字,以及通过使用条件@numbers == 4 等来确定是否恰好存在四个。

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

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

注意 2:由于 map 在存储 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

此外,使用 slow 库函数会使其他解决方案的运行速度降低 50%。在 10,000 行数据上运行程序时,输出在其他方面是相同的。

【讨论】:

    【解决方案4】:

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

    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
    

    【讨论】:

      猜你喜欢
      • 2012-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-10
      • 1970-01-01
      • 2012-10-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多