我想通读一个文本文件,并将每行划分为以下三个变量。每个变量都必须定义,尽管它可能等于空字符串。
$a1code
:所有字符,不包括第一个非转义的百分号。如果没有未转义的百分号,这就是整行。正如我们在下面的例子中看到的,这也可以是一行中的空字符串,其中以下两个变量是非空的。$a2boundary
:第一个非转义的百分号(如果有)。$a3cmnt
:第一个非转义百分号(如果有)之后的任何字符。
下面的脚本完成了这一点,但需要几行代码、两个哈希和一个复合正则表达式,即由|
组合的2个正则表达式。复合句似乎是必要的,因为第一个子句
(?<a1code>.*?)(?<a2boundary>(?<!\)%)(?<a3cmnt>.*)
不匹配没有注释的纯代码行。是否有更优雅的方法,使用单个正则表达式和更少的步骤?特别是,是否有一种方法可以免除%match
哈希,并且以某种方式在一个步骤中用所有三个变量填充%+
哈希?
#!/usr/bin/env perl
use strict; use warnings;
print join('', 'perl ', $^V, "n",);
use Data::Dumper qw(Dumper); $Data::Dumper::Sortkeys = 1;
my $count=0;
while(<DATA>)
{
$count++;
print "$countt";
chomp;
my %match=(
a2boundary=>'',
a3cmnt=>'',
);
print "|$_|n";
if($_=~/^(?<a1code>.*?)(?<a2boundary>(?<!\)%)(?<a3cmnt>.*)|(?<a1code>.*)/)
{
print "from regex:n";
print Dumper %+;
%match=(%match,%+,);
}
else
{
die "no match? coding error, should never get here";
}
if(scalar keys %+ != scalar keys %match)
{
print "from multiple lines of code:n";
print Dumper %match;
}
print "------------------------------------------n";
}
__DATA__
This is 100% text and below you find an empty line.
abba 5% %comment 9% %Borgia
%all comment
%
结果:
perl v5.34.0
1 |This is 100% text and below you find an empty line. |
from regex:
$VAR1 = {
'a1code' => 'This is 100\% text and below you find an empty line. '
};
from multiple lines of code:
$VAR1 = {
'a1code' => 'This is 100\% text and below you find an empty line. ',
'a2boundary' => '',
'a3cmnt' => ''
};
------------------------------------------
2 ||
from regex:
$VAR1 = {
'a1code' => ''
};
from multiple lines of code:
$VAR1 = {
'a1code' => '',
'a2boundary' => '',
'a3cmnt' => ''
};
------------------------------------------
3 |abba 5% %comment 9% %Borgia|
from regex:
$VAR1 = {
'a1code' => 'abba 5\% ',
'a2boundary' => '%',
'a3cmnt' => 'comment 9\% %Borgia'
};
------------------------------------------
4 |%all comment|
from regex:
$VAR1 = {
'a1code' => '',
'a2boundary' => '%',
'a3cmnt' => 'all comment'
};
------------------------------------------
5 |%|
from regex:
$VAR1 = {
'a1code' => '',
'a2boundary' => '%',
'a3cmnt' => ''
};
------------------------------------------
您可以使用以下命令:
my ($a1code, $a2boundary, $a3cmnt) =
/
^
( (?: [^\%]+ | \. )* )
(?: (%) (.*) )?
z
/sx;
它不认为%
在abc\%def
中转义了,因为前面的已经转义了。
它不需要回溯,并且总是匹配。
$a1code
总是一个字符串。它可以是零字符长(当输入是空字符串并且%
是第一个字符时),也可以是整个输入字符串(当没有未转义的%
时)。
然而,$a2boundary
和$a3cmnt
只有在存在未转义的%
时才会被定义。也就是说,$a2boundary
等价于defined($a3cmnt) ? '%' : undef
。
:[^\%]+
匹配除和
%
以外的非转义字符。\.
匹配转义字符。因此,(?: [^\%]+ | \. )*
为我们提供前缀,如果没有未转义的%
,则为整个字符串。
在this\%string
这样的情况下,百分号前的反斜杠本身是转义的呢?
考虑这样的事情,它不是尝试使用正则表达式将字符串分成三组,而是使用一组来查找应该将字符串分割的位置,并使用substr
来进行实际的分割:
#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;
sub splitter {
my $line = shift;
if ($line =~ /
# Match either
(?<!\)% # A % not preceded by a backslash
| # or
(?<=[^\])(?:\\)+K% # Any even number of backslashes followed by a %
/x) {
return (substr($line, 0, $-[0]), '%', substr($line, $+[0]));
} else {
return ($line, '', '');
}
}
while (<DATA>) {
chomp;
# Assign to an array instead of individual scalars for demonstration purposes
my @vals = splitter $_;
print Dumper(@vals);
}
__DATA__
This is 100% text and below you find an empty line.
abba 5% %comment 9% %Borgia
%all comment
%
a tricky\%test % case
another \%one % to mess with you