在perl中跟踪前一行和后一行的最佳方法

时间:2013-08-18 09:45:28

标签: perl loops readfile

在perl中保留前一行和/或后续行的信息的最佳/正确方法是什么。例如,使用以下代码:

while (<IN>) {
   print;
}

只有当文件中的上一行或下一行与foo匹配时才能更改为不打印行,但是否则打印?

你能给出代码示例吗?感谢。

4 个答案:

答案 0 :(得分:2)

更新:简化展示。

基本上,如果要根据其他两行中包含的信息打印当前行,则需要跟踪两条额外的行。这是一个简单的脚本,所有内容都是硬编码的:

#!/usr/bin/env perl

use strict;
use warnings;

my $prev = undef;
my $candidate = scalar <DATA>;

while (defined $candidate) {
    my $next = <DATA>;
    unless (
        (defined($prev) && ($prev =~ /foo/)) ||
        (defined($next) && ($next =~ /foo/))
    ) {
        print $candidate;
    }
    ($prev, $candidate) = ($candidate, $next);
}

__DATA__
1
2
foo
3
4
5
foo
6
foo
7
8
9
foo

我们可以将它概括为一个带文件句柄和测试的函数(作为子例程引用):

#!/usr/bin/env perl

use strict; use warnings;

print_mid_if(\*DATA, sub{ return !(
    (defined($_[0]) && ($_[0] =~ /foo/)) ||
    (defined($_[1]) && ($_[1] =~ /foo/))
)} );

sub print_mid_if {
    my $fh = shift;
    my $test = shift;

    my $prev = undef;
    my $candidate = scalar <$fh>;

    while (defined $candidate) {
        my $next = <$fh>;
        print $candidate if $test->($prev, $next);
        ($prev, $candidate) = ($candidate, $next);
    }
}

__DATA__
1
2
foo
3
4
5
foo
6
foo
7
8
9
foo

答案 1 :(得分:2)

你可以将你的行读入一个数组,然后如果你得到一些以某种方式发出信号的东西,则弹出数组的最后几个元素。一旦你完成了所有内容的阅读,就可以打印出来了:

use strict;
use warnings;
use feature qw(say);
use autodie;  #Won't catch attempt to read from an empty file

use constant    FILE_NAME => "some_name.txt"
   or die qq(Cannot open ) . FILE_NAME . qq(for reading: $!\n);
open my $fh, "<", FILE_NAME;

my @output;
LINE:
while ( my $line = <DATA> ) {
    chomp $line;
    if ( $line eq "foo" ) {
        pop @output;  #The line before foo
        <DATA>;        #The line after foo
        next LINE;    #Skip line foo. Don't push it into the array
    }
    push @output, $line;
}

从那里,您可以使用您不想打印的值打印出数组。

for my $line ( @output ) {
   say $line;
}

唯一的问题是这需要记忆。如果您的文件非常大,则可能内存不足。

解决此问题的一种方法是使用缓冲区。将值存储在数组中,并在数组中按另一个值时移出最后一个值。如果读入的值为foo,则可以重置阵列。在这种情况下,缓冲区最多只包含一行:

#! /usr/bin/env perl

use strict;
use warnings;
use autodie;
use feature qw(say);

my @buffer;
LINE:
while ( my $line = <DATA> ) {
    chomp $line;
    if ( $line eq "foo" ) {
        @buffer = ();    #Empty buffer of previous line
        <DATA>;           #Get rid of the next line
        next LINE;       #Foo doesn't get pushed into the buffer
    }
    push @buffer, $line;
    if ( @buffer > 1 ) {    #Buffer is "full"
        say shift @buffer; #Print out previous line
    }
}
#
# Empty out buffer
#
for my $line ( @buffer ) {
    say $line;
}
__DATA__
2
3
4
5
6
7
8
9
10
11
12
13
1
2
foo
3
4
5
foo
6
7
8
9
foo

请注意,当我跳过下一行时,我可能会尝试从空文件中读取数据。这没关系。 <$fh>将返回空字符串或undef,但我可以忽略它。当我回到循环的顶部时,我会发现错误。

答案 2 :(得分:2)

我没有看到你对“最佳”有任何具体标准,所以我会给你一个解决方案,该解决方案沿着与目前为止所呈现的不同的轴线“最佳”。您可以使用Tie::File并将整个文件视为数组,然后使用索引迭代数组。上一行和下一行分别只是$index-1$index+1。你只需要担心你的索引超出了数组的范围。这是一个例子:

#!/usr/bin/env perl

use strict;
use warnings;
use 5.010;          # just for "say"
use Tie::File;

tie my @array, 'Tie::File', "filename" or die;

for my $i (0..$#array) {
    if ($i > 0 && $i < $#array) {   # ensure $i-1 and $i+1 make sense
        next if $array[$i-1] =~ /BEFORE/ &&
                $array[$i+1] =~ /AFTER/;
    }
    say $array[$i];
}

如果更方便,您可以指定文件句柄而不是文件名,Tie::File也有一些参数来控制内存使用情况,或者如果您愿意,可以更改“行”的含义。查看文档以获取更多信息。

无论如何,如果您熟悉数组并且喜欢用数组来思考,那么这是另一种做你想要的方法,这可能在概念上更简单。

答案 3 :(得分:1)

我会将文件读入一个数组,每行都是一个数组元素,然后你可以进行比较。唯一真正的设计考虑因素是正在读入内存的文件大小。