Perl switch/case 对包含非捕获组 '?' 的文字正则表达式字符串失败
Perl switch/case Fails on Literal Regex String Containing Non-Capturing Group '?'
我有包含以下行的文本文件:
2/17/2018 400000098627 =2,000.0 .0994 ,387.75
3/7/2018 1)0000006043 2,000.0 .0731 ,332.78
3/26/2018 4 )0000034242 2,000.0 .1729 ,541.36
4/17/2018 2)0000008516 2,000.0 .219 ,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 = '?'
然后将 ?
替换为 $q
在 "
字符串赋值(即 $pat = "/^\s*(\S+)\s+($q:[0-9|\)| ]+)+\s+([0-9|.|,]+)\s+$/";
)中,脚本编译并运行。如果我在 switch/case
块之外分配正则表达式字符串也可以正常工作。 Perl v5.26.1 .
我的代码中也没有任何 }continue
,正如编译失败中所报告的那样,这可能是 Switch.pm
对 switch/case
代码的某种转换本机编译器阻塞。这是 Switch.pm 中的某种错误吗?即使我以完全相同的方式使用 given/when
,它也会失败。
#!/usr/local/bin/perl
use Switch;
# Edited for demo
switch($format)
{
# Format A eg:
# 2/17/2018 400000098627 =2,000.0 .0994 ,387.75
# 3/7/2018 1)0000006043 2,000.0 .0731 ,332.78
# 3/26/2018 4 )0000034242 2,000.0 .1729 ,541.36
# 4/17/2018 2)0000008516 2,000.0 .219 ,637.71
#
case /^(?:april|snow)$/i
{ # This is where the ? character breaks compilation:
$pat = '^\s*(\S+)\s+(?:[0-9|\)| ]+)+\s+\D?(\S+)\s+$';
# WORKS:
# $pat = '^\s*(\S+)\s+(' .$q. ':[0-9|\)| ]+)+\s+\D' .$q. '(\S+)\s+$';
}
# Format B
case /^(?:umberto|petro)$/i
{
$pat = '^(\S+)\s+.*Think 1\s+(\S+)\s+';
}
}
不要使用 Switch
。正如@choroba 在评论中提到的那样,Switch
使用源过滤器,这会导致神秘且难以调试的错误,正如您所说的那样。
模块的文档本身说:
In general, use given/when instead. It were introduced in perl 5.10.0. Perl 5.10.0 was released in 2007.
然而,given/when
不一定是一个好的选择,因为它是实验性的并且将来可能会改变(似乎这个特性是来自 Perl v5.28 的 almost removed;所以你肯定如果可以避免,现在不想开始使用它)。一个好的替代方法是使用 for
:
for ($format) {
if (/^(?:april|snow)$/i) {
...
}
elsif (/^(?:umberto|petro)$/i) {
...
}
}
一开始可能看起来很奇怪,但一旦你习惯了,我认为它实际上是合理的。或者,当然,您可以使用此选项的 none 并执行:
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 说:
Also, the presence of regexes specified with raw ?...? delimiters may cause mysterious errors. The workaround is to use m?...? instead.
我一开始看错了,因此尝试使用 m/../
而不是 /../
,这解决了问题。
另一个替代 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 而不是常规哈希。
我有包含以下行的文本文件:
2/17/2018 400000098627 =2,000.0 .0994 ,387.75
3/7/2018 1)0000006043 2,000.0 .0731 ,332.78
3/26/2018 4 )0000034242 2,000.0 .1729 ,541.36
4/17/2018 2)0000008516 2,000.0 .219 ,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 = '?'
然后将 ?
替换为 $q
在 "
字符串赋值(即 $pat = "/^\s*(\S+)\s+($q:[0-9|\)| ]+)+\s+([0-9|.|,]+)\s+$/";
)中,脚本编译并运行。如果我在 switch/case
块之外分配正则表达式字符串也可以正常工作。 Perl v5.26.1 .
我的代码中也没有任何 }continue
,正如编译失败中所报告的那样,这可能是 Switch.pm
对 switch/case
代码的某种转换本机编译器阻塞。这是 Switch.pm 中的某种错误吗?即使我以完全相同的方式使用 given/when
,它也会失败。
#!/usr/local/bin/perl
use Switch;
# Edited for demo
switch($format)
{
# Format A eg:
# 2/17/2018 400000098627 =2,000.0 .0994 ,387.75
# 3/7/2018 1)0000006043 2,000.0 .0731 ,332.78
# 3/26/2018 4 )0000034242 2,000.0 .1729 ,541.36
# 4/17/2018 2)0000008516 2,000.0 .219 ,637.71
#
case /^(?:april|snow)$/i
{ # This is where the ? character breaks compilation:
$pat = '^\s*(\S+)\s+(?:[0-9|\)| ]+)+\s+\D?(\S+)\s+$';
# WORKS:
# $pat = '^\s*(\S+)\s+(' .$q. ':[0-9|\)| ]+)+\s+\D' .$q. '(\S+)\s+$';
}
# Format B
case /^(?:umberto|petro)$/i
{
$pat = '^(\S+)\s+.*Think 1\s+(\S+)\s+';
}
}
不要使用 Switch
。正如@choroba 在评论中提到的那样,Switch
使用源过滤器,这会导致神秘且难以调试的错误,正如您所说的那样。
模块的文档本身说:
In general, use given/when instead. It were introduced in perl 5.10.0. Perl 5.10.0 was released in 2007.
然而,given/when
不一定是一个好的选择,因为它是实验性的并且将来可能会改变(似乎这个特性是来自 Perl v5.28 的 almost removed;所以你肯定如果可以避免,现在不想开始使用它)。一个好的替代方法是使用 for
:
for ($format) {
if (/^(?:april|snow)$/i) {
...
}
elsif (/^(?:umberto|petro)$/i) {
...
}
}
一开始可能看起来很奇怪,但一旦你习惯了,我认为它实际上是合理的。或者,当然,您可以使用此选项的 none 并执行:
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 说:
Also, the presence of regexes specified with raw ?...? delimiters may cause mysterious errors. The workaround is to use m?...? instead.
我一开始看错了,因此尝试使用 m/../
而不是 /../
,这解决了问题。
另一个替代 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 而不是常规哈希。