2010-09-22 1 views
14

I boucle généralement par des lignes dans un fichier en utilisant le code suivant:Quel est le moyen le plus défensif de faire défiler des lignes dans un fichier avec Perl?

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

Cependant, in answering another question, Evan Carroll modifié ma réponse, en changeant ma déclaration while à:

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

Son raisonnement était que si vous avoir une ligne qui est 0 (il devrait être la dernière ligne, sinon il aurait un retour chariot) alors votre while quitterait prématurément si vous avez utilisé ma déclaration ($line serait réglé sur "0", et la valeur de retour de l'affectation serait donc également "0" qui sera évaluée à faux). Si vous vérifiez pour défini-ness, alors vous ne rencontrez pas ce problème. Cela prend tout son sens.

Alors je l'ai essayé. J'ai créé un fichier texte dont la dernière ligne est 0 sans retour chariot. Je l'ai couru dans ma boucle et la boucle ne s'est pas éteinte prématurément. Je pensais alors, "Aha, peut-être que la valeur n'est pas vraiment 0, peut-être y a-t-il quelque chose d'autre qui fout le pot!" Donc, je Dump() de Devel::Peek et voici ce qu'il m'a donné:

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

Cela me semble dire que la valeur est en fait la chaîne "0", que je reçois un résultat similaire si je l'appelle Dump() sur un scalaire I » ve explicitement mis à "0" (la seule différence est dans le champ LEN - du fichier LEN est 80, alors que de la LEN scalaire est 8).

Alors, quel est le problème? Pourquoi ma boucle while() ne quitte-t-elle pas prématurément si je lui passe une ligne "0" sans retour chariot? La boucle d'Evan est-elle réellement plus défensive, ou est-ce que Perl fait quelque chose de fou en interne, ce qui signifie que vous n'avez pas besoin de vous inquiéter de ces choses et que while() sort seulement quand vous frappez eof?

+1

Si vous cherchez à écrire du code défensif, utilisez un [tank] (http://en.wikipedia.org/wiki/Tank). –

+3

C'est pourquoi je ne modifierais pas le sens de la réponse de quelqu'un (je corrige uniquement les fautes de frappe évidentes). Ajoutez un commentaire à la place si vous pensez que quelque chose manque ou pourrait être amélioré. Et bravo à vous pour enquêter sur les internes! – Ether

Répondre

18

Parce que

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

fait jusqu'à compile

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

Il est peut-être nécessaire dans une version très ancienne de Perl, mais pas plus! Vous pouvez voir ceci de courir B :: Deparse sur votre 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 

Ainsi vous êtes déjà bon pour aller!

+1

PS, j'aime ... absolument aimer comment '...' est syntaxe valide en 5.12 et plus. Aimer. –

+0

Oh mon dieu! Je me demande si quelqu'un se fait mordre dans le cul par cette «définition» implicite? – zigdon

+0

Si quelqu'un écrit réellement du code qui vérifie si une ligne un-chomp() ed lue depuis un fichier sans fin de ligne est False de cette façon, elle obtient exactement ce qu'elle mérite. L'attitude DWIM de Perl fait généralement bien les choses. – geoffspear

13

BTW, cela est couvert dans l'E/S section Opérateurs de perldoc perlop:

Dans un contexte scalaire, évaluer un descripteur de fichier entre crochets donne la ligne suivante à partir de ce fichier (le retour à la ligne, le cas échéant, inclus), ou "undef" à la fin du fichier ou en cas d'erreur. Lorsque $/est défini sur "undef" (parfois appelé mode fichier-slurp) et que le fichier est vide, il renvoie "" la première fois, suivi de "undef" par la suite.

Normalement, vous devez affecter la valeur renvoyée à une variable, mais il existe une situation dans laquelle une affectation automatique a lieu.Si et seulement si le symbole d'entrée est la seule chose à l'intérieur du conditionnel d'une instruction "while" (même déguisée en boucle "for (;;)"), la valeur est automatiquement assignée à la variable globale $ _, détruisant tout était là auparavant. (Cela peut vous sembler étrange, mais vous utiliserez la construction dans presque tous les scripts Perl que vous écrivez.) La variable $ _ n'est pas implicitement localisée. Vous devrez mettre un "$ local"; avant la boucle si vous voulez que cela se produise.

Les lignes suivantes sont équivalentes:

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

Ce comportement similaire, mais évite $ _:

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

Dans ces constructions en boucle, la valeur attribuée (si l'affectation est automatique ou explicite) est ensuite testé pour voir s'il est défini. Le test défini évite les problèmes où la ligne a une valeur de chaîne qui serait considérée comme fausse par Perl, par exemple un "" ou un "0" sans saut de ligne. Si vous voulez réellement les valeurs de mettre fin à la boucle, ils doivent être testés pour explicitement:

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

Dans d'autres contextes booléens, « <filehandle> » sans un test explicite « défini » ou la comparaison ELICIT un avertissement si le "use warnings" pragma ou le commutateur de ligne de commande -w (la variable $^W) est actif.

+1

Bonne réponse donc j'ai supprimé le mien. Mais les documents Perl sont trompeurs - ils disent, "Si ** et seulement si ** le symbole d'entrée est la seule chose à l'intérieur du conditionnel d'une déclaration while" - mais contredisent plus tard la partie "et seulement si" en montrant que 'while (my $ line = )' se comporte également de la même manière. Laissant nous nous demandons exactement dans quelles circonstances cette DWIMmery sera exécutée. –

+1

@j_random: La partie "si et seulement si" ne fait pas référence à l'utilisation de $ _ comme emplacement pour la ligne lue depuis le descripteur, pas que le la logique "définie" est employée? – Ether

+0

Vous avez absolument raison, mauvaise compréhension de la lecture de ma part. Mes excuses. Je pense toujours qu'il ne serait pas douloureux d'être explicite sur exactement quand 'define' est auto-appliqué. Ma conjecture est: si le test conditionnel de la boucle est '' ou une affectation scalaire avec '' sur le RHS - est-ce tout cependant? –

1

Il est exact que la forme de while (my $line=<$fh>) { ... } obtient compiled-while (defined(my $line = <$fh>)) { ... } considèrent qu'il existe une variété de moments où une lecture légitime de la valeur « 0 » est mal interprété si vous ne disposez pas d'une defined explicite dans la boucle ou tester le retour de <>.

Voici quelques exemples:

#!/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: $!"; 
} 

Je ne dis pas que ce sont de bonnes formes de Perl! Je dis qu'ils sont possibles; en particulier Failure 3,4 et 5. Notez l'échec sans avertissement Perl sur les numéros 4 et 5. Les deux premiers ont leurs propres problèmes ...

Questions connexes