La programation en Perl : Le couteau suisse

by Hal Pomeranz traduction Philippe Bereski

Un peu de philosophie

Je louais la gloire de Perl auprès d'un de mes collègues quand il me fit remarquer que Perl est plutôt contraire à la philosophie des outils UNIX. Chaqu'un a son propre avis sur le sujet, mais la combinaison d'outils simples pour en créer de plus complexes est généralement admise comme étant un principe de base d'UNIX. Perl, au contraire, a choisi de fournir un riche ensemble de fonctionnalités, qui à leur tour, émulent un large ensemble d'utilitaires UNIX. Bien sur Perl permet aussi d'invoquer facilement les divers outils UNIX - il ne serait pas un langage si intéressant autrement. Souvent même, la lisibilité de vos programmes pourrait être améliorée en invoquant différents utilitaires par open() ou avec des backquotes plutôt que de récrire la fonction en Perl. Cependant il y a de solides raisons qui font que cela pourait être sous-optimal.

Tout d'abord, il y a la sempiternelle efficassité. L'invocation d'un sous process a un coût important. Bien sur à partir du moment où la taille des données à traiter augmente, ce surcoût diminue en proportion. Comme pour toute optimisation, vous devez expérimenter : essayer différentes solutions sur un échantillon de données réelles pour trouver l'approche la plus efficasse.

La portabilité est un argument plus parlant à l'encontre de l'utilisation des outils que les constucteurs livrent avec leur système. Si vous avez déjà eu à maintenir un logiciel sur plusieurs plateformes, vous connaissez les difficultés qu'il y a à trouver l'outil dont vous avez besoin. Où se trouve donc la commande find - /bin, /usr/bin ou d'un autre coté obscure du système ? Accepte-elle les mêmes options sur toutes les plateformes ? Multipliez ces questions par le nombre d'utilitaires UNIX dont votre programme Perl peut avoir besoin et leur réimplémentation vous apparaîtra soudain moins coûteuse. Si vous maintenez la même version de Perl sur toutes vos plateformes vous aurez une base de travail commune. Entre autres objectifs, cet article explicite quelques méthodes simples pour émuler en Perl divers utilitaires UNIX.

Un air de famille

Certains utilitaires UNIX ont une fonction équivalente directement implémentée dans l'interpréteur Perl. C'est le cas pour chown(), chmod(), mkdir(), et rmdir(). On trouve également des fonctions link() and symlink() pour créer respectivement des liens durs et des liens symboliques de même que unlink() pour effacer un fichier et même rename() une fonction qui émule partiellement mv.

Les functions chown(), chmod() et unlink() opèrent sur une liste de fichiers et retournent le nombre de succès. Il est cependant parfois nécessaire de connaître la nature exacte des échecs. Dans ce cas, on pourra utiliser la boucle suivante sur chaque élément de la liste :

     for (@files) {
          chown(0644, $_) || warn "Can't change permissions on $_\n"; 
     }
La même méthode peut être utilisée pour les opérations ne travaillent pas directement sur des listes de fichiers.

Notez que si votre système ne supporte pas une des fonctions précédantes, vous rencontrerez des erreurs plus ou moins bien traitées. Par exemple, si les liens symboliques ne sont pas supportés, alors l'appel système symlink causera la mort de votre programme avec une erreur fatale à l'exécution. Comme pour toute fonction dont vous n'êtes pas certain, il est bon de l'enclore dans un eval() pour intercepter les éventuelles erreurs :

     eval "symlink($old, $new);";
     warn "Symlink not supported\n" if ($@);
Perl garantit que la variable $@ est nulle si eval() réussit, c'est un test tout à fait fiable.

Parfois Perl invoque simplement la commande standard si elle n'est pas disponible dans une bibliothèque du système. C'est le cas de mkdir. Il vaut alors mieux que vous invoquiez vous même cette commande avec la liste de répertoires à créer plutôt que de créer un sous process à chaque invocation de mkdir dans la boucle précédante.

Quelques subtilités

Certaines fonctions Perl ressemblent de près à des filtres UNIX. Par exemple, split() et substr() émulent de très près cut. Perl dispose d'une fonction sort interne bien plus puissante que le sort UNIX. Vous aurez cependant à définir vous même votre propre fonction de tri. (Voyez pour cela le premier article de cette série pour plus d'informations sur les tris en Perl). Parfois vous aurez à changer légèrement votre tournure d'esprit pour parvenir à ce que Perl fasse ce que vous souhaitez.

Par exemple, les programmeurs ont coutume d'utiliser basename et dirname pour obtenir le nom du programme invoqué (pour construire les messages d'erreurs) ainsi que le nom du répertoire où il a été chargé. Perl mémorise le chemin complet du nom du script invoqué dans la variable $0. basename et dirname peuvent être émulées avec les substitions d'expressions régulières suivantes :

     ($basename = $0) =~ s%.*/%%; 	
     ($dirname = $0) =~ s%/[^/]*$%%;
La première substitution utilise la recherche gourmande des motifs pour consommer tous les caractères jusqu'à la dernière occurence de '/' dans le path. Si le nom du fichier vous intéresse également, vous pouvez utiliser l'unique instruction suivante :
     ($dirname, $basename) = $0 =~ /(.*)\/(.*)/;
Ici aussi nous utilisons la recherche gourmande mais nous utilisons en plus la possibilité de retourner une liste de sous-expressions. L'instruction peut paraître étrange, mais l'ordre de précédence est correct.

     open(FILE, "< myfile") || die "Can't open myfile\n"; 	
     while (<FILE>) { 	 
          next if $seen{$_}++; 	 
          ...do some processing here... 	
     } 	
     close(FILE);
Notez que cette fonction peut consommer beaucoup de mémoire si le fichier est gros. En revanche, le tableau associatif %seen contient le nombre d'occurrences de chaque ligne ce qui est pratique si vous tenez à émuler uniq -c. Vous pouvez aussi exécuter sort si vous tenez absoluement à trier la sortie.

     open(FILE, "< myfile") || die "Can't open myfile\n"; 
     @lines = <FILE>; 	
     close(FILE); 	
     @found = grep(/$pattern/, @lines);
Ceci peut cependant être gourmand en mémoire pour des gros fichiers. Dans ce cas vous pouvez travailler séquentiellement :
     open(FILE, "< myfile") || die "Can't openmyfile\n"; 
     while (<FILE>) { 	 
          next unless (/$pattern/); 	 
          ...process here... 
     } 	
     close(FILE);
Si vous avez besoin de la liste des lignes contenant le motif, il vous suffit de les mémoriser par push dans le traitement de la boucle. Vous n'aurez pas à garder en mémoire l'intégralité du fichier.

La bibliothèque Perl

Depuis la distribution pl36, la bibliothèque Perl contient plusieurs packages émulant les outils UNIX les plus utiles. D'autres packages sont disponibles dans les archives coombs.anu.edu.au (150.203.76.2). Consultez les avant de réinventer la roue.

Un package s'utilise en le requérant puis en appelant les fonctions qu'il contient comme vous le feriez avec n'importe laquelle de vos propres fonctions. Par exemple le package ctime.pl offre des fonctionnalités similaires à celles de la commande UNIX date :

     require "ctime.pl"; 
     $date_str = &ctime(localtime);
Bien sur ceci ne procure pas les capacités de formatage de date. Mais vous pouvez toujours utiliser printf pour les émuler.

Dans la catégorie "facile à utiliser" on trouvera également getcwd.pl et fastgetcwd.pl pour vous aider à retrouver votre chemin dans l'arbre des répertroires. La fonction définie dans fastgetcwd.pl est plus efficace parce qu`elle utilise chdir() pour remonter le chemin jusqu'à la racine, par contre vous ne serez pas en mesure de revenir à votre point de départ. Pour ceux qui en C-shell aiment la variable $PWD, il y a la bibliothèque pwd.pl. Insérez simplement &initpwd() après avoir requis la bibliothèque et utilisez la conction &chdir() définie dans le package au lieu de la fonction interne à Perl. La fonction chdir() maintiendra en permanence à jour la variable d'environnement $PWD.

L'entrée la plus utile de la bibliothèque Perl est sans conteste find.pl. La réunion de toutes les options du find des différents UNIX est énorme, par contre leur intersection est souvent minimale. Essayer d'écrire une application portable utilisant find relève souvent de la recherche opérationnelle. find.pl a été écrite pour les besoins du programe find2perl livré avec la distribution de Perl mais vous pouvez l'utiliser pour vos propres programmes.

La fonction &find() définie dans la bibliothèque Perl accepte une liste de fichiers et de répertoires en argument et parcours cette liste comme le fait la commande find UNIX. Pour chaque item rencontré lors du parcours, &find() invoque la routine &wanted définie par l'utilisateur. Elle ne reçoit pas d'argument mais la variable $dir contient le nom du répertoire courant, $_ celui de l'item courant et $name le path complet "$dir/$_". Si $_ est un répertoire, la fonction &wanted peut définir à vrai la variable $prune pour empêcher &find de continuer la recursion dans $_.

À part ceci, l'exécution de &wanted reste entièrement sous le contrôle de l'utilisateur. L'utilisation judicieuse de stat() ou de lstat() et des opérateurs de test de Perl sur les fichiers permettent d'émuler la plupart des options de find proposées par les différents UNIX. ( N'oubliez pas que la variable $_ contient le résultat de la dernière invocation de stat() ou lstat(), qu'elle ait été invoquée directement ou par l'intermédiaire d'un opérateur de test sur les fichiers). Bien sur Perl est un langage plus riche et plus puissant que la syntaxe de la ligne de commande de find. Ainsi des effets de bords très puissants peuvent être obtenus. Pour en avoir une meilleur idée, faites tourner find2perl sur vos invocations favorites de find et étudiez attentivement sa sortie. (Il y a certainement une douzaine de bons candidats simplement dans /usr/spool/cron/crontabs/*).

Il y a une foule d'autres outils disponibles dans la bibliothèque Perl. Dans look.pl, vous trouvez une recherche dans un dictionnaire qui émule la commande UNIX look. Il y a même syslog.pl une interface à syslog pour le cas où vous n'aimeriez pas passer votre temps à appeler logger. D'autres bibliothèques sont postées dans comp.lang.perl et archivées sur coombs chaque jour.

Assez pour cette fois

Arrivé à ce point, nous espérons vous avoir convaincu que Perl est plus que capable d'émuler la plus part des outils UNIX simples (et même certains plus complexes, c'est le cas par exemple des traducteurs s2p et a2p livrés avec la distribution Perl). Parfois cependant vous aurez vraiment besoin d'appeler des outils au dehors de Perl. Par exemple je cherche toujours quelque chose de mieux que :
     chop($hostname = `/bin/hostname`);
La fois prochaine nous parlerons des stratégies à mettre en oeuvre pour écrire des scripts Perl portables sur l'affolante variété d'implémentations d'UNIX subtilement différentes les unes des autres dans leurs chemins et dans le comportement de leurs commandes. Le titre provisoire pourrait être "Ce que j'aime avec les standards c'est que vous avez toujours le choix entre plusieurs".

Traduction de ;login: Vol. 18 No. 6, décembre 1993.


Dernière édition: 21 décembre 1997 phb
Original 11/22/96pc
Back to the original
Retour à l'index