在perl中使用split()时,如何实现自己的转义序列?
我正在尝试为EDI数据格式编写一个解析器,它只是分隔文本,但分隔符是在文件顶部定义的 本质上,它是一组基于我在代码顶部读取的值的splits()。 问题是还有一个自定义的“转义字符”,表示我需要忽略以下分隔符 例如,假设*是分隔符,而?是逃跑,我在做类似的事情在perl中使用split()时,如何实现自己的转义序列?,perl,split,delimiter,escaping,Perl,Split,Delimiter,Escaping,我正在尝试为EDI数据格式编写一个解析器,它只是分隔文本,但分隔符是在文件顶部定义的 本质上,它是一组基于我在代码顶部读取的值的splits()。 问题是还有一个自定义的“转义字符”,表示我需要忽略以下分隔符 例如,假设*是分隔符,而?是逃跑,我在做类似的事情 use Data::Dumper; my $delim = "*"; my $escape = "?"; my $edi = "foo*bar*baz*aster?*isk"; my @split = split("\\" . $del
use Data::Dumper;
my $delim = "*";
my $escape = "?";
my $edi = "foo*bar*baz*aster?*isk";
my @split = split("\\" . $delim, $edi);
print Dumper(\@split);
我需要它返回“aster*isk”作为最后一个元素
我最初的想法是,在调用split()函数之前,用一些自定义映射的不可打印ascii序列替换转义字符和后面字符的每个实例,然后使用另一个regexp将它们切换回正确的值
这是可行的,但感觉像一个黑客,并会变得相当丑陋,一旦我做了所有5个不同的潜在定界符。每个分隔符也可能是一个regexp特殊字符,导致在我自己的正则表达式中进行大量转义
有没有办法避免这种情况,可能是通过向我的split()调用传递一个特殊的regexp
更新:要允许转义字符转义任何字符,包括其本身,而不仅仅是分隔符,需要不同的方法。这里有一个方法:
my @split = $edi =~ /(?:\Q$delim\E|^)((?:\Q$escape\E.|(?!\Q$delim\E).)*+)/gs;
s/\Q$escape$delim\E/$delim/g for @split;
# Process escapes to hide the following character:
$edi =~ s/\Q$escape\E(.)/sprintf '%s%d%s', $escape, ord $1, $escape/esg;
my @split = split( /\Q$delim\E/, $edi);
# Convert escape sequences into the escaped character:
s/\Q$escape\E(\d+)\Q$escape\E/chr $1/eg for @split;
*+
需要perl 5.10+。在此之前,它将是:
/(?:\Q$delim\E|^)((?>(?:\Q$escape\E.|(?!\Q$delim\E).)*))/gs
试试。如果要正确处理转义字符是字段最后一个字符的情况,这有点棘手。这里有一个方法:
my @split = $edi =~ /(?:\Q$delim\E|^)((?:\Q$escape\E.|(?!\Q$delim\E).)*+)/gs;
s/\Q$escape$delim\E/$delim/g for @split;
# Process escapes to hide the following character:
$edi =~ s/\Q$escape\E(.)/sprintf '%s%d%s', $escape, ord $1, $escape/esg;
my @split = split( /\Q$delim\E/, $edi);
# Convert escape sequences into the escaped character:
s/\Q$escape\E(\d+)\Q$escape\E/chr $1/eg for @split;
请注意,这假设转义字符和分隔符都不是数字,但它支持全部Unicode字符。这里有一个自定义函数——它比ysth的答案长,但我认为它更容易分解成有用的片段(不是一个正则表达式),它还能够处理您要求的多个分隔符
sub split_edi {
my ($in, %args) = @_;
die q/Usage: split_edi($input, escape => "#", delims => [ ... ]) /
unless defined $in and defined $args{escape} and defined $args{delims};
my $escape = quotemeta $args{escape};
my $delims = join '|', map quotemeta, @{ $args{delims} };
my ($cur, @ret);
while ($in !~ /\G\z/cg) {
if ($in =~ /\G$escape(.)/mcg) {
$cur .= $1;
} elsif ($in =~ /\G(?:$delims)/cg) {
push @ret, $cur;
$cur = '';
} elsif ($in =~ /\G((?:(?!$delims|$escape).)+)/mcg) {
$cur .= $1;
} else {
die "hobbs can't write parsers";
}
}
push @ret, $cur if defined $cur;
@ret;
}
第一行是参数解析,根据需要反斜杠转义字符,并构建一个匹配任何分隔符的正则表达式片段
然后是匹配循环:
- 如果我们找到转义,跳过它并捕获以下字符作为输出的文字位,而不是专门处理它
- 如果我们找到任何分隔符,则启动一个新记录
- 否则,捕获字符直到下一个转义符或分隔符
- 当我们到达绳子的末端时停止
如果输入包含
“??*”
,该怎么办?这会使星号转义还是不转义?据我所知,这应该只是作为一个文字来阅读?一个非转义星号猜测转义将只在分隔符上使用,而不会在其他字符上使用,并且分隔符将始终不同于转义。您将文本分隔到字段中,基本上是一个xSV文件。在我的问题中,使用Text::CSV和与转义符和分隔符匹配的选项charsI可能没有足够清楚地说明这一点,但问题是有多个嵌套分隔符,我认为Text::CSV一次只能处理一个。例如:abc | foo~bar^ alpha~beta | def将是['abc'、['foo'、'bar'、['alpha'、'beta']、'def'];这无法正确处理转义转义字符。e、 例如,它不拆分'foo???*bar'
,但应该拆分为'foo?'
和'bar'
;我的$csv=Text::csv->new({escape\u char=>'?',sep\u char=>'*',allow\u loose\u escapes=>1})$csv->parse($edi);打印转储程序($csv->字段);
say for split_edi("foo*bar;baz*aster?*isk", delims => [qw(* ;)], escape => "?");
foo
bar
baz
aster*isk