【问题标题】:Parse report in blocks to CSV将块中的报告解析为 CSV
【发布时间】:2017-09-01 20:41:47
【问题描述】:

我有大量的数据转储在大量的数据结构如下

Key1:.............. Value
Key2:.............. Other value
Key3:.............. Maybe another value yet

Key1:.............. Different value
Key3:.............. Invaluable
Key5:.............. Has no value at all

我想转换成类似的东西:

Key1,Key2,Key3,Key5
Value,Other value,Maybe another value yet,
Different value,,Invaluable,Has no value at all

我的意思是:

  • 生成所有键的集合
  • 使用所有键生成标题行
  • 将所有值映射到它们正确的“列”(请注意,在此示例中,我没有“Key4”,并且 Key3/Key5 互换了)
  • 可能在 Perl 中,因为它更容易在各种环境中使用。

但我不确定这种格式是否不寻常,或者是否有工具已经这样做了。

【问题讨论】:

  • 您的问题到底是什么?转换的最佳格式是读取数据的“东西”想要的任何东西!
  • 这看起来像一个简单的转座。文件相当小吗?您在编写解决方案时遇到了什么问题?
  • 这些点真的在数据中吗?
  • 您是否在 CSV 输出的右栏中找到了“无价之宝”?如果是这样,它是如何正确的? (双逗号应该在“无价之宝”之前,而不是之后——我认为。)
  • 当您说“相当大量的数据”时,它是否如此庞大以至于无法全部放入处理文件的计算机的内存中?这是一个速度和易用性与内存使用的对比...

标签: perl unix data-dumper


【解决方案1】:

使用哈希和Text::CSV_XS 模块相当容易:

use strict;
use warnings;

use Text::CSV_XS;

my @rows;
my %headers;

{
    local $/ = "";

    while (<DATA>) {
        chomp;
        my %record;

        for my $line (split(/\n/)) {
            next unless $line =~ /^([^:]+):\.+\s(.+)/;
            $record{$1} = $2;
            $headers{$1} = $1;
        }

        push(@rows, \%record);
    }
}

unshift(@rows, \%headers);

my $csv = Text::CSV_XS->new({binary => 1, auto_diag => 1, eol => $/});
$csv->column_names(sort(keys(%headers)));

for my $row_ref (@rows) {
    $csv->print_hr(*STDOUT, $row_ref);
}

__DATA__
Key1:.............. Value
Key2:.............. Other value
Key3:.............. Maybe another value yet

Key1:.............. Different value
Key3:.............. Invaluable
Key5:.............. Has no value at all

输出:

Key1,Key2,Key3,Key5
Value,"Other value","Maybe another value yet",
"Different value",,Invaluable,"Has no value at all"

【讨论】:

    【解决方案2】:

    如果您的 CSV 格式“复杂” - 例如它包含逗号等 - 然后使用 Text::CSV 模块之一。但如果不是这样 - 通常情况下 - 我倾向于只使用 splitjoin

    在您的场景中有用的是,您可以使用正则表达式轻松地在记录中映射键值。然后使用哈希切片输出:

    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    
    #set paragraph mode - records are blank line separated. 
    local $/ = "";
    
    my @rows;
    my %seen_header;
    
    #read STDIN or files on command line, just like sed/grep 
    while ( <> ) {
       #multi - line pattern, that matches all the key-value pairs,
       #and then inserts them into a hash. 
       my %this_row = m/^(\w+):\.+ (.*)$/gm;
       push ( @rows, \%this_row ); 
    
       #add the keys we've seen to a hash, so we 'know' what we've seen. 
       $seen_header{$_}++ for keys %this_row; 
    }
    
    #extract the keys, make them unique and ordered. 
    #could set this by hand if you prefer.    
    my @header = sort keys %seen_header;
    
    #print the header row
    print join ",", @header, "\n";
    
    #iterate the rows
    foreach my $row ( @rows ) {
        #use a hash slice to select the values matching @header.
        #the map is so any undefined values (missing keys) don't report errors, they
        #just return blank fields. 
        print join ",", map { $_ // '' } @{$row}{@header},"\n";
    }
    

    这为您提供示例输入,产生:

    Key1,Key2,Key3,Key5,
    Value,Other value,Maybe another value yet,,
    Different value,,Invaluable,Has no value at all,
    

    如果你想变得非常聪明,那么循环的大部分初始构建都可以通过以下方式完成:

    my @rows = map { { m/^(\w+):\.+ (.*)$/gm } } <>;
    

    那么问题是 - 你仍然需要建立 'headers' 数组,这意味着有点复杂:

    $seen_header{$_}++ for map { keys %$_ } @rows;
    

    它有效,但我认为它对正在发生的事情不太清楚。

    但是,您的问题的核心可能是文件大小 - 这就是您有一点问题的地方,因为您需要读取文件两次 - 第一次是找出整个文件中存在哪些标题,然后第二次迭代和打印:

    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    
    open ( my $input, '<', 'your_file.txt') or die $!;
    local $/ = "";
    
    my %seen_header;
    while ( <$input> ) { 
        $seen_header{$_}++ for m/^(\w+):/gm; 
    }  
    
    my @header = sort keys %seen_header; 
    
    #return to the start of file:
    seek ( $input, 0, 0 ); 
    
    while ( <$input> )  {
       my %this_row = m/^(\w+):\.+ (.*)$/gm;
       print join ",", map { $_ // '' } @{$this_row}{@header},"\n";
    }
    

    这会稍微慢一些,因为它必须读取文件两次。但它不会使用几乎一样多的内存占用,因为它没有将整个文件保存在内存中。

    除非您事先知道所有密钥,并且您可以定义它们,否则您必须读取文件两次。

    【讨论】:

    • 完全同意,我也不会有问题,读取文件两次或使用大量内存,我的主要问题是需要先读取文件才能获取密钥,然后他们制作一些自定义每个键集的代码。我只是有点松懈,试图预先完成密钥的收集,然后他们正确地倾倒它们。通过研究您的代码,我学到了很多东西,谢谢!
    【解决方案3】:

    这似乎适用于您提供的数据

    use strict;
    use warnings 'all';
    
    my %data;
    
    while ( <> ) {
    
        next unless /^(\w+):\W*(.*\S)/;
    
        push @{ $data{$1} }, $2;
    }
    
    use Data::Dump;
    dd \%data;
    

    输出

    {
      Key1 => ["Value", "Different value"],
      Key2 => ["Other value"],
      Key3 => ["Maybe another value yet", "Invaluable"],
      Key5 => ["Has no value at all"],
    }
    

    【讨论】:

    • 这看起来不太像所需的输出。当然,只有 2 条记录,要弄清楚发生了什么并不难,但即使只有 12 条具有不同属性列表的记录,您显示的格式也无法管理。
    猜你喜欢
    • 2019-11-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多