【问题标题】:Perl finding valid pairs of lines among different casesPerl 在不同情况下查找有效的行对
【发布时间】:2012-04-29 15:17:53
【问题描述】:

我有 HTTP 标头请求并以制表符分隔的形式回复数据,每个 GET/POST 并在不同的行中回复。该数据使得一个 TCP 流有多个 GET、POST 和 REPLY。我只需要从这些案例中选择第一个有效的 GET - REPLY 对。一个例子(简化)是:

ID       Source    Dest    Bytes   Type   Content-Length  host               lines.... 
1         A         B       10     GET        NA          yahoo.com            2
1         A         B       10     REPLY      10          NA                   2 
2         C         D       40     GET        NA          google.com           4
2         C         D       40     REPLY      20          NA                   4
2         C         D       40     GET        NA          google.com           4
2         C         D       40     REPLY      30          NA                   4
3         A         B       250    POST       NA          mail.yahoo.com       5
3         A         B       250    REPLY      NA          NA                   5
3         A         B       250    REPLY      15          NA                   5
3         A         B       250    GET        NA          yimg.com             5
3         A         B       250    REPLY      35          NA                   5
4         G         H       415    REPLY      10          NA                   6
4         G         H       415    POST       NA          facebook.com         6
4         G         H       415    REPLY      NA          NA                   6
4         G         H       415    REPLY      NA          NA                   6
4         G         H       415    GET        NA          photos.facebook.com  6
4         G         H       415    REPLY      50          NA                   6

....

所以,基本上我需要为每个 ID 获取一个请求-回复对并将它们写入一个新文件。

对于“1”,它只是一对,所以很容易。 但也有两行都是 GET、POST 或 REPLY 的错误情况。所以,这种情况被忽略了。

对于“2”,我会选择第一个 GET - REPLY 对。

对于“3”,我会选择第一个 GET 但第二个 REPLY,因为第一个中没有 Content-Length(使 subsequest REPLY 成为更好的候选者)。

对于“4”,我会选择第一个 POST(或 GET),因为第一个标头无法回复。即使 POST 之后的内容长度缺失,我也不会在第二个 GET 之后选择 REPLY,因为 REPLY 在那之后。所以我会选择第一个回复。

因此,在选择了最佳请求和回复对后,我需要将它们配对在一行中。例如,输出将是:

 ID       Source    Dest    Bytes   Type   Content-Length  host         .... 
   1         A         B       10     GET      10          yahoo.com
   2         C         D       40     GET      20          google.com
   3         A         B       250    POST     15          mail.yahoo.com
   4         G         H       415    POST     NA          facebook.com

实际数据中还有很多其他标题,但这个示例几乎显示了我需要的内容。在 Perl 中如何做到这一点?我几乎一开始就被卡住了,所以我一次只能读取一行文件。

open F, "<", "file.txt" || die "Cannot open $f: $!";

  while (<F>) {
    chomp;
    my @line = split /\t/;


      # get the valid pairs for cases with multiple request - replies


      # get the paired up data together

  }
  close (F);

*编辑:我添加了一个额外的列,给出了每个 ID 的 HTTP 标题行数。这可能有助于了解要检查多少后续行。另外,我修改了 ID '4',以便第一个标题行是回复。 *

【问题讨论】:

  • +1 了解所需内容的详细说明。谢谢!
  • ID 是否足以识别要处理的行组?如果是这样,那么在 ID 内,我们可以假设源和目标是相同的吗?
  • @JonathanLeffler 是的,这就足够了,因为它代表了一个具有相同源和目标、端口等的 TCP 流。所以,我需要为每个 ID 创建一个请求-回复对,如图所示。
  • 我们可以假设单个 ID 的行是连续的吗?即当 ID 从 1 变为 2 时,是否保证不再有 ID 为 1 的行?
  • @cjm 是的,所有具有相同 ID 的行都分组在一起。

标签: perl http multiline file-manipulation


【解决方案1】:

下面的程序可以满足你的需要。

它被注释了,我认为它相当清晰。请问有什么不清楚的地方。

use strict;
use warnings;

use List::Util 'max';

my $file = $ARGV[0] // 'file.txt';
open my $fh, '<', $file or die qq(Unable to open "$file" for reading: $!);

# Read the field names from the first line to index the hashes
# Remember where the data in the file starts so we can get back here
#
my @fields = split ' ', <$fh>;
my $start = tell $fh;

# Build a format to print the accumulated data
# Create a hash that relates column headers to their widths
#
my @headers = qw/ ID Source Dest Bytes Type Content-Length host /;
my %len = map { $_ => length } @headers;

# Read through the file to find the maximum data width for each column
#
while (<$fh>) {
  my %data;
  @data{@fields} = split;
  next unless $data{ID} =~ /^\d/;
  $len{$_} = max($len{$_}, length $data{$_}) for @headers;
}

# Build a format string using the values calculated
#
my $format = join '   ', map sprintf('%%%ds', $_), @len{@headers};
$format .= "\n";

# Go back to the start of the data
# Print the column headers
#
seek $fh, $start, 0;
printf $format, @headers;

# Build transaction data hashes into $record and print them
# Ignore any events before the first request
# Ignore the second request and anything after it
# Update the stored Content-Length field if a value other than NA appears
#
my $record;
my $nreq = 0;

while (<$fh>) {

  my %data;
  @data{@fields} = split;
  my ($id, $type) = @data{ qw/ ID Type / };
  next unless $id =~ /^\d/;

  if ($record and $id ne $record->{ID}) {
    printf $format, @{$record}{@headers};
    undef $record;
    $nreq = 0;
  }

  if ($type eq 'GET' or $type eq 'POST') {
    $record = \%data if $nreq == 0;
    $nreq++;
  }
  elsif ($nreq == 1) {
    if ($record->{'Content-Length'} eq 'NA' and $data{'Content-Length'} ne 'NA') {
      $record->{'Content-Length'} = $data{'Content-Length'};
    }
  }
}

printf $format, @{$record}{@headers} if $record;

输出

根据问题中给出的数据,这个程序产生

ID   Source   Dest   Bytes    Type   Content-Length                  host
 1        A      B      10     GET               10             yahoo.com
 2        C      D      40     GET               20            google.com
 3        A      B     250    POST               15        mail.yahoo.com
 4        G      H     415    POST               NA          facebook.com

【讨论】:

    【解决方案2】:

    这似乎适用于给定的数据:

    #!/usr/bin/env perl
    use strict;
    use warnings;
    
    # Shape of input records
    use constant ID       => 0;
    use constant Source   => 1;
    use constant Dest     => 2;
    use constant Bytes    => 3;
    use constant Type     => 4;
    use constant Length   => 5;
    use constant Host     => 6;
    
    use constant fmt_head => "%-6s  %-6s  %-6s  %-6s  %-6s  %-6s  %s\n";
    use constant fmt_data => "%-6d  %-6s  %-6s  % 6d  %-6s  % 6s  %s\n";
    
    printf fmt_head, "ID", "Source", "Dest", "Bytes", "Type", "Length", "Host";
    
    my @post_get;
    my @reply;
    my $lastid = -1;
    my $pg_count = 0;
    
    sub print_data
    {
        # Final validity checking
        if ($lastid != -1)
        {
            printf fmt_data, $post_get[ID], $post_get[Source],
                   $post_get[Dest], $post_get[Bytes], $post_get[Type], $reply[Length], $post_get[Host];
            # Reset arrays;
            @post_get = ();
            @reply = ();
            $pg_count = 0;
        }
    }
    
    while (<>)
    {
        chomp;
        my @record = split;
        # Validate record here (number of fields, etc)
        # Detect change in ID
        print_data if ($record[ID] != $lastid);
        $lastid = $record[ID];
    
        if ($record[Type] eq "REPLY")
        {
            # Discard REPLY if there wasn't already a POST/GET
            next unless defined $post_get[ID];
            # Discard REPLY if there was a second POST/GET
            next if $pg_count > 1;
            @reply = @record if !defined $reply[ID];
            $reply[Length] = $record[Length]
                             if $reply[Length] eq "NA" && $record[Length] ne "NA";
        }
        else
        {
            $pg_count++;
            @post_get = @record if !defined $post_get[ID];
            $post_get[Length] = $record[Length]
                                if $post_get[Length] eq "NA" && $record[Length] ne "NA";
        }
    }
    print_data;
    

    它产生:

    ID   Source   Dest   Bytes   Type   Content-Length             host
     1        A      B      10    GET               10        yahoo.com
     2        C      D      40    GET               20       google.com
     3        A      B     250   POST               15   mail.yahoo.com
     4        G      H     415   POST               NA     facebook.com
    

    与问题的主要偏差是用“长度”代替“内容长度”;如果需要,修复很容易——将fmt_datafmt_head 中的第6 个长度更改为14,并将"Length" 更改为"Content-Length"

    【讨论】:

    • print_data 中使用全局变量并依靠它来重置这些全局变量可能不是最好的主意。改用引用,并清除主循环中的数组。此外,chomp 不需要拆分空格。但是,尊重制表符分隔的格式并使用 chomp + split /\t/ 将是更好的选择,IMO。
    • 另外,使用数组切片 printf fmt_data, @post_get[ID, Source, Dest, Bytes, Type], $reply[Length], $post_get[Host] 更具可读性。
    • @Jonathan Leffler:使用由有效enum 索引的数组而不是简单的哈希值似乎是不恰当的。
    猜你喜欢
    • 2013-02-12
    • 2014-02-28
    • 2021-06-13
    • 1970-01-01
    • 1970-01-01
    • 2015-06-02
    • 2017-02-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多