在Perl中读取文件时如何跳过行?

6

我该怎么做?

open FILE, $somefile;
foreach (<FILE>)
{
   if (/some_regex/)
   {
      $want_the_next_line = <FILE>;
      $want_the_next_line_after_that = <FILE>;
   }
}

你懂我的意思吗?我想在我的foreach循环中 slurp(读取)一堆行,而不是每次迭代都要记住我的状态并检查它。目前在Perldoc中找不到任何有用的关于 <> 的信息。

顺便说一下,我真的不想:

@file = <FILE>;

我相信你已经理解了。

我不明白。除非你的意思是在读取整个文件时使用while而不是foreach,如果存在内存问题。 - ysth
是的,我的确是指foreach,就像你提到的和David在下面所说的一样,无论如何我都将整个文件读入了一个数组,这正是我不想要的。 - Joe
2个回答

11

使用while代替foreach:

open FILE, $somefile;
while (<FILE>)                      # <<-- HERE
{
   if (/some_regex/)
   {
      $want_the_next_line = <FILE>;
      $want_the_next_line_after_that = <FILE>;
   }
}
while循环只会从<FILE>读取一行,然后您可以在当前迭代中对其进行任何操作。此技术还可以帮助您避免一次性读取整个文件。 技术背景: foreach()需要一个数组,因此一次性读取整个文件,而while()循环中的表达式是标量上下文,并且仅检查“false”值(如EOF所产生的值)。

你知道吗,我完全忘记了那件事。谢谢,先生。你太棒了。但是你这么晚还在做什么? :D - Joe
3
现在是欧洲的上午9:35。欢迎来到全球化。 - David Schmitt
2
+1. 一个警告:与foreach不同,while会覆盖$,因此您可能需要事先使用“local $;”。 - j_random_hacker
1
实际上,foreach需要一个列表,而while对其条件进行false值的评估。像-1这样的“负”值实际上是true。 - brian d foy
@j_random_hacker:我不确定本地化 $_ 是一个好主意,至少通常不是。在我看来,每个作用域一个神奇的默认变量就足够了。我建议只在最内层循环中使用 $_。否则会让人困惑它指的是什么。 - Jon Ericson
2
@Jon:在一个30行的脚本中不要费心。在更大的项目中,始终确保您的函数不会破坏 $_(或任何其他全局状态)的好处是,您不必考虑从任何地方调用它们是否安全——即使是最内层的循环。基本上,它消除了您需要记住有关函数的一种类型信息。 - j_random_hacker

5
您可以使用perlfaq5中相同的技术:如何更改、删除或插入文件中的一行,或在文件开头添加内容:


(由brian d foy贡献)

从文本文件中插入、更改或删除一行的基本思路是读取并打印文件直到要进行更改的位置,进行更改,然后读取并打印其余部分。Perl不提供对行的随机访问(特别是因为记录输入分隔符$/是可变的),尽管像Tie::File这样的模块可以模拟它。

执行这些任务的Perl程序的基本形式是打开文件,打印其行,然后关闭文件:

open my $in,  '<',  $file      or die "Can't read old file: $!";
open my $out, '>', "$file.new" or die "Can't write new file: $!";

while( <$in> )
    {
    print $out $_;
    }

    close $out;

在基本形式的基础上,添加需要插入、更改或删除行的部分。

要在开头添加行,请在打印现有行的循环之前打印那些行。

open my $in,  '<',  $file      or die "Can't read old file: $!";
open my $out, '>', "$file.new" or die "Can't write new file: $!";

print $out "# Add this line to the top\n"; # <--- HERE'S THE MAGIC

while( <$in> )
    {
    print $out $_;
    }

    close $out;

要更改现有的行,请在 while 循环中插入代码以修改行。在本例中,代码查找所有小写版本的 "perl" 并将它们变为大写。这将发生在每一行上,因此请确保您需要在每一行上执行此操作!

open my $in,  '<',  $file      or die "Can't read old file: $!";
open my $out, '>', "$file.new" or die "Can't write new file: $!";

print $out "# Add this line to the top\n";

while( <$in> )
    {
    s/\b(perl)\b/Perl/g;
    print $out $_;
    }

    close $out;

如果只想更改特定的一行,则输入行号 $ . 是有用的。首先读取并打印要更改的行之前的行。接下来,读取要更改的单行,进行更改并打印它。然后,读取剩余的行并打印它们:

while( <$in> )   # print the lines before the change
    {
    print $out $_;
    last if $. == 4; # line number before change
    }

my $line = <$in>;
$line =~ s/\b(perl)\b/Perl/g;
print $out $line;

while( <$in> )   # print the rest of the lines
    {
    print $out $_;
    }

要跳过行,使用循环控制。在此示例中,下一个跳过注释行,而最后一行则在遇到 ENDDATA 时停止所有处理。

while( <$in> )
    {
    next if /^\s+#/;             # skip comment lines
    last if /^__(END|DATA)__$/;  # stop at end of code marker
    print $out $_;
    }

使用 next 跳过不想出现在输出结果中的行,执行相同的操作以删除特定行。以下示例跳过每 5 行:

while( <$in> )
    {
    next unless $. % 5;
    print $out $_;
    }

如果出于某种奇怪的原因,您真的想一次性看到整个文件而不是逐行处理,您可以将其读入内存(只要您能够将整个文件放入内存中):

open my $in,  '<',  $file      or die "Can't read old file: $!"
open my $out, '>', "$file.new" or die "Can't write new file: $!";

my @lines = do { local $/; <$in> }; # slurp!

    # do your magic here

print $out @lines;

模块如File::Slurp和Tie::File也可以帮助解决这个问题。但是,如果可以的话,请避免一次读取整个文件。在进程完成之前,Perl不会将该内存返回给操作系统。

您还可以使用Perl one-liners来就地修改文件。以下代码将inFile.txt中的所有“Fred”更改为“Barney”,并用新内容覆盖文件。使用-p开关,Perl会在-e指定的代码周围包装while循环,而-i则打开原地编辑。当前行在$ 中。使用-p,Perl会自动在循环结束时打印$的值。有关更多详细信息,请参见perlrun。

perl -pi -e 's/Fred/Barney/' inFile.txt

为了备份inFile.txt,给-i一个文件扩展名以添加:
perl -pi.bak -e 's/Fred/Barney/' inFile.txt

如果你只想更改第五行,可以添加一个检查$.(输入行号)的测试,然后只有当测试通过时才执行操作:

perl -pi -e 's/Fred/Barney/ if $. == 5' inFile.txt

要在某一行之前添加代码行,您可以在Perl打印$_之前添加一行(或多行!):
perl -pi -e 'print "Put before third line\n" if $. == 3' inFile.txt

您甚至可以在文件开头添加一行,因为当前行会在循环结束时打印:

perl -pi -e 'print "Put before first line\n" if $. == 1' inFile.txt

要在文件中已有的一行后插入一行,请使用-n开关。它与-p类似,只是在循环结束时不打印$ _,所以您需要自己打印。在这种情况下,先打印$ _,然后再打印要添加的行。

perl -ni -e 'print; print "Put after fifth line\n" if $. == 5' inFile.txt

要删除行,只需打印您想要的行。

perl -ni -e 'print unless /d/' inFile.txt

    ... or ...

perl -pi -e 'next unless /d/' inFile.txt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接