【问题标题】:perl - How to extract lines from a file based on their positionperl - 如何根据位置从文件中提取行
【发布时间】:2015-08-31 09:46:08
【问题描述】:

我正在处理一个文本文件以提取包含时间戳的行,然后对这些时间戳执行计算。该行包含一个时间戳,后跟一条消息,我正在执行正则表达式以提取该消息。

TIME | MESSAGE
20:48:27.159 | FOO
20:48:47.353 | BAR
20:48:49.227 | SPAM
20:48:52.192 | FOO

下面是我对文件执行的正则表达式的 sudo 代码

... .... ... 


open (my $FH, "<", $file) or die "Cannot open <$file>: $!";
for my $line (<$FH>) {
    if ($line =~ /bar/) {
        my $ts1 = ExtractTimestamp($line);
    } elsif ($line =~ /FOO/) {
        my $ts2 = ExtractTimestamp($line);
    }
}
my $diff = $ts2 - $ts1;

这里的问题是正则表达式找到第一次出现的行并提取它,这给我留下了负时间戳。我想知道 perl 中是否有任何模块或任何技术可以提取 BAR 之后文件中出现的比如说 FOO 的出现?

在此不胜感激!

【问题讨论】:

  • 我认为您示例中的 /bar/ 是一个错字,因为您的充足数据是 BAR 并且您的正则表达式不区分大小写?

标签: regex perl


【解决方案1】:

此解决方案使用 范围运算符 查找第一个 BAR 行,然后是其后的第一个 FOO 行。记录中的时间被推送到数组@ts,如果它是范围内的第一行或最后一行

use strict;
use warnings;

my @ts;
while ( <DATA> ) {
    next unless my $state = /BAR/ .. /FOO/;
    push @ts, /([\d:.]+)/ if $state == 1 or $state =~ /E/;
}

print join(' ... ', @ts), "\n";

__DATA__
TIME | MESSAGE
20:48:27.159 | FOO
20:48:47.353 | BAR
20:48:49.227 | SPAM
20:48:52.192 | FOO

输出

20:48:47.353 ... 20:48:52.192

【讨论】:

  • 在标量上下文中,它不应该被称为触发器运算符吗?
  • @paveljurca:不,因为 flip-flop 是两个音节,而 range 只是一个音节。 (同样的原因使得 hash 比荒谬的七音节 associative array 更可取。)而且因为它为 range 返回 true 个值。
  • 好的,这是合理的。然后我会坚持使用 range :)
【解决方案2】:
open (my $FH, "<", $file) or die "Cannot open <$file>: $!";
# define $ts1 and $ts2 OUTSIDE "for" loop
my( $ts1, $ts2);
for my $line (<$FH>) {
    if ($line =~ /bar/) {
        $ts1 = ExtractTimestamp($line);
    } 
    # ignore FOO before first BAR sets $ts1
    elsif ( defined($ts1) and $line =~ /FOO/) { 
        $ts2 = ExtractTimestamp($line);
        # stop searching after first FOO and "BAR after FOO" pair
        last;
    }
}
# if both FOO and "BAR after FOO" has set their variables
if( defined($ts1) and defined($ts2)) {
   my $diff = $ts2 - $ts1;
   ...
 }

【讨论】:

  • 谢谢,没有想到定义的关键字!打算试一试,看看这是否有效。
【解决方案3】:

在 perl 中有几种方法可以做到这一点,具体取决于您想要完成的任务。如果我没看错,您正在寻找FOOBAR 时间戳,并可能试图提取增量?

关键问题是 - FOOBAR 是否完全匹配?

我的意思是,你可以通过多行正则表达式来做到这一点:

#!/usr/bin/env perl

use strict;
use warnings;
use Data::Dumper;

local $/;

my ( $bar, $foo )  =  <DATA> =~ m/^(\d\S+) \| BAR.*?(\d\S+) \| FOO$/ms;
print "BAR: $bar\nFOO: $foo\n";

__DATA__
TIME | MESSAGE
20:48:27.159 | FOO
20:48:47.353 | BAR
20:48:49.227 | SPAM
20:48:52.192 | FOO

这将匹配成对的“BAR”和“FOO”的第一个实例。 (如果您在正则表达式上使用g 标志,则可以多次捕获)。

或者-您可以将记录分隔符设置为FOO

#!/usr/bin/env perl

use strict;
use warnings;
use Data::Dumper;

local $/ = "FOO\n"; 

while ( <DATA> ) {

   my ( $foo ) = m/(\S+) \| FOO/;
   my ( $bar ) = m/(\S+) \| BAR/;
   print "$foo $bar\n";

}

__DATA__
TIME | MESSAGE
20:48:27.159 | FOO
20:48:47.353 | BAR
20:48:49.227 | SPAM
20:48:52.192 | FOO

或者你在做什么——逐行迭代:

#!/usr/bin/env perl

use strict;
use warnings;
use Data::Dumper;

my $last_bar;
while (<DATA>) {

    if (m/^(\d\S+) \| BAR/) {
        $last_bar = $1;
    }
    if ( my ($foo) = m/^(\d\S+) \| FOO/ ) {
        if ($last_bar) {
            print "$foo $last_bar\n";
        }
        else {
            print "Unmatched:\n";
            print;
        }
        $last_bar = undef;
    }
}

__DATA__
TIME | MESSAGE
20:48:27.159 | FOO
20:48:47.353 | BAR
20:48:49.227 | SPAM
20:48:52.192 | FOO

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-04-20
    • 2020-03-02
    • 2023-01-18
    • 1970-01-01
    • 2021-09-26
    • 1970-01-01
    • 2021-08-15
    • 2020-07-14
    相关资源
    最近更新 更多