5

我很快记下了一个 Perl 脚本,该脚本将平均几个文件,其中只有一列数字。它涉及从文件句柄数组中读取。这是脚本:

#!/usr/local/bin/perl

use strict;
use warnings;

use Symbol;

die "Usage: $0 file1 [file2 ...]\n" unless scalar(@ARGV);

my @fhs;

foreach(@ARGV){
    my $fh = gensym;
    open $fh, $_ or die "Unable to open \"$_\"";
    push(@fhs, $fh);
}

while (scalar(@fhs)){
    my ($result, $n, $a, $i) = (0,0,0,0);
    while ($i <= $#fhs){
        if ($a = <$fhs[$i]>){
            $result += $a;
            $n++;
            $i++;
        }
        else{
            $fhs[$i]->close;
            splice(@fhs,$i,1);
        }
    }
    if ($n){ print $result/$n . "\n"; }
}

这行不通。如果我调试脚本,在我初始化 @fhs 后它看起来像这样:

  DB<1> x @fhs
0  GLOB(0x10443d80)
   -> *Symbol::GEN0
         FileHandle({*Symbol::GEN0}) => fileno(6)
1  GLOB(0x10443e60)
   -> *Symbol::GEN1
         FileHandle({*Symbol::GEN1}) => fileno(7)

到现在为止还挺好。但它在我尝试从文件中读取的部分失败:

  DB<3> x $fhs[$i]
0  GLOB(0x10443d80)
   -> *Symbol::GEN0
         FileHandle({*Symbol::GEN0}) => fileno(6)
  DB<4> x $a
0  'GLOB(0x10443d80)'

$a 填充了这个字符串,而不是从 glob 中读取的内容。我做错了什么?

4

4 回答 4

13

您只能在内部使用简单的标量变量<>从文件句柄中读取。 <$foo>作品。 <$foo[0]>不从文件句柄中读取;它实际上相当于glob($foo[0]). 您必须使用readline内置函数、临时变量或使用IO::File和 OO 表示法。

$text = readline($foo[0]);
# or
my $fh = $foo[0];  $text = <$fh>;
# or
$text = $foo[0]->getline;  # If using IO::File

如果您没有从循环内的数组中删除元素,则可以通过将while循环更改为循环来轻松使用临时变量foreach

就个人而言,我认为使用gensym创建文件句柄是一个丑陋的黑客。您应该使用 IO::File,或者将未定义的变量传递给open(至少需要 Perl 5.6.0,但现在已经快 10 年了)。(只要说my $fh;代替my $fh = gensym;,Perl 会自动创建一个新的文件句柄并在$fh您调用时将其存储在其中open。)

于 2010-01-07T19:39:37.137 回答
2

如果你愿意使用一点魔法,你可以很简单地做到这一点:

use strict;
use warnings;

die "Usage: $0 file1 [file2 ...]\n" unless @ARGV;

my $sum   = 0;

# The current filehandle is aliased to ARGV
while (<>) {
    $sum += $_;
} 
continue {
    # We have finished a file:
    if( eof ARGV ) {
        # $. is the current line number.
        print $sum/$. , "\n" if $.;
        $sum = 0;

        # Closing ARGV resets $. because ARGV is 
        # implicitly reopened for the next file.
        close ARGV;  
    }
}

除非您使用的是非常旧的 perl,否则gensym没有必要搞乱。IIRC、perl 5.6 和更新版本对正常的词法句柄很满意:open my $fh, '<', 'foo';

于 2010-01-07T21:17:56.377 回答
1

我很难理解你的逻辑。你想读取几个文件,其中只包含数字(每行一个数字)并打印它的平均值吗?

use strict;
use warnings;

my @fh;
foreach my $f (@ARGV) {
    open(my $fh, '<', $f) or die "Cannot open $f: $!";
    push @fh, $fh;
}

foreach my $fh (@fh) {
    my ($sum, $n) = (0, 0);
    while (<$fh>) {
        $sum += $_;
        $n++;
    }
    print "$sum / $n: ", $sum / $n, "\n" if $n;
}
于 2010-01-07T19:40:11.390 回答
1

似乎for循环更适合您,您实际上可以使用标准的读取(迭代)运算符。

for my $fh ( @fhs ) { 
    while ( defined( my $line = <$fh> )) {
        # since we're reading integers we test for *defined*
        # so we don't close the file on '0'
        #...
    }
    close $fh;
}

看起来您根本不想缩短循环。因此,while似乎是错误的循环习语。

于 2010-01-07T21:19:51.223 回答