使用Perl从输入中提取并过滤一系列行

时间:2016-03-21 10:06:44

标签: perl foreach

我对Perl很新,我在使用Meteor.users.update( {_id: Meteor.userId(), 'courseList.courseId': courseId}, {$set: {'courseList.$.quizScore.0.ans': selectedAns}, $inc: {'courseList.$.quizScore.0.retry': 1} }); 循环跳过行时遇到一些问题。我想将一些文本文件的行复制到一个新行。

当一行的第一个单词为foreach时,再跳过两行,然后打印所有内容,直到文件末尾或遇到空行。

我试图找到类似的帖子,但没有人谈论使用文本文件。

这是我想到的形式

FIRST ITERATION

我尝试使用use 5.010; use strict; use warnings; open( INPUT, "xxx.txt" ) or die("Could not open log file."); open( OUT, ">>yyy.txt" ); foreach my $line (<INPUT>) { if ( $line =~ m/^FIRST ITERATION/ ) { # print OUT } } close(OUT); close(INFO); next,但我的程序仅打印以$line++开头的行。

我可能会尝试使用FIRST ITERATION循环,但我不知道我的文件可能有多少行,也不知道“First Iteration”和下一个空行之间有多少行。 / p>

6 个答案:

答案 0 :(得分:5)

最简单的方法是一次处理一行文件,并保留一个状态标志,如果当前行以FIRST ITERATION开头,则设置为1,如果为空,则保持为0,否则如果为空则保持为0它已经是正数,因此它提供了当前块中行号的计数

此解决方案期望输入文件的路径作为命令行上的参数并将其输出打印到STDOUT,因此您需要根据需要将输出重定向到命令行上的文件

请注意,正则表达式模式/\S/会检查当前行中的任何位置是否存在非空字符,因此如果该行为空或所有空白字符,则not /\S/为真

use strict;
use warnings;

my $lines = 0;

while ( <> ) {

    if ( /^FIRST ITERATION/ ) {
        $lines = 1;
    }
    elsif ( not /\S/ ) {
        $lines = 0;
    }
    elsif ( $lines > 0 ) {
        ++$lines;
    }

    print if $lines > 3;
}

使用Perl的内置范围运算符可以大大简化这一过程,该运算符保持自己的内部状态并返回已计算的次数。所以可以写上面的

use strict;
use warnings;

while ( <> ) {
    my $s = /^FIRST ITERATION/ ... not /\S/;
    print if $s and $s > 3;
}

最后一个可以重写为像这样的单行命令行程序

$ perl -ne '$s = /^FIRST ITERATION/ ... not /\S/; print if $s and $s > 3' myfile.txt

答案 1 :(得分:2)

使用额外的计数器,即说明打印行的条件。像这样:

$skipCounter = 3;

在foreach中:

if ($skipCounter == 2) {
    // print OUT
}
if ( $line =~ m/^FIRST ITERATION/) {
    $skipCounter = 0;
}

$skipCounter++;

答案 2 :(得分:2)

建议:使用STDIN和STDOUT而不是文件,这将允许您在不修改脚本的情况下更改它们

代码:

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


open(INPUT, "xxx.txt" ) or die "Could not open log file: $!.";
open(OUT, ">yyy.txt") or die "Could not open output file: $!";
while( my $line = <INPUT> )
{
    if ( $line =~ m/^FIRST ITERATION/) {
        <INPUT>; # skip line
        <INPUT>; # skip line
        while( $line = <INPUT>) # print till empty line
        {
            last if $line eq "\n";
            print OUT $line;
        }
    };
};
close (OUT);
close (INPUT);

答案 3 :(得分:1)

你走在正确的轨道上。您需要使用的是flip-flop operator(基本上是范围运算符)..。它将在两场比赛之间为您切换,因此您可以获得介于两者之间的所有内容。在那之后,这是一个跟踪你想要跳过的行的问题。

所以基本上我们正在检查FIRST ITERATION和空行,并抓住它们之间的所有内容。 $skip用于记住跳过了多少行。它从0开始,在我们开始进入触发器if块后,前两行增加。在else情况下,我们触发器之后,它会重置为0,因此我们可以从下一个块重新开始。

由于您知道如何打开和写入文件,我将跳过它。

use strict;
use warnings;

my $skip = 0;
while (<DATA>) {
    if (/^FIRST ITERATION$/ .. /^$/) {
        next if $skip++ <= 2;
        print $_;
    } else {
        $skip = 0;
    }
}
__DATA__
FIRST ITERATION
skip1
skip2
foo
bar
baz

don't print this

这个输出是:

foo
bar
baz

要坚持使用自己的代码,这里有一个非常详细的解决方案,它使用foreach而没有触发器。它做了同样的事情,只是说了很多话。

my $skip = 0;   # skip lines
my $match = 0;  # keep track of if we're in between the borders
foreach my $line (<DATA>) {
    if ( $line =~ m/^FIRST ITERATION/ ) {
        $match = 1; # we are inside the match
        next;
    }
    if ($line =~ m/^$/) {
        $match = 0; # we are done matching
        next;
    }
    if ($match) {
        $skip++;     # count skip-lines
        if ($skip <= 2) {
            next;    # ... and skip the first two
        }
        print $line; # this is the content we want  
    }
}

答案 4 :(得分:1)

使用段落模式(返回由空行而不是行分隔的块):

local $/ = "";  # Paragraph mode.

while (<>) {
    s/\n\K\n+//;  # Get rid of trailing empty lines.
    print /^FIRST ITERATION\n[^\n]*\n[^\n]*\n(.*)/ms;
}

答案 5 :(得分:1)

使用触发器操作符:

while (<>) {
    if (my $line_num = /^FIRST ITERATION$/ .. /^$/) {
        print if $line_num > 3 && $line_num !~ /E0/;
    }
}
当触发器翻转时(即$line_num !~ /E0/之后的第一个空行),

FIRST ITERATION为真。检查这是为了避免打印空白行。