Quelle est la façon la plus défensive à boucle à travers les lignes dans un fichier avec Perl?

StackOverflow https://stackoverflow.com/questions/3773917

Question

I boucle généralement par lignes dans un fichier en utilisant le code suivant:

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

Cependant, pour répondre à une autre question , Evan Carroll édité ma réponse, en changeant ma déclaration de while à:

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

Son raisonnement était que si vous avez une ligne qui est 0 (il faudrait être la dernière ligne, sinon il aurait un retour chariot), votre while sortirait prématurément si vous avez utilisé ma déclaration ($line serait fixé à "0", et la valeur de retour de la mission serait donc aussi "0" qui obtient évalué à false). Si vous vérifiez-ness définie, vous ne courez pas ce problème. Il est parfaitement logique.

Alors je l'ai essayé. J'ai créé un fichier texte dont la dernière ligne est 0 sans retour chariot là-dessus. Je l'ai couru à travers ma boucle et la boucle n'a pas quitté prématurément.

J'ai alors pensé, « Aha, peut-être la valeur est en fait 0, peut-être il y a quelque chose d'autre que de visser les choses! » 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 semble me 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 que j'ai explicitement mis à "0" (la seule différence est dans le domaine LEN - de la LEN fichier est de 80, tandis que de la LEN scalaire est 8).

Alors, quelle est l'affaire? Pourquoi ne pas ma sortie de boucle de while() prématurément si je passe une ligne qui est seulement "0" sans retour chariot? Est-ce que la boucle de Evan fait plus défensif, ou fait quelque chose de fou Perl faire en interne cela signifie que vous ne devez pas vous inquiéter au sujet de ces choses et while() ne fait que la sortie lorsque vous appuyez sur eof?

Était-ce utile?

La solution

Comme

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

compile en fait jusqu'à

 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 cette course de 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

Vous êtes déjà bon pour vous!

Autres conseils

BTW, cela est couvert dans la section Opérateurs d'E / S 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 de fichier ou d'erreur. Lorsque $ / est réglé sur « undef » (parfois appelé mode fichier slurp) et le fichier est vide, il retourne « » la première fois, suivi de « undef » par la suite.

     

Normalement, vous devez attribuer la valeur renvoyée à une variable, mais il y a une situation où une affectation automatique qui se passe. Si et seulement si le symbole d'entrée est la seule chose dans la condition d'un « tout » déclaration (même si déguisé en « pour (;;) » boucle), la valeur est automatiquement attribuée à la variable $ _, détruisant tout était là auparavant. (Cela peut sembler une chose étrange à vous, mais vous utiliser la construction dans presque tous les scripts Perl que vous écrivez.) La variable $ _ est pas implicitement localisée. Vous devrez mettre un « local $ _; » avant que 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, est ensuite testé pour voir si elle est définie la valeur attribuée (si l'affectation est automatique ou explicite). Le test défini évite les problèmes en ligne a une valeur de chaîne qui serait traitée comme fausse par Perl, par exemple un « » ou un « 0 » sans retour à la ligne de fuite. 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, « » sans un test explicite « défini » ou la comparaison provoquent un avertissement si le pragma « use warnings » ou le commutateur de ligne de commande -w (la variable $ ^ W) est en vigueur .

Il est exact que la forme de while (my $line=<$fh>) { ... } obtient compilé à 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 non 3,4 et 5. Notez l'échec sans avertissement Perl sur le numéro 4 et 5. Les deux premiers ont leurs propres problèmes ...

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top