Perl开关/大小写在包含非捕获组'?'的正则表达式字符串上失败

时间:2019-04-14 19:29:56

标签: regex perl regex-group

我的文本文件包含以下行:

2/17/2018 400000098627 =2,000.0 $2.0994 $4,387.75
3/7/2018 1)0000006043 2,000.0 $2.0731 $4,332.78
3/26/2018 4 )0000034242 2,000.0 $2.1729 $4,541.36
4/17/2018 2)0000008516 2,000.0 $2.219 $4,637.71

我将它们与/^\s*(\S+)\s+(?:[0-9|\)| ]+)+\s+([0-9|.|,]+)\s+\$/进行匹配,但是我也有一些文件的行格式完全不同,并且与其他正则表达式匹配。当我打开文件时,我确定哪种格式并在switch / case块中分配$pat = '<regex-string>';

$pat = '/^\s*(\S+)\s+(?:[0-9|\)| ]+)+\s+([0-9|.|,]+)\s+\$/'

但是在日期之后且在第一个货币金额导致Perl解释器无法编译脚本之前,重复使用?字符来引入我用来匹配的非捕获组,从而导致脚本无法编译,并报告中止:

syntax error at ./report-dates-amounts line 28, near "}continue "

如果我删除?字符,或将?替换为\?转义字符,或者先分配$q = '?',然后将?替换为{{1} }在$q字符串分配(即")中编译并运行脚本。如果我在$pat = "/^\s*(\S+)\s+($q:[0-9|\)| ]+)+\s+([0-9|.|,]+)\s+\$/";块外分配了正则表达式字符串,那么它也可以正常工作。 Perl v5.26.1。

我的代码中也没有任何switch/case,正如编译失败所报告的那样,可能是}continueswitch/case代码转换为本地代码的某种形式。编译器阻塞。这是Switch.pm中的某种错误吗?即使我以完全相同的方式使用Switch.pm也会失败。

given/when

2 个答案:

答案 0 :(得分:4)

请勿使用Switch。正如@choroba在评论中所提到的那样,Switch使用了源过滤器,这导致了您所构造的神秘且难以调试的错误。

该模块的文档本身说:

  

通常,使用给定/时间代替。它是在perl 5.10.0中引入的。 Perl 5.10.0于2007年发布。

但是,given/when不一定是一个好的选择,因为它是实验性的,并且将来可能会发生变化(此功能似乎是almost removed来自Perl v5.28;因此,您肯定不会这样做)如果您可以避免的话,现在就想开始使用它)。一个不错的选择是使用for

for ($format) {
    if (/^(?:april|snow)$/i) {
       ...
    } 
    elsif (/^(?:umberto|petro)$/i) {
       ...
    }
}

乍一看可能很奇怪,但是一旦习惯了,我认为这实际上是合理的。或者,当然,您可以不使用任何这些选项,而只需执行以下操作:

sub pattern_from_format {
    my $format = shift;

    if ($format =~ /^(?:april|snow)$/i) {
       return qr/^\s*(\S+)\s+(?:[0-9|\)| ]+)+\s+\D?(\S+)\s+\$/;
    } 
    elsif ($format =~ /^(?:umberto|petro)$/i) {
        return qr/^(\S+)\s+.*Think 1\s+(\S+)\s+/;
    }
    # Some error handling here maybe
 }

如果由于某些原因您仍想使用Switch:请使用m/.../而不是/.../

我不知道为什么会发生此错误,但是documentation说:

  

此外,是否存在用原始?...?指定的正则表达式。分隔符可能会导致神秘的错误。解决方法是使用m?...?代替。

我一开始就读错了,因此尝试使用m/../而不是/../来解决此问题。

答案 1 :(得分:2)

另一个代替if / elsif链的选择是遍历散列,该散列将您的正则表达式映射到应分配给$pat的值:

#!/usr/local/bin/perl

my %switch = (
  '^(?:april|snow)$'    => '^\s*(\S+)\s+(?:[0-9|\)| ]+)+\s+\D?(\S+)\s+\$',
  '^(?:umberto|petro)$' => '^(\S+)\s+.*Think 1\s+(\S+)\s+',
);

for my $re (keys %switch) {
  if ($format =~ /$re/i) {
    $pat = $switch{$re};
    last;
  }
}

对于更一般的情况(即,如果您要做的不仅仅是将标量分配给字符串),您可以使用相同的通用技术,但将coderefs用作哈希值,从而允许它执行基于匹配的任意sub

此方法可以涵盖通常与switch / case构造相关的相当广泛的功能,但请注意,由于条件是从哈希键中提取的,因此可以以随机顺序进行评估。如果您的数据可以匹配多个条件,则需要采取额外的预防措施来进行处理,例如以条件的正确顺序并行排列数组,或使用Tie::IxHash代替常规哈希。