Какой самый безопасный способ перебрать строки в файле с помощью Perl? - PullRequest
14 голосов
/ 23 сентября 2010

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

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" без возврата каретки?Цикл Эвана на самом деле более оборонительный, или Perl делает что-то сумасшедшее внутренне, что означает, что вам не нужно беспокоиться об этих вещах, и while() фактически завершается, только когда вы нажимаете eof?

Ответы [ 3 ]

18 голосов
/ 23 сентября 2010

Потому что

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

фактически компилируется до

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

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

>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

Значит, ты уже готов идти!

13 голосов
/ 23 сентября 2010

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

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

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

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

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 $_; ... }

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

1 голос
/ 23 сентября 2010

Хотя верно, что форма 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. У первых двух есть свои проблемы ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...