在perl中使用split()时如何实现自己的转义序列?

时间:2010-08-27 21:45:43

标签: perl split delimiter escaping

我正在尝试为EDI数据格式编写一个解析器,它只是分隔文本,但是在文件顶部定义了分隔符。

基本上,它是基于我在代码顶部读取的值的一堆splits()。 问题是还有一个自定义的“转义字符”,表示我需要忽略以下分隔符。

例如假设*是分隔符并且?是逃避,我正在做类似

的事情
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序列替换转义字符和后续字符的每个实例,然后用另一个正则表达式将它们切换回右边值。

这是可行的,但感觉就像一个黑客,一旦我为所有5个不同的潜在分隔符做了它将变得非常难看。每个分隔符都可能是一个regexp特殊字符,导致我自己的正则表达式中有很多转义。

有没有办法避免这种情况,可能会将一个特殊的正则表达式传递给我的split()调用?

4 个答案:

答案 0 :(得分:7)

my @split = split( /(?<!\Q$escape\E)\Q$delim\E/, $edi);

将为您执行拆分,但您必须单独删除转义字符:

s/\Q$escape$delim\E/$delim/g for @split;

更新:允许转义字符转义任何字符,包括其自身,而不仅仅是分隔符需要不同的方法。这是一种方式:

my @split = $edi =~ /(?:\Q$delim\E|^)((?:\Q$escape\E.|(?!\Q$delim\E).)*+)/gs;
s/\Q$escape$delim\E/$delim/g for @split;

*+需要perl 5.10+。在此之前,它将是:

/(?:\Q$delim\E|^)((?>(?:\Q$escape\E.|(?!\Q$delim\E).)*))/gs

答案 1 :(得分:2)

尝试Text::CSV

答案 2 :(得分:1)

如果要处理转义字符是字段的最后一个字符的情况,这有点棘手。这是一种方式:

# 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字符。

答案 3 :(得分:1)

这是一个自定义函数 - 它比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;
}

第一行是参数解析,根据需要反斜杠转义字符串,并构建一个匹配任何分隔符的正则表达式片段。

然后是匹配循环:

  • 如果我们找到了转义符,请跳过它并将以下字符捕获为输出的文字位而不是专门处理它。
  • 如果我们找到任何分隔符,请开始新记录。
  • 否则,捕获字符直到下一个转义符或分隔符。
  • 当我们到达字符串结尾时停止。

这非常简单,但仍然具有非常可靠的性能。就像ysth的正则表达式解决方案一样,它是棘手的 - 它不会试图不必要地回溯。如果转义或任何分隔符是多字符的,则无法保证正确性,尽管我实际上认为它非常正确:)

say for split_edi("foo*bar;baz*aster?*isk", delims => [qw(* ;)], escape => "?");
foo
bar
baz
aster*isk