2010-09-22 1 views
14

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

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

Однако in answering another question, Evan Carroll отредактировал мой ответ, меняю 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() на скаляр I» ve явно установлен на "0" (единственное различие в поле LEN - из файла LEN - 80, тогда как из скалярного LEN - 8).

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

+1

Если вы хотите написать защитный код, используйте [tank] (http://en.wikipedia.org/wiki/Tank). –

+3

Вот почему я не отредактировал значение чей-то реакции (я только фиксирую очевидные опечатки). Добавьте комментарий вместо этого, если вы считаете, что что-то отсутствует или его можно улучшить. И вам, к счастью, для расследования внутренних дел! – Ether

ответ

18

Поскольку

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 

Итак, вы уже готовы к работе!

+1

PS, я люблю ... абсолютно ЛЮБЛЮ, как '...' является допустимым синтаксисом в 5.12 и выше. Любить это. –

+0

О, мой. Интересно, кто-нибудь когда-нибудь будет укушен в задницу этим неявным «определенным»? – zigdon

+0

Если кто-то действительно пишет код, проверяющий, что строка un-chomp() ed, прочитанная из файла без окончания строки, оценивает False таким образом, они получают именно то, что они заслуживают. Отношение DWIM от Perl, как правило, все в порядке. – geoffspear

13

Кстати, это рассматривается в разделе I/O Операторы perldoc perlop:

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

Обычно вы должны назначить возвращаемое значение переменной, но есть одна ситуация, когда происходит автоматическое присвоение.Если и только если входной символ является единственной вещью внутри условного оператора «while» (даже если он замаскирован под цикл «for (;;)»), значение автоматически присваивается глобальной переменной $ _, уничтожая все был там ранее. (Это может показаться вам странным, но вы будете использовать конструкцию почти для каждого скрипта Perl, который вы пишете.) Переменная $ _ неявно локализована. Вам нужно будет поставить «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 $_; ... } 

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

+1

Хороший ответ, поэтому я удалил свою. Но документы Perl вводят в заблуждение - они говорят: «Если ** и только если ** входной символ является единственной вещью внутри условного выражения while», - но позже противоречит части «и только если», показывая, что 'while (my $ line = )' также ведет себя одинаково. Оставив нас задуматься о том, в каких обстоятельствах этот DWIMmery будет выполнен. –

+1

@j_random: Не является ли частью «только если», ссылаясь на то, что $ _ используется как место для строки, считанной из дескриптора, а не Используется логика 'defined'? – Ether

+0

Вы абсолютно правы, плохое понимание прочитанного с моей стороны. Мои извинения. Я все еще думаю, что было бы не совсем ясно, когда именно «define» автоматически применяется. Мое предположение: если условный тест цикла равен «» или скалярное присваивание с «» на RHS - это все? –

1

Хотя это правильно, что форма while (my $line=<$fh>) { ... } получает compiled к 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. У первых двух есть свои проблемы ...

Смежные вопросы