Perl使用多行对csv中的列进行分组

时间:2017-09-20 07:30:40

标签: perl csv hash

我有一个带多线的csv(用,分隔)。 csv有4列,其中前3列包含多行文本,而group by发生在最后一列。

输入csv内容:/tmp/test.tmp.csv

"Total Sections",ota,4!n,01
"Input History",80,"HHMM28!c1!a[4!a]
6X
9X]",1
"T (MR)",17t,(MTR),02
"Input History",80,"HHMM28!c1!a[4!a]
6X
9X]",2
Reference,:4!t/1c,:(Text1)/(Text2),30
Reference,:4!t/1c,:(Text1)/(Text2),32

以上csv由6条记录组成,记录2和4由多条线组成。

预期输出(分组为空格):

"Total Sections",ota,4!n,01
"Input History",80,"HHMM28!c1!a[4!a]
6X
9X]",1 2
"T (MR)",17t,(MTR),02
Reference,:4!t/1c,:(Text1)/(Text2),30 32

我的 perl脚本(读取哈希中的前三个字段作为键,哈希中的最后一个字段作为值,使用连接打印到csv):

#!/usr/bin/perl -w

use strict;
use warnings;
use Text::CSV;

my %hash;
my @array;
my $in_qfn = "/tmp/test.tmp.csv";
my $out_qfn = "/tmp/test.out.tmp.csv";

# Li: Parsing multilines in csv
my $parser = Text::CSV->new({
   binary => 1,
   auto_diag => 1,
   sep_char => ','
});

# Li: output multilines to csv
my $csvo = Text::CSV->new({
   binary => 1,
   eol => "\r\n",
   sep_char => ','
});

open(my $data, '<:encoding(utf8)', $in_qfn) or die "Could not open $in_qfn: $!\n";
open(my $sts, '>:encoding(utf8)', $out_qfn) or die "Could not write $out_qfn: $!\n";

while (my $fields = $parser -> getline($data)) {
   my $fz = $fields->[0];
   my $fo = $fields->[1];
   my $ft = $fields->[2];
   my $fth = $fields->[3];
   my @flds = ($fz, $fo, $ft, $fth);

   # Li: push the first 3 columns as key and the last column as value
   push(@{$hash{@flds[0..2]} }, $flds[3]);
}

# Li: print to output csv without join yet
for my $k (sort keys %hash) {
   my @fldsAll = ($k, @{ $hash{$k}});
   print("###LI### 1: key: $k, value: @fldsAll\n");
   $csvo -> print($sts, \@fldsAll);
}

然而,脚本不能很好地工作,哈希键由于多行和可能的特殊字符而丢失,并且到处都没有双引号。

缺陷输出

(MTR),02
4!n,01
:(Text1)/(Text2),30,32
"HHMM28!c1!a[4!a]
6X
9X]",1,2

有关如何修复它的任何想法?或者全新的perl解决方案也很受欢迎。

1 个答案:

答案 0 :(得分:1)

您不能像使用数组那样使用数组作为哈希键,因为它不使用所有值,而只使用最后一个。

并且您不能使用对数组的引用,因为键与数组中的值无关。以此示例代码为例......

for($i=0;$i<3;$i++)
  {
  my @a=(1,2,3);
  $hash{\@a}=10;
  }

因为@a的范围是循环的本地范围,所以最终会得到3个密钥。如果将my @a;放在循环之外,最后会得到1个密钥。您可以更改数组的内容,它对密钥没有任何影响。

相反,您需要做的是将join数组放入一个字符串中。

push(@{$hash{join("\t",@flds[0..2])} }, $flds[3]);

我使用了一个标签,但是任何3列中都不会出现的任何字符串都是您想要的,因此如果需要,您可以split稍后获取原始值回来。