Как я могу прочитать из файлового дескриптора Perl, который является элементом массива? - PullRequest
4 голосов
/ 07 января 2010

Я быстро набросал сценарий 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 заполнена этой строкой, а не чем-то прочитанным из глобуса. Что я сделал не так?

Ответы [ 4 ]

12 голосов
/ 07 января 2010

Вы можете использовать только простую скалярную переменную внутри <> для чтения из файлового дескриптора. <$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.)

2 голосов
/ 08 января 2010

Если вы хотите использовать немного магии, вы можете сделать это очень просто:

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';

1 голос
/ 08 января 2010

Кажется, что цикл 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 кажется неправильной идиомой цикла.

1 голос
/ 07 января 2010

У меня проблемы с пониманием вашей логики. Вы хотите прочитать несколько файлов, которые содержат только числа (по одному в строке) и вывести среднее значение?

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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...