【问题标题】:Perl: Using split but ignore quotesPerl:使用拆分但忽略引号
【发布时间】:2013-02-02 13:27:27
【问题描述】:

我正在尝试从输入字符串创建 Perl 哈希,但我遇到了原始“拆分”的问题,因为值可能包含引号。下面是一个示例输入字符串,以及我的(期望的)结果哈希:

my $command = 'CREATE:USER:TEL,12345678:MOB,444001122:Type,Whatever:ATTRIBUTES,"ID,0,MOB,123,KEY,VALUE":TIME,"08:01:59":FIN,0';

my %hash = 
  (
   CREATE     => '',
   USER       => '',
   TEL        => '12345678',
   MOB        => '444001122',
   Type       => 'Whatever',
   ATTRIBUTES => 'ID,0,MOB,123,KEY,VALUE',
   TIME       => '08:01:59',
   FIN        => '0',
  );

输入字符串为任意长度,未设置key个数。

谢谢!

-hq

【问题讨论】:

  • $hash{ATTRIB} 值中有MOB,123。他们来自哪里?
  • 不要重新发明轮子,使用 csv 解析库。
  • 您的意思是将ATTRIB 的密钥设为ATTRIBUTES
  • 我的错。我修复了ATTRIBUTES 的困惑。所以$hash{'ATTRIBUTES'}="ID,0,MOB,123,KEY,VALUE" 我也修复了$command 字符串。

标签: regex perl hash split


【解决方案1】:

使用Text::CSV。它正确处理逗号分隔的值文件。

更新

标准模块似乎无法解析您的输入格式,即使使用sep_charallow_loose_quotes 也是如此。因此,您必须自己完成繁重的工作,但您仍然可以使用 Text::CSV 来解析每个键值对:

#!/usr/bin/perl
use warnings;
use strict;
use feature qw(say);

use Data::Dumper;

use Text::CSV;

my $command = 'CREATE:USER:TEL,12345678:MOB,444001122:Type,Whatever:ATTRIBUTES,"ID,0,KEY,VALUE":TIME,"08:01:59":FIN,0';

my @fields = split /:/, $command;
my %hash;
my $csv = Text::CSV->new();

my $i = 0;
while ($i <= $#fields) {
    if (1 == $fields[$i] =~ y/"//) {
        my $j = $i;
        $fields[$i] .= ':' . $fields[$j] until 1 == $fields[++$j] =~ y/"//;
        $fields[$i] .= ':' . $fields[$j];
        splice @fields, $i + 1, $j - $i, ();
    }
    $csv->parse($fields[$i]);
    my ($key, $value) = $csv->fields;
    $hash{$key} = "$value"; # quotes turn undef to q()
    $i++;
}

print Dumper \%hash;

【讨论】:

  • 分隔符是 ':',据我所知,您无法更改此模块中的分隔符..
  • @bliof:你可以。请参阅构造函数的sep_char 选项。
  • 我的错.. 我搜索了分隔符.. +1
  • -1:如果你真的尝试这个,你会发现你不能让它处理像TIME,"08:01:59"这样的字段
  • 确实如此。这应该是一个制表符分隔的文件。
【解决方案2】:

据我所知,最明显的候选者 - Text::CSV - 无法正确处理这种格式,因此只有一个自制的正则表达式解决方案。

use strict;
use warnings;

my $command = 'CREATE:USER:TEL,12345678:MOB,444001122:Type,Whatever:ATTRIBUTES,"ID,0,KEY,VALUE":TIME,"08:01:59":FIN,0';

my %config;
for my $field ($command =~ /(?:"[^"]*"|[^:])+/g) {
  my ($key, $val) = split /,/, $field, 2;
  ($config{$key} = $val // '') =~ s/"([^"]*)"/$1/;
}

use Data::Dumper;
print Data::Dumper->Dump([\%config], ['*config']);

输出

%config = (
            'TIME' => '08:01:59',
            'MOB' => '444001122',
            'Type' => 'Whatever',
            'CREATE' => '',
            'TEL' => '12345678',
            'ATTRIBUTES' => 'ID,0,KEY,VALUE',
            'USER' => '',
            'FIN' => '0'
          );

如果你有 Perl v5.10 或更高版本,那么你有方便的(?| ... ) 正则表达式组,它允许你编写这个

use 5.010;
use warnings;

my $command = 'CREATE:USER:TEL,12345678:MOB,444001122:Type,Whatever:ATTRIBUTES,"ID,0,KEY,VALUE":TIME,"08:01:59":FIN,0';

my %config = $command =~ /(\w+) (?| , " ([^"]*) " | , ([^:"]*) | () )/gx;

use Data::Dumper;
print Data::Dumper->Dump([\%config], ['*config']);

产生与上面代码相​​同的结果。

【讨论】:

  • 这令人印象深刻。谢谢一百万。
  • 这又是非常优雅的。我不知道你可以在一行中做到这一点......我什至会做chomp($command);$command = s/;$//;。我会尝试看看是否可以将其包含在您的解决方案中。亲切的问候。
【解决方案3】:

这看起来像是 Text::ParseWords 可以处理的。 quotewords 子例程将拆分分隔符 : 上的输入,忽略引号内的分隔符。这将为我们提供基本的项目列表,在输出中首先显示为$VAR1。之后,使用正则表达式解析逗号分隔的项目就很简单了,该正则表达式将处理可选的第二次捕获以容纳空标签,例如CREATEUSER 的标签。

use strict;
use warnings;
use Data::Dumper;
use Text::ParseWords;

while (<DATA>) {
    chomp;
    my @list = quotewords(':', 0, $_);
    my %hash = map { my ($k, $v) = /([^,]+),?(.*)/; $k => $v; } @list;
    print Dumper \@list, \%hash;
}

__DATA__
CREATE:USER:TEL,12345678:MOB,444001122:Type,Whatever:ATTRIBUTES,"ID,0,KEY,VALUE":TIME,"08:01:59":FIN,0

输出:

$VAR1 = [
          'CREATE',
          'USER',
          'TEL,12345678',
          'MOB,444001122',
          'Type,Whatever',
          'ATTRIBUTES,ID,0,KEY,VALUE',
          'TIME,08:01:59',
          'FIN,0'
        ];
$VAR2 = {
          'TIME' => '08:01:59',
          'MOB' => '444001122',
          'Type' => 'Whatever',
          'CREATE' => '',
          'TEL' => '12345678',
          'ATTRIBUTES' => 'ID,0,KEY,VALUE',
          'USER' => '',
          'FIN' => '0'
        };

【讨论】:

    【解决方案4】:
    my %hash = $command =~ /([^:,]+)(?:,((?:[^:"]|"[^"]*")*))?/g;
    s/"([^"]*)"/$1/g
       for grep defined, values %hash;
    

    【讨论】:

    • 我故意未定义缺失值 (foo:bar),以便您可以将它们与空值 (foo,:bar,) 区分开来。
    • 谢谢。这很棒。您将如何更改它以允许空值(例如,不需要区分)?
    • 为什么是s/"([^"]*)"/$1/g 而不是s/^"(.*)"$/$1/
    • 因为我已经允许"foo"bar"baz",所以我需要取消引用。
    猜你喜欢
    • 2012-06-07
    • 2020-08-03
    • 2016-03-08
    • 2010-12-17
    • 2016-01-12
    • 2011-12-25
    相关资源
    最近更新 更多