在Perl中,如何在一个循环中读取多个文件句柄?

Rya*_*yan 8 perl file-io

我想知道如何在Perl中实现它:

while ( not the end of the files )
    $var1 = read a line from file 1
    $var2 = read a line from file 2
    # operate on variables
end while
Run Code Online (Sandbox Code Playgroud)

我不知道如何在一个while循环中从两个文件一次读取一行.

TLP*_*TLP 11

好像你自己写的答案差不多.只需检查eof两个文件句柄,如下所示:

while (not eof $fh1 and not eof $fh2) {
    my $var1 = <$fh1>;
    my $var2 = <$fh2>;
    # do stuff
}
Run Code Online (Sandbox Code Playgroud)

更多阅读:


Sin*_*nür 10

注意:我在回答@zostay和@ jm666的评论时扩展了我的答案.

提出一个有效,清晰,简洁的问题答案的第一步,从相关变量汇总的观点开始.因此,数组@fh将包含我们同时读取的文件句柄.

然后,我们可以从每个文件句柄中读取一行,并使用<>运算符和map将它们存储在一个数组中.map采用转换规则和列表,并返回另一个列表.因此:

my @lines = map scalar <$_>, @fh;
Run Code Online (Sandbox Code Playgroud)

获取文件句柄@fh,并从每个文件句柄中读取一行(注释标量),并将这些行放入@lines.这是一个one-to-one转变@fh.

作为<>指示的文档,<>如果到达文件结尾,则返回未定义的值,或者存在错误.

现在,检查我们是否成功读取所有文件的一种方法是检查数字定义的行是否与文件句柄的数量相同.grep选择满足特定条件的列表元素.于是

@fh == grep defined, my @lines = map <$_>, @fh;
Run Code Online (Sandbox Code Playgroud)

将检查文件句柄@fh的数量是否与中定义的元素的数量相同@lines.但是,@fh这种比较的两侧出现确实令人困惑,因此检查没有未定义元素的另一种方法@lines是:

0 == grep !defined, my @lines = map <$_>, @fh;
Run Code Online (Sandbox Code Playgroud)

如果你想把这个条件放在while循环中,你必须写:

while (0 == grep !defined, my @lines = map <$_>, @fh) {
Run Code Online (Sandbox Code Playgroud)

而如果你去一个直到,你可以简单地写:

until (grep !defined, my @lines = map <$_>, @fh) {
Run Code Online (Sandbox Code Playgroud)

这意味着" 直到至少有一个读取行返回一个未定义的值,执行循环体 ".

现在,请注意PerleofCeof不同.Perl的eof文档说明:

实用提示:您几乎不需要eof在Perl中使用,因为输入操作符通常undef在数据耗尽或遇到错误时返回.

如果你eof每次都通过循环检查,那么你的文件IO就会翻倍,因为" 这个函数实际上是在读取一个字符,然后ungetc是它."

我几乎总是用我的代码给出一个自包含的runnable示例.下面,我不想依赖系统中存在的任何特定文件,因此我使用始终可用DATASTDIN处理.与使用该eof函数相反,当您使用此方法时,您不必担心从哪里读取:您关心的是任何一个文件的readline是否返回了未定义的值.它也可以与任意数量的文件句柄一起使用.此外,你真的没有把文件句柄放在一个数组中,但正如我所说,相关的变量属于一个聚合,所以如果你发现自己输入像

my $var1 = <$fh1>;
my $var2 = <$fh2>;
Run Code Online (Sandbox Code Playgroud)

意识到你应该使用数组来存储文件句柄.

#!/usr/bin/env perl

use strict; use warnings;

my @fh = (\*DATA, \*STDIN);

until (grep !defined, my @lines = map scalar <$_>, @fh) {
    print for @lines;
}

__DATA__
one
two
three
Run Code Online (Sandbox Code Playgroud)

此示例脚本将停止询问您输入STDIN何时DATA用尽线路.如果脚本中没有任何尾随空白行,则必须输入 脚本终止前的四行.

现在,如果您想知道哪些文件句柄到达目的地,您将切换到使用以下内容:

#!/usr/bin/env perl

use strict; use warnings;

my @fh = (\*DATA, \*STDIN);

while (1) {
    my @lines = map scalar <$_>, @fh;

    if (my @eof = grep !defined($lines[$_]), 0 .. $#fh) {
        warn "Could not read from filehandle(s) '@eof'";
        last;
    }

    print for @lines;
}

__DATA__
one
two
three
Run Code Online (Sandbox Code Playgroud)

重要

上面的循环设计为在任何一个文件耗尽时停止.另一方面,您可能希望循环运行,直到所有文件都用完为止.在这种情况下,您将使用:

 while (grep defined, my @lines = map scalar <$_>, @fh) {
Run Code Online (Sandbox Code Playgroud)

  • 我已经编写了12年的Perl编程,这是一个非常明显的解决方案来给新的Perl黑客.我必须仔细阅读才能理解它.TLP的答案要好得多,我立刻明白了. (3认同)
  • @John我很好奇:我的解决方案的哪一部分需要任何未来的修改(假设它是在一个适当命名的方法或子方法中)?通过显式命名文件句柄,并明确检查它们是否为"eof",每次更改文件数时都需要更改代码.如何不需要修改代码会产生维护成本? (2认同)