Какой самый защитный способ перебрать строки в файле с помощью Perl?

Я обычно перебираю строки в файле, используя следующий код:

open my $fh, '<', $file or die "Could not open file $file for reading: $!\n";
while ( my $line = <$fh> ) {
  ...
}

Однако в ответе на другой вопрос, Эван Кэрролл отредактировал мой ответ, изменив инструкцию while в:

while ( defined( my $line = <$fh> ) ) {
  ...
}

Его обоснование состояло в том, что если у вас есть строка, которая 0 (она должна быть последней строкой, иначе она будет иметь возврат каретки), тогда ваш while выйдет преждевременно, если вы использовали мое утверждение ($line будет установлено на "0", и возвращаемое значение из присваивания также будет "0", которое получает значение false). Если вы проверяете определенность, то вы не столкнетесь с этой проблемой. Это имеет смысл.

Итак, я попробовал. Я создал текстовый файл, последняя строка которого 0 без возврата каретки. Я провел его через мой цикл, и цикл не вышел преждевременно.

Тогда я подумал: "Ага, может быть, ценность не на самом деле 0, может быть, там есть что-то еще, что заворачивает!" Поэтому я использовал Dump() из Devel::Peek, и это то, что он мне дал:

SV = PV(0x635088) at 0x92f0e8
  REFCNT = 1
  FLAGS = (PADMY,POK,pPOK)
  PV = 0X962600 "0"\0
  CUR = 1
  LEN = 80

Мне кажется, что значение на самом деле является строкой "0", так как я получаю аналогичный результат, если я вызываю Dump() на скаляр, я явно установлен в "0" (единственное различие в Поле LEN - из файла LEN - 80, тогда как из скалярного LEN - 8).

Так что же сделка? Почему мой цикл while() не срабатывает преждевременно, если я передам ему строку, которая только "0" без возврата каретки? Является ли цикл Evan более защитным, или Perl делает что-то сумасшедшее внутренне, что означает, что вам не нужно беспокоиться об этих вещах, а while() на самом деле выходит только при нажатии eof?

Ответы

Ответ 1

Поскольку

 while (my $line = <$fh>) { ... }

фактически сводится к

 while (defined( my $line = <$fh> ) ) { ... }

Возможно, это было необходимо в очень старой версии perl, но не больше! Вы можете видеть это от запуска B:: Deparse на script:

>perl -MO=Deparse
open my $fh, '<', $file or die "Could not open file $file for reading: $!\n";
while ( my $line = <$fh> ) {
  ...
}

^D
die "Could not open file $file for reading: $!\n" unless open my $fh, '<', $file;
while (defined(my $line = <$fh>)) {
    do {
        die 'Unimplemented'
    };
}
- syntax OK

Итак, вы уже хорошо поехали!

Ответ 2

BTW, это описано в разделе "Операторы ввода/вывода" perldoc perlop:

В скалярном контексте оценка дескриптора файла в угловых скобках дает следующую строку из этого файла (включая новую строку, если таковая имеется), или "undef" в конце файла или при ошибке. Когда $/устанавливается в значение "undef" (иногда называемое режимом файла-slurp), и файл пуст, он возвращает "первый", а затем "undef" впоследствии.

Обычно вы должны назначить возвращаемое значение переменной, но есть одна ситуация, когда происходит автоматическое присвоение. Если и только если входной символ является единственной вещью внутри условного оператора "while" (даже если он замаскирован под цикл "for (;;)" ), значение автоматически присваивается глобальной переменной $_, уничтожая все был там ранее. (Это может показаться вам странным, но вы будете использовать конструкцию почти в каждом Perl script, который вы пишете.) Переменная $_ неявно локализована. Вам нужно будет поставить "local $_;" перед циклом, если вы хотите, чтобы это произошло.

Следующие строки эквивалентны:

while (defined($_ = <STDIN>)) { print; }
while ($_ = <STDIN>) { print; }
while (<STDIN>) { print; }
for (;<STDIN>;) { print; }
print while defined($_ = <STDIN>);
print while ($_ = <STDIN>);
print while <STDIN>;

Это также ведет себя аналогично, но избегает $_:

while (my $line = <STDIN>) { print $line }

В этих конструкциях цикла назначенное значение (независимо от того, является ли назначение автоматическим или явным), затем тестируется, чтобы определить, определено ли оно. Определенный тест позволяет избежать проблем, когда строка имеет строковое значение, которое Perl обрабатывает как ложное, например, "или" 0" без конечной новой строки. Если вы действительно хотите, чтобы такие значения завершили цикл, они должны быть проверены явно:

while (($_ = <STDIN>) ne '0') { ... }
while (<STDIN>) { last unless $_; ... }

В других булевых контекстах "<filehandle> " без явного "определенного" теста или сравнения вызывают предупреждение, если действует прагма "use warnings" или ключ командной строки -w (переменная $^ W).

Ответ 3

Хотя верно, что форма while (my $line=<$fh>) { ... } получает скомпилированный в while (defined( my $line = <$fh> ) ) { ... }, считается, что существует множество случаев, когда законное чтение значения "0" неверно истолковывается, если у вас нет явного defined в цикле или проверки возврата <>.

Вот несколько примеров:

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

my $str = join "", map { "$_\n" } -10..10;
$str.="0";
my $sep='=' x 10;
my ($fh, $line);

open $fh, '<', \$str or 
     die "could not open in-memory file: $!";

print "$sep Should print:\n$str\n$sep\n";     

#Failure 1:
print 'while ($line=chomp_ln()) { print "$line\n"; }:',
      "\n";
while ($line=chomp_ln()) { print "$line\n"; } #fails on "0"
rewind();
print "$sep\n";

#Failure 2:
print 'while ($line=trim_ln()) { print "$line\n"; }',"\n";
while ($line=trim_ln()) { print "$line\n"; } #fails on "0"
print "$sep\n";
last_char();

#Failure 3:
# fails on last line of "0" 
print 'if(my $l=<$fh>) { print "$l\n" }', "\n";
if(my $l=<$fh>) { print "$l\n" } 
print "$sep\n";
last_char();

#Failure 4 and no Perl warning:
print 'print "$_\n" if <$fh>;',"\n";
print "$_\n" if <$fh>; #fails to print;
print "$sep\n";
last_char();

#Failure 5
# fails on last line of "0" with no Perl warning
print 'if($line=<$fh>) { print $line; }', "\n";
if($line=<$fh>) { 
    print $line; 
} else {
    print "READ ERROR: That was supposed to be the last line!\n";
}    
print "BUT, line read really was: \"$line\"", "\n\n";

sub chomp_ln {
# if I have "warnings", Perl says:
#    Value of <HANDLE> construct can be "0"; test with defined() 
    if($line=<$fh>) {
        chomp $line ;
        return $line;
    }
    return undef;
}

sub trim_ln {
# if I have "warnings", Perl says:
#    Value of <HANDLE> construct can be "0"; test with defined() 
    if (my $line=<$fh>) {
        $line =~ s/^\s+//;
        $line =~ s/\s+$//;
        return $line;
    }
    return undef;

}

sub rewind {
    seek ($fh, 0, 0) or 
        die "Cannot seek on in-memory file: $!";
}

sub last_char {
    seek($fh, -1, 2) or
       die "Cannot seek on in-memory file: $!";
}

Я не говорю, что это хорошие формы Perl! Я говорю, что они возможны; особенно сбой 3,4 и 5. Обратите внимание на отказ без предупреждения Perl на номера 4 и 5. У первых двух есть свои проблемы...