La programmation en Perl: Tout savoir sur les chevrons

Par Hal Pomeranz traduction Philippe Bereski

Perl 5 arrive!

Bonne nouvelle, fervents de Perl ! À l'heure ou j'écris ces mots, Larry Wall vient juste d'annoncer la livraison Alpha de Perl 5. Il a passé tous les tests de non régression de Perl 4 mais il ne dispose pas encore du script de configuration. Il ne peut donc encore être construit que sur un Sun Sparc. De nouveaux sites ftp le proposant apparaissent quotidiennement, voyez comp.lan.perl pour plus de détails. Aurons-nous une version stable de Perl 5 pour Noël ? Qui vivra verra ...

La manipulation des fichiers

La manipulation des fichiers est un aspect fondamental de Perl. Si vous avez déjà utilisé un tant soit peu le langage, vous êtes certainement plus que familiers de ce genre d'instructions :
     open(FILE, "< myfile") || die "Can't open 'myfile'\n"; 	
     while (<FILE>) { ... }
Cependant, vous n'avez peut-être pas saisi tout ce qui se cache derrière ces chevrons. Ils ne concernent pas uniquement les file handles.

Les arguments de la ligne de commande sont vus comme des noms de fichier.

Tout d'abord, nous avons un file handle spécial ARGV. Quand il est utilisé à l'intérieur d'une boucle de ce type :
     while (<ARGV>) { 	
          ... 	
     }
Chaque élément de la liste des arguments, @ARGV, est traité en tant que nom de fichier. Perl va essayer d'ouvrir chacun d'eux, d'en lire le contenu, puis de passer au suivant. Vous aurez un message d'erreur si l'un deux ne peut pas être ouvert, mais la boucle continuera jusqu'à épuisement du contenu de la liste. Si le programme a été exécuté sans argument, (i.e., si @ARGV est vide), alors la boucle précédente lira ses informations sur le standard input comme doit le faire tout bon programe UNIX. Au passage, puisque la construction est si courante, on peut écrire plus brièvement <>, ce qui a la même signification.

     while (<>) { 	
          ... 	
     }

Le scalaire $ARGV est associé au file handle ARGV. Il contient le nom du fichier courant ouvert. Nous pouvons utiliser ceci pour écrire un programme grep simpliste:

     $pat = shift @ARGV; 
     $many = @ARGV > 1; 
     while (<>) { 
          next unless /$pat/; 
          print "$ARGV:" if $many; 
          print;
     }
Le programme utilise shift() pour extraire le premier argument de la liste, le motif de la recherche. Tous les autres arguments sont traités comme des noms de fichier. Si plusieurs noms de fichiers sont passés en argument alors le nom du fichier courant est imprimé avant l'ensemble des lignes contenant le motif (exactement comme le fait le programe UNIX grep).

Le seul grain de sable avec cette technique de ARGV est que lorsque chaque fichier est ouvert, la variable spéciale $. qui contient le numéro de ligne courant n'est pas remise à zéro. Si cela vous pose un problème, utilisez l'astuce suivante :

     $oldname = ''; 
     while (<>) { 
          if ($ARGV ne $oldname) { 
               $lineno = 0; 
               $oldname = $ARGV; 	
          } 
          $lineno++; 
          ... 
     }
et utilisez $lineno en lieu et place de $.. Vous vous demandez peut-être pourquoi tout ce fatras avec $lineno plutôt que de simplement assigner $.. Tous simplement par ce que cela ne marche pas : $. ne peut être remise à zéro qu'à l'appel de close().

Indirection dans les File Handles

Une variable scalaire contenue dans des chevrons, <$file>, est une indirection dans le file handle. Perl essayera de lire ses données dans le fichier dont le nom est la chaîne de caractère contenue dans la variable <$file>. Par exemple :
     open(FILE, "/etc/motd") || die "Can't open /etc/motd\n"; 	
     $file = 'FILE'; 
     $line1 = <FILE>; 
     $line2 = <$file>;
En quoi cela est-il utile ? Premièrement ceci permet de passer élégamment des noms de fichier à des routines :
     open(FILE, "/etc/motd") || die "Can't open /etc/motd\n"; 
     &mysub(FILE);
     sub mysub { 
          local($file) = @_; 
          while (<$file>) { 
          ... 
          } 
     }
Deuxièmement, la chaîne contenue dans la variable n'a pas besoin d'être un identificateur valide. Elle peut même être, par exemple, le nom du fichier à ouvrir :
     for (0..8) {
          $file = "/var/adm/messages.$_"; 	 
          open($file, "$file") || die "Can't open $file\n"; 	
     }
ainsi nous pourrons faire des choses du genre :
     &do_something_with ("/var/adm/messages.0");

     sub do_something_with { 	 
          local($file) = @_; 
          while (<$file>) { 	
          ... 
          }
     } 
Ceci peut accroître considérablement la lisibilité de vos programme en particulier si vous avez de nombreux fichiers ouverts. Troisièmement, nous pouvons construire des listes ou des tableaux de références à des file handles.
     @myfiles = 0..7; 	
          for (@myfiles) { 
               open($myfiles[$_], "syslog.$_") || die "Can't open syslog.$_\n"; 	
     }
Notez que bien que nous avons pu utiliser une référence à un élément d'un tableau dans la fonction open de l'exemple précédant, nous ne pouvons utiliser qu'un scalaire à l'intérieur des chevrons. Nous devons donc déréférencer la variable @myfiles avant de l'utiliser:
     $file = $myfiles[3]; 
     $line = <$file>; 
     print "$line";

L'expansion des métas caractères

Si une chaîne à l'intérieur de chevrons n'est pas un file handle (direct ou indirect), elle est passée à un sous shell (au C shell si il existe au Bourne shell sinon) pour y être expansée. Il est possible d'utiliser l'expansion à l'intérieur d'une boucle pour récupérer un à un chaque nom des fichiers correspondants.
     while (<*.c>) { 
          print "Checking out $_...\n"; 
          system("co -l $_");
     } 
ou vous pouvez insérer tous les noms dans une liste :
     chmod 0644, <*.c>;
Cependant ne concluez pas faussement des deux exemples précédants que les métas caractères se comportent comme un file handle. Cet autre exemple
     $file1 = <*.c>; 
     print "$file1\n"; 
     $file2 = <*.c>; 
     print "$file2\n";
imprime deux fois le même nom de fichier (et fork un sous-shell deux fois également), au lieu d'imprimer le premier puis du second des noms correspondant à l'expansion. Prenez-y bien garde si vous ne voulez pas assister à des catastrophes.

L'interprétation des variables a lieu avant l'expansion des métas caractères, mais vous ne pouvez pas écrire <$glob> parce que c'est un file handle indirect. Il faut placer des accolades autour du nom de votre variable pour forcer son interpolation.

     $glob = "*.c"; 	
     @c_files = <${glob}>;
Pour ceux qui n'y auraient pas pris garde, nous venons juste de mettre en relief un des aspects sournois de Perl : une situation où $glob et ${glob} n'ont pas la même signification.

Comme Perl exécute un exec() pour forcer l'expansion dans un shell plutôt que d'employer une fonction d'expansion interne, il est plus efficace (en terme de vitesse d'exécution, mais peut-être pas en terme de lisibilité ou de taille du code) d'utiliser la fonction interne de parcours de répertoires:

     opendir(DIR, ".") || die "Can't open directory `.'\n";
     @c_files = grep(/\.c$/, readdir(DIR)); 
     closedir(DIR);
Notez que l'expansion retourne toujours les noms dans leur ordre alphabétique alors que le code ci-dessus ne le fait pas (vous pouvez toujours utiliser sort() pour trier la liste dans la méthode ci-dessus).

Conclusion

La construction <> est utile est devrait faire partie du bagage tout programmeur Perl. Les file handles indirects et les métas caractères sont moins fréquemment utilisés mais peuvent souvent améliorer la clarté et la lisibilité du code. Les file handles indirects en particulier peuvent être utilisés pour éviter d'obscurcir inutilement votre code. Souvenez vous, au moment où vous essayerez d'embrumer l'esprits des pauvres mortels, que dans ce bas monde, on a parfois besoin de maintenir son propre code.

Traduction de ;login: Vol. 18 No. 5, October 1993.


Dernière édition: 15 mars 1998 phb
Original 05/24/96ah
Back to the original
Retour à l'index