如何在Perl中处理可变数量的输入行?

时间:2017-03-23 15:35:20

标签: arrays perl if-statement hash

我正在使用Perl脚本,我需要使用MTA的日志。以下是我想要使用的查询。

sh-3.2# cat /var/log/pmta/File_name-2017-03-23*|egrep 'email.domain.com'|cut -d, -f6|cut -d- -f1|sort|uniq -c

此查询的输出存储在$case8Q1

   310 blk
  1279 hrd
    87 sft
144056 success
    18 unk

正如您所看到的,查询给出了5个值,但情况并非总是如此。它也可以这样给出。因此行数可能每次都变化(2或3或4或最大5)

   310 blk
144056 success
    18 unk

下面是给出错误结果的示例代码

sub get_stats {

    $case8Q1 =~ s/^\s+//;

    @case8Q1_split = split( '\n', $case8Q1 );

    @first_part    = split( ' ',  $case8Q1_split[0] );
    @second_part   = split( ' ',  $case8Q1_split[1] );
    @third_part    = split( ' ',  $case8Q1_split[2] );
    @fourth_part   = split( ' ',  $case8Q1_split[3] );
    @fifth_part    = split( ' ',  $case8Q1_split[4] );

    if ( $first_part[1] eq 'blk' ) {
        $report{Block} = $first_part[0];
    }
    elsif ( $first_part[1] eq 'hrd' ) {
        $report{Hard} = $first_part[0];
    }
    elsif ( $first_part[1] eq 'sft' ) {
        $report{Soft} = $first_part[0];
    }
    elsif ( $first_part[1] eq 'success' ) {
        $report{Success} = $first_part[0];
    }
    elsif ( $first_part[1] eq 'unk' ) {
        $report{Unknown} = $first_part[0];
    }

    # rest ifelse blocks so on........!
}

其中报告为哈希%report

有人可以帮助我如何从这里操作它。

我拥有所有的值,但如果我按照上面的正常if - else进行操作,则需要至少25个块。

如果不清楚,请告诉我。

来源日志示例:

b,email@aol.com,206.1.1.8,2017-03-23 00:01:11-0700,<14901.eb201.TCR2.338351.18567117907MSOSI1.152‌​OSIMS@email.domain.c‌​om>,sft-routing-erro‌​rs,4.4.4 (unable to route: dns lookup failure),
b,email@gmail.com,206.9.1.8,2017-03-23 00:02:13-0700,<149019.eb201.TCR2.338351.18567119237MSOSI1.15‌​2OSIMS@email.domain.‌​com>,sft-no-answer-f‌​rom-host,4.4.1 (no answer from host), 
b,email@gmail.com,206.1.1.5,2017-03-23 03:43:36-0700,<149020.eb201.TCR2.338656.18570260933MSOSI1.15‌​2OSIMS@email.domain.‌​com>,sft-server-rela‌​ted,4.3.2 (system not accepting network messages),smtp;421 Too many concurrent SMTP connections 
b,email@yahoo.com,,2017-03-23 03:54:44-0700,<149019.eb201.TCR2.338351.18567013352MSOSI1.15‌​2OSIMS@email.domain.‌​com>,sft-message-exp‌​ired,4.4.7 (delivery time expired), 
b,email@msn.com,206.1.1.1,2017-03-23 05:04:20-0700,<14902666.eb201.TCR2.3831.2620484MSOSI6374125.‌​102OSIMS@email.domai‌​n.com>,hrd-invalid-m‌​ailbox,5.0.0 (undefined status),smtp;550 Requested action not taken: mailbox unavailable 
b,email@msn.com,206.1.1.1,2017-03-23 05:04:20-0700,<14902666.eb201.TCR2.3831.2620484MSOSI6374125.‌​102OSIMS@email.domai‌​n.com>,hrd-invalid-d‌​omain,5.0.0 (undefined status),smtp;550 Requested action not taken: mailbox unavailable 
b,email@aol.com.com,66.1.1.1,2017-03-23 05:08:44-0700,<149021.eb201.KCR2.021089.566131285MSOSI1.89OS‌​IMS@email.domain.com‌​>,unk-other,4.0.0 (undefined status),smtp;451 Your domain is not configured to use this MX host.
b,email@gmail.com,206.1.1.1,2017-03-23 05:13:22-0700,<1490206.eb201.KCR2.6637.56206428MSOSI1.102OSI‌​MS@email.domain.com>‌​,blk-bad-connection,‌​4.4.2 (bad connection), 
b,email@qq.com.com,206.1.1.1,2017-03-23 05:13:22-0700,<1490206.eb201.KCR2.6637.56206428MSOSI1.102OSI‌​MS@email.domain.com>‌​,blk-spam-related,4.‌​4.2 (bad connection), 

这里的要求更进一步。我需要域名计数 - 例如

Date          Domain       Success Block Soft Hard Unknown
2017-03-23    gmail         1       1   1   1   1    1
2017-03-23    yahoo         1       1   1   1   1    1
2017-03-23    msn           1       1   1   1   1    1
2017-03-23    aol           1       1   1   1   1    1
2017-03-23    other domain  1       1   1   1   1    1

我的问题是其他域包含除gmail,yahoo,msn,hotmail和aol之外的所有域。 count 1就是一个例子,它可以是0。

2 个答案:

答案 0 :(得分:2)

好的,所以 - 你开始这么做是非常困难的,因为...... perl原则上可以做任何削减/排序/ uniq的事情。

如果没有一些示例输入,我无法为你重写,但......我认为你应该考虑这一点。

您也不应该使用全局变量,并使用my的词法变量。

并且 - 正如您已经注意到的 - 如果您为变量名编号,那么您真的应该考虑使用数组。

这样的事情:

use Data::Dumper
my @stuff = map { [split] } split( "\n", $case8Q1 );
print Dumper \@stuff;

给你:

$VAR1 = [
          [
            '310',
            'blk'
          ],
          [
            '1279',
            'hrd'
          ],
          [
            '87',
            'sft'
          ],
          [
            '144056',
            'success'
          ],
          [
            '18',
            'unk'
          ]
        ];

但是你可以更进一步,因为你根本不需要将其解析为数据结构:

   my %data =  reverse $case8Q1 =~ m/(\d+) (\w+)/g;
   print Dumper \%data;

然后给你:

$VAR1 = {
          'hrd' => '1279',
          'sft' => '87',
          'blk' => '310',
          'unk' => '18',
          'success' => '144056'
        };

然后,您可以将其转换为您的报告&#39;通过再次使用,键值查找:

my %keyword_for = ( 
    "blk" => "Block",
    "hrd" => "Hard",
    "sft" => "Soft",
    "success" => "Success",
    "unk" => "Unknown",
    );

foreach my $key ( keys %data ) { 
   $report{$keyword_for{$key}} = $data{$key}; 
}

这会给你:

$VAR1 = {
          'Soft' => '87',
          'Unknown' => '18',
          'Success' => '144056',
          'Block' => '310',
          'Hard' => '1279'
        };

或者更进一步,使用map

内联转换
my %report =   map { m/(\d+) (\w+)/ 
                 && $keyword_for{$2} // $2 => $1 } split "\n", $case8Q1;
print Dumper \%report;

正如你所说,你想要所有要填充的值....实际上,我建议不要这样做,并处理未定义的&#39;在使用类似的东西生成输出时正确:

my @field_order = qw ( Block Hard Soft Success Unknown this_field_missing ); 
print join "\t", @field_order,"\n";
print join "\t", ( map { $report{$_} // 0 } @field_order),"\n";

通过这种方式,您可以获得定义顺序输出,其中哈希不要执行定义顺序。这给出了:

Block   Hard    Soft    Success Unknown this_field_missing  
310     1279    87      144056  18      0   

但是如果你真的想用零值回填空哈希:

$report{$_} //= 0 for values %keyword_for;

但是,现在您已经发布了一些日志来解决您的问题 - 问题的很多更简单:

#!/usr/bin/env perl
use strict;
use warnings;

#configure it:
my %keyword_for = (
   "blk"     => "Block",
   "hrd"     => "Hard",
   "sft"     => "Soft",
   "success" => "Success",
   "unk"     => "Unknown",
);
#set output order - last field is for illustration purposes
my @field_order = qw ( Block Hard Soft Success Unknown this_field_missing );

my %count_of;
#iterate 'STDIN' or files specified to command line.
#So you can 'thisscript.pl /var/log/pmta/File_name-2017-03-23*'
while (<>) {
   #split the line on commas
   my ( $id, $em_addr, $ip, $timestamp, $msg_id, $code, $desc ) = split /,/;
   #require msg_id contains '@email.domain.com'. 
   next unless $msg_id =~ m/\@email\.domain\.com/;
   #split the status field on dash, extracting first word. 
   my ($status) = $code =~ m/^(\w+)-/;
   #update the count - reference the 'keyword for' hash first, 
   #but insert 'raw' if it's something new. 
   $count_of{ $keyword_for{$status} // $status }++;
}

#print a header row (tab sep)
print join "\t", @field_order, "\n";
#print the rest of the values. 
#map is so 'missing' fields get zeros, not 'undefined'. 
print join "\t", ( map { $count_of{$_} // 0 } @field_order ), "\n";

鉴于您发布的小样本,此输出:

Block   Hard    Soft    Success Unknown this_field_missing  
2       2       4       0       1       0   

答案 1 :(得分:1)

很难知道你想要什么结果,但我会在列表上下文中做反对,以便行已经分开,并用if / elsif的链替换一个简单的哈希查找

此示例代码构建与您的哈希%report相同的哈希值,并返回对它的引用。我不得不假设你似乎最有可能使用反叛。 Sobrique 是正确的,你的shell代码也应该在Perl中完成

my %map = (
    blk     => 'Block',
    hrd     => 'Soft',
    sft     => 'Block',
    success => 'Success',
    unk     => 'Unknown',
);

my $cmd = q{cat /var/log/pmta/File_name-2017-03-23*|egrep 'email.domain.com'|cut -d, -f6|cut -d- -f1|sort|uniq -c};

sub get_stats {

    my %report;

    for ( `$cmd` ) {

        my ($val, $type) = split;

        $report{$map{$type}} = $val;
    }

    \%report;
}