IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Programmation en PERL

de Larry Wall, Tom Christiansen et Jon Orwant


précédentsommairesuivant

IV. Culture Perl

22. CPAN

CPAN (Comprehensive Perl Archive Network, le réseau d'archives détaillées de Perl) est le dépôt central pour tout ce qui concerne Perl. Il contient la sagesse engrangée par la communauté Perl tout entière : des centaines de modules et de scripts Perl, une documentation représentant la valeur de plusieurs livres et la distribution complète de Perl. Si quelque chose est écrit en Perl, utile et libre, il est certainement sur CPAN. Il existe des miroirs de CPAN dans le monde entier et vous pouvez en trouver près de chez vous grâce au multiplexeur de CPAN, situé à l'adresse http://www.perl.com/CPAN/. Le multiplexeur se souviendra du miroir que vous avez choisi pour que vous y soyez automatiquement redirigé lorsque vous reviendrez sur http://www.perl.com/CPAN/ (faites attention au slash à la fin). Vous pouvez également commencer à www.cpan.org. L'interface est différente, mais les données sont les mêmes.

Une fois dans le répertoire principal de CPAN, vous verrez quelques sous-répertoires :

authors

  • Ce répertoire contient de nombreux sous-répertoires à raison de un par contributeur de logiciel. Par exemple, si vous désirez trouver le superbe module CGI de Lincoln Stein(164) et que vous êtes parvenus à savoir de source sûre qu'il en est l'auteur, vous pouvez regarder dans authors/Lincoln_Stein. Si vous ne savez pas qui l'a écrit, vous pouvez chercher dans le répertoire modules, décrit ci-dessous.

doc

  • Ce répertoire contient toutes les formes de documentation de Perl, y compris toutes les pages de manuel officielles de Perl dans plusieurs formats et arrangements différents, comme du texte, du HTML, du PostScript et du pod, le format natif de Perl, documenté au chapitre 26.

modules

  • Ce répertoire contient des modules écrits soit en Perl, soit en combinant Perl et C. Voir la discussion à propos du répertoire modules ci-dessous.

ports

  • Ce répertoire contient le code source et également quelques fois des images exécutables précompilées des portages de Perl vers des systèmes d'exploitation qui ne sont pas directement supportés dans la distribution standard ou pour lesquels il est de notoriété publique que les compilateurs sont difficiles à se procurer. Ces portages résultent des efforts individuels de leurs auteurs respectifs et peuvent ne pas fonctionner précisément comme ce livre l'indique. De nos jours, peu de systèmes nécessitent un portage spécifique. Il est toutefois intéressant de regarder le document d'index de ce répertoire, car il comprend également des informations détaillant le moment où chaque vendeur de systèmes a commencé à livrer Perl.

scripts

  • Ce répertoire contient une petite collection de divers programmes Perl provenant du monde entier. Ils sont très utiles en tant que programmes seuls et comme exemples (bien que le code ne soit pas assujetti aux vérifications de contrôle de qualité). En ce moment, il n'y a pas beaucoup de programmes répertoriés, mais nous espérons que cette zone s'enrichira avec le temps. Le projet Perl Power Tool (PPT, Puissants outils en Perl) se trouve également ici. Le projet PPT a pour but de recréer en Perl tous les utilitaires standards d'Unix. La plupart des outils standards sont déjà intégrés, plus quelques-uns qui ne sont pas standards.

src

  • Dans ce répertoire, vous trouverez le code source de la distribution standard de Perl. En fait, de deux distributions standards de Perl. L'une est indiquée comme stable et l'autre comme devel (version de développement). (La page d'index de ce répertoire explique tous les détails.) Il s'agit seulement de liens vers les versions appropriées. Au moment où nous écrivons, stable.tar.gz est un lien symbolique vers perl-5.6.1.tar.gz(165), mais il est susceptible de pointer vers une version plus élevée au moment où vous lirez ceci. Cet énorme fichier contient le code source complet et la documentation de Perl. La configuration et l'installation devraient être relativement simples sur la plupart des plates-formes. Si ce n'est pas le cas, voyez le répertoire ports décrit précédemment.

22-1. Le répertoire modules de CPAN

Bien que CPAN contiennent le code source complet de Perl, plus quelques distributions binaires pour les systèmes privés de compilateurs C, ainsi que quelques programmes, CPAN est surtout connu pour sa collection de modules.

Lorsque nous parlons de « modules », nous voulons dire trois choses : de purs modules Perl à 100 % (décrits aux chapitres 11 et 12), des extensions (des modules dépendant de code C, décrits au chapitre 21) et des pragmas (des modules contenant des instructions spéciales pour le compilateur de Perl, décrits au chapitre 31). Il existe également sur CPAN des bundles (groupements) de modules. Les bundles sont des collections de modules qui interagissent d'une manière ou d'une autre et qui sont généralement la production d'un développeur de modules désirant fournir une solution autonome à un ensemble de problèmes. Si un module dépend d'un autre (et éventuellement d'une version spécifique), les développeurs grouperont souvent les modules dans un bundle. Voir par exemple Bundle-XML.

Une façon de parcourir les modules de CPAN est de vous rendre à l'adresse http://search.cpan.org, qui fournit un frontal de moteur de recherche pour CPAN. Une autre manière est de vous rendre sur votre miroir local de CPAN et d'entrer dans le répertoire modules, où vous verrez trois sous-répertoires : by-authors, by-category et by-module (par auteur, par catégorie et par module). Le répertoire by-module peut être le plus utile si votre navigateur possède une fonctionnalité de recherche — bien que (lamentablement) certains modules ne sont disponibles que dans les répertoires sous author. Si vous recherchez par catégorie, vous obtiendrez les choix suivants :

Perl core modules, langage extensions, and documentation tools

  • (Modules centraux de Perl, extensions de langage et outils pour la documentation)
    Cette catégorie comprend des pragmas et d'autres modules standards, des modules qui vous aident à écrire différemment en Perl, des modules en rapport avec le compilateur de Perl, des filtres pour le code source et des modules en rapport avec pod, le format de documentation de Perl. Cette catégorie comprend également des modules pour générer du bytecode Java.

Development support

  • (Support du développement)
    Cette catégorie comprend des modules pour en créer d'autres et examiner comment Perl lance des programmes.

Operating system interfaces and hardware drivers

  • (Interfaces de systèmes d'exploitation et pilotes matériels)
    Vous trouverez ici des modules pour interagir avec des entités étranges comme des systèmes d'exploitation, des PalmPilots ou des ports série.

Networking, device control and interprocess communication

  • (Communication réseau, contrôle de périphériques et communication interprocessus)
    Ceci comprend des modules implémentant des protocoles réseau, manipulant des données réseau, maniant des modems et contrôlant les appareils électroménagers de votre maison.

Data types and data type utilities

  • (Types de données et utilitaires pour les types de données)
    Cette catégorie contient des modules pour les maths, les statistiques, les algorithmes, les structures de données (et leurs sauvegardes persistantes), les dates et les heures, la programmation orientée objet, le PDL (le langage de données de Perl (Perl Data Language)) et le POE (l'environnement objet de Perl (Perl Object Environment), un ordonnanceur événementiel orienté objet).

Database interfaces

  • (Interfaces de bases de données)
    Vous trouverez ici des modules vous permettant d'exploitant plusieurs douzaines de systèmes de bases de données en Perl, la plupart d'entre elles avec le système DBI de Perl. Cette catégorie inclut les modules DBD spécifiques à chaque base de données.

User interfaces (character and graphical)

  • (Interfaces utilisateur (en mode caractères et graphique))
    Cette catégorie comprend des modules pour manipuler les terminaux utilisateur (édition de la ligne de commande et graphiques en mode caractères dans le style de curses(3)), ainsi que Perl/Tk et les liens avec Gtk, Gnome, Sx et Qt pour écrire vos propres interfaces graphiques en Perl.

Interfaces to or emulations of other programming languages

  • (Interfaces vers ou émulations d'autres langages de programmation)
    Cette catégorie contient des modules pour utiliser d'autres langages de programmation depuis Perl ou vous permettre de prétendre que Perl n'est pas ce qu'il prétend être. Si vous êtes intéressés par utiliser Perl en C ou C en Perl, voir le chapitre 21.

Filenames, filesystems, and file locking

  • (Noms de fichier, systèmes de fichiers et verrous de fichier)
    Cette catégorie comprend des modules pour inspecter, créer, verrouiller et tout ce qui concerne la manipulation de fichiers et de répertoires.

String processing, language text processing, parsing, and searching

  • (Traitement de chaînes et traitement, analyse et recherche de langages textuels)
    Cette catégorie contient des modules pour manipuler du texte : placer des césures, enrober, analyser, rechercher des variations grammaticales et faire des recherches. Elle comprend des modules pour manipuler du PostScript, des fontes, du XML et du RTF.

Option, argument, parameter, and configuration file processing

  • (Traitement d'options, d'arguments, de paramètres et de fichiers de configuration)
    Cette catégorie contient des modules pour traiter les arguments de la ligne de commande (le -x dans mon_prog_perl -x) et pour gérer des fichiers de configuration (comme les fichiers commençant par un point).

Internationalization and locale

  • (Internationalisation et locales)
    Cette catégorie comprend des modules pour tailler sur mesure vos programmes pour un pays et une langue particuliers.

Authentication, security, and encryption

  • (Authentification, sécurité et cryptage)
    Vous trouverez ici des modules pour gérer des mots de passe d'utilisateurs, calculer des signatures de messages, crypter des données et authentifier des utilisateurs.

World Wide Web, HTML, HTTP, CGI, MIME

Server and daemon utilities

  • (N.d.T. Vous avez compris sans traduction, non ?)
    Cette catégorie contient des modules vous permettant de créer des pages web à base de CGI, des arpenteurs web et des systèmes de gestion de contenus basés sur le web. D'autres modules vous permettent de gérer les cookies, d'analyser du HTML et des messages MIME et de manipuler des caches web. Il existe également une section spéciale uniquement consacrée aux modules Perl que vous pouvez intégrer dans le serveur web Apache.
  • (Utilitaires pour les serveurs et les démons)
    Cette catégorie comprend des modules pour créer des serveurs de réseau et d'événements.

Archiving, compression, and conversion

  • (Archivage, compression et conversion)
    Vous trouverez ici des modules pour manipuler des fichiers zip et tar et pour faire des conversions entre des formats de fichiers (même le format de fichier d'Apple II).

Images, pixmap, and bitmap manipulation, drawing, and graphing

  • (Manipulations d'images, de cartes de pixels et de bits, dessins et graphiques)
    Cette catégorie contient des modules pour créer des graphiques, des GIF, du VRML et travailler avec Gimp.

Mail and Usenet news

  • (N.d.T. Vous avez compris sans traduction, non ?)
    Dans cette catégorie, vous trouverez des modules pour envoyer, recevoir et filtrer les mails et les news.

Control flow utilities

  • (Utilitaires de contrôle de flux)
    Cette catégorie contient des modules pour exécuter du code Perl de temps à autre.

Filehandle, directory handle, and input/output stream utilities

  • (Utilitaires pour les handles de fichiers, les handles de répertoires et les flux d'entrées/sorties)
    Ici se trouvent des modules pour les entrées depuis des fichiers et les sorties dans des fichiers, y compris des fichiers journaux (log). Ceci comprend tous les modules IO:: et un module Expect pour automatiser les dialogues avec des services réseau ou d'autres programmes interactifs.

Microsoft Windows modules

  • (N.d.T.  Vous avez compris sans traduction, non ?)
    Cette catégorie comprend des modules pour manipuler la base de registre de Windows, ASP, ODBC, OLE et d'autres technologies spécifiques à Windows.

Miscellaneous modules

  • (Modules divers)
    Vous trouverez ici des modules pour l'astronomie, la biologie, la chimie, la validation (ou l'invalidation) de cartes de crédit, les amortissements d'hypothèques, l'audio, la vidéo, la norme MIDI, la météo et les jeux.

22-2. Utilisation des modules de CPAN

La plupart des modules que vous trouverez sur CPAN sont dans un format « tarball ». C'est-à-dire qu'ils possèdent une extension de fichier en tar.gz et qu'ils sont décompressés dans un répertoire avec le code du module et tous les fichiers annexes, comprenant généralement un fichier README et un fichier Makefile.PL.

Quatre étapes rendent accessibles les modules de CPAN depuis vos programmes : la décompression, le dépaquetage, la construction et l'installation. La manière dont fonctionne chacune de ces étapes dépend de votre système d'exploitation et du module que vous installez, nous ne pouvons donc pas vous donner de recette à toute épreuve qui fonctionnerait à chaque fois. Lorsque vous doutez, lisez les fichiers README et INSTALL qui sont, espérons-le, distribués avec le module. Lisez également la page de manuel de perlmodinstall.

Mais il se peut que vous n'ayez jamais à réfléchir à la procédure d'installation si vous utilisez le module CPAN (livré avec la distribution de Perl) ou PPM (Perl Package Manager, le gestionnaire de paquetages Perl, livré avec la distribution ActiveState de Perl). Pour utiliser le module CPAN (à ne pas confondre avec CPAN, lui-même), tapez :

 
Sélectionnez
% perl -MCPAN -e "shell"

sur votre ligne de commande pour commencer le processus de configuration. Après avoir répondu à une variété de questions sur la manière dont vous préférez récupérer des fichiers, vous pouvez installer un module en tapant :

 
Sélectionnez
install Un::Module

dans le shell du module CPAN, ou en tapant :

 
Sélectionnez
% perl -MCPAN -e "install 'Un::Module'"

sur votre ligne de commande ordinaire.

Si vous ne disposez pas des avantages pratiques du module CPAN ou de PPM, vous devrez passer à la main les étapes décrites dans les paragraphes suivants. Les instructions sont données pour Unix, Windows et Macintosh ; pour d'autres systèmes d'exploitation, consultez la page de manuel de perlmodinstall.

22-2-a. Décompression et dépaquetage des modules de CPAN

La plupart des milliers d'utilitaires sur CPAN sont compressés pour occuper moins de place. Une fois que vous avez récupéré le tarball d'un module, vous devez tout d'abord le transformer en une arborescence de répertoires sur votre système en décompressant (« dézippant » ) et en dépaquetant le tarball. Sur Unix, vous pouvez utiliser gzip et tar pour faire cela. (Sur la plupart des systèmes, tar fera les deux.) Sur Windows, WinZip décompressera et dépaquetera à la fois les tarballs. Sur un Macintosh, vous pouvez utiliser StuffIt, DropStuff, MacGzip ou suntar.

22-2-b. Construction des modules de CPAN

Une minorité de modules de CPAN viennent avec du code C que vous devrez compiler pour votre système, ce qui est naturellement un problème pour les systèmes qui ne disposent pas d'un compilateur C. La procédure standard pour construire un module de CPAN (avec ou sans code C) réside dans les trois commandes suivantes, exécutées à partir de la ligne de commande. (Si vous n'avez pas de ligne de commande ou d'équivalent de make, vous devrez avoir recours à des mesures plus drastiques et plus dépendantes du système. Les utilisateurs de Windows possèdent une ligne de commande, mais peuvent avoir besoin d'utiliser l'utilitaire nmake à la place de make.)

 
Sélectionnez
% perl Makefile.PL 
% make 
% make test

La commande perl Makefile.PL essayera de créer un Makefile, qu'utiliseront les commandes make qui suivent pour déterminer quels utilitaires doivent être construits et dans quel ordre. La dernière commande, make test, lance la série de tests que par bonheur l'auteur du module a incluse.

22-2-c. Installation des modules de CPAN dans la bibliothèque Perl

Si vous avez suivi les étapes précédentes, vous avez maintenant un module qui a été construit et testé, mais pas encore installé dans la bibliothèque de Perl. Lorsque Perl a été installé sur votre système, un répertoire lib a été créé pour contenir des modules, des pragmas et les fichiers qui s'y rapportent. Il s'agit de la bibliothèque Perl, qui se situe généralement à un endroit comme /usr/local/lib/perl5 sur les systèmes Unix et C:\PERL\LIB par défaut sur les systèmes Windows. Les modules installés après que Perl a été construit sont stockés dans le sous-répertoire site_perl de la bibliothèque Perl. Vous pouvez voir les noms de tous les répertoires de votre bibliothèque (et un tas d'autres choses) en écrivant :

 
Sélectionnez
% perl -V

Pour installer le module, tapez :

 
Sélectionnez
% make install

Cela nécessite normalement d'être super-utilisateur, même pour installer des modules dans les répertoires de la bibliothèque Perl spécifiques à votre site.

Avec un peu de travail, vous pouvez installer le module dans un répertoire hors de votre bibliothèque Perl (comme votre répertoire maison). Au lieu de taper normalement perl Makefile.PL pour créer un Makefile, vous pouvez plutôt utiliser cette incantation :

 
Sélectionnez
% perl Makefile.PL LIB=/mon/rep/perllib \
        INSTALLMAN1DIR=/mon/rep/man/man1 \
        INSTALLMAN3DIR=/mon/rep/man/man3 \
            INSTALLBIN=/mon/rep/bin \
         INSTALLSCRIPT=/mon/rep/scripts

Ceci installera les modules quelque part dans le répertoire /mon/rep/perllib et tous les fichiers restants là où ils doivent aller. (Si vous vous trouvez en train de taper cela souvent, vous pouvez même écrire un petit programme Perl pour le faire à votre place. Perl est très bon pour accomplir des choses comme celles-là.)

Vous pouvez ensuite indiquer à Perl de rechercher les modules dans votre répertoire spécial en ajoutant :

 
Sélectionnez
use lib "/mon/rep/perllib";

avant que votre programme ne tente de charger le module. Vous pouvez également positionner la variable d'environnement PERL5LIB à ce répertoire ou utiliser l'option de Perl -I. Voir le pragma use lib au chapitre 31, qui donne des exemples pour faire cela.

22-3. Création de modules pour CPAN

Si vous avez un module dont vous pensez qu'il puisse être utile aux autres, envisagez de rendre le monde meilleur en le téléchargeant sur CPAN. Le serveur qui gère les soumissions de nouveaux modules est appelé PAUSE (Perl Authors Upload Server, le serveur de téléchargement des auteurs Perl) et on peut le trouver sur https://pause.kbx.de/pause/. Avant de pouvoir télécharger votre module, vous devrez obtenir un compte sur PAUSE. C'est un peu comme devenir un « développeur Perl répertorié ».

Si vous vous considérez vous-même comme un développeur Perl répertorié, vous devriez en savoir assez pour documenter vos modules. Perl possède une convention pour inclure la documentation à l'intérieur de votre code source. (De cette manière, vous ne l'égarez jamais.) Cette documentation incluse se trouve dans un format appelé « pod » (pour Plain Old Documentation, bonne vieille documentation) et est décrite au chapitre 26.

Vous devriez envisager de sécuriser votre module du point de vue des threads. Voir le chapitre 17, Threads.

Vous devriez également vous inquiéter quelque peu si votre joli petit module fait ou non des choses risquant de perturber la sécurité des gens qui l'utilisent, car les autres peuvent avoir de véritables bonnes raisons d'être plus concernés par la sécurité que vous ne l'êtes (POUR L'INSTANT). Voir le chapitre 23, Sécurité, pour tout savoir comment éviter d'être tenu responsable de la 3e Guerre Mondiale et d'autres nuisances du même acabit.

Les modules destinés à être distribués sur CPAN doivent inclure un programme Perl appelé Makefile.PL qui, lorsqu'on le lance, génère un Makefile, ainsi qu'un fichier README décrivant brièvement ce que fait le module et comment l'installer. Le Makefile s'attend à comporter également une série de tests. Vous pouvez créer tous ces fichiers d'un coup grâce à l'utilitaire h2xs :

 
Sélectionnez
h2xs -X -n Truc::Machin

(Remplacez -X par -A si vous construisez un module comprenant un composant XS. XS est décrit au chapitre 21.) Le programme h2xs crée un seul répertoire avec des squelettes de fichiers que vous devrez garnir de chair. Lorsque vous avez terminé, vous pouvez télécharger le répertoire dans un tarball sur PAUSE.

Le Makefile.PL généré par h2xs ressemblera à quelque chose comme ceci :

 
Sélectionnez
use ExtUtils::MakeMaker;
# See lib/ExtUtils/MakeMaker.pm for details of how to influence 
# the contents of the Makefile that is written. 
WriteMakefile(
    NAME         => 'Mon_test',
    VERSION_FROM => 'Mon_test.pm', # finds $VERSION
    LIBS         => [''], # e.g., '-lm'
    DEFINE       => '',   # e.g., '-DHAVE_SOMETHING'
    INC          => '',   # e.g., '-I/usr/include/other' 
);

N.d.T. Voir /lib/ExtUtils/MakeMaker.pm pour les détails sur comment influencer le contenu du Makefile généré.

La première chose que fait le Makefile.PL est de charger le module ExtUtils::MakeMaker. La fonction WriteMakefile de MakeMaker possède une pléthore d'options (avec une pléthore valant approximativement 88) pour vous aider à personnaliser ce qui se passe après que l'utilisateur a téléchargé votre module depuis CPAN et a tapé perl Makefile.PL pour commencer sa construction. La bonne nouvelle à propos de tout ceci est que, puisque l'utilisateur est supposé lancer le perl qui sera utilisé plus tard, vous disposez d'une large étendue d'informations de configuration disponibles (via le module Config ou la variable spéciale $^O) pour vous aider à décider comment diriger MakeMaker. D'un autre côté, MakeMaker est vraiment excellent pour trouver des valeurs par défaut satisfaisantes pour presque tout, ainsi le fichier squelette écrit par h2xs peut très bien contenir tout ce dont vous avez besoin avec peut-être un ou deux ajustements. Pour plus d'informations à ce sujet, voir la documentation en ligne très complète de ExtUtils::MakeMaker.

Lorsque vous écrivez un module Perl destiné à un usage général, vous devriez permettre que la version de Perl de l'utilisateur diffère de la vôtre. Vous devriez toujours inclure une description en anglais de toutes les dépendances (les versions particulières de Perl, des prérequis du système ou d'autres modules) dans le fichier README. De toute façon, cela ne suffira peut-être même pas, puisque quelqu'un qui utilise le module CPAN pour télécharger et installer automatiquement votre module ne verrait jamais cet avertissement. Vous devriez donc vérifier les dépendances dans le Makefile.PL. Voilà comment vous pourriez vous assurer que la personne qui télécharge votre module lance Perl 5.6 ou une version supérieure :

 
Sélectionnez
eval { require 5.6.0 }
    or die << 'FIN_DOC'; 
############ 
### This module requires lvaluable subroutines, which are not available 
### in versions of Perl earlier than 5.6. Please upgrade!
############ 
FIN_DOC

N.d.T. Ce module nécessite des sous-programmes pris comme lvalues, qui ne sont pas disponibles dans les versions de Perl antérieures à 5.6. Merci de mettre à niveau !

22-3-a. Tests internes

Les instructions standards pour installer un module disent à l'utilisateur de lancer make test après avoir construit le module avec make. Merci donc d'inclure un script de test décent avec tout module que vous téléchargez vers CPAN. Vous devriez émuler le style ok/not ok que Perl emploie dans sa propre série de tests, afin que l'utilisateur détermine facilement le résultat de chaque test unitaire. Le fichier test.pl généré par h2xs vous aidera à démarrer. Le chapitre 21 donne des exemples de tests que vous pouvez ajouter à la fin de test.pl.

Si vous avez beaucoup de tests unitaires, vous pouvez avoir envie de singer la série de tests de Perl en créant un sous-répertoire appelé t/ dans le répertoire du module et d'ajouter l'extension .t au nom de vos différents scripts de test. Lorsque vous lancerez make test, tous les fichiers de tests seront exécutés automatiquement.

22-3-b. Tests externes

Les modules téléchargés vers CPAN sont testés par une variété de volontaires sur différentes plates-formes. Ces testeurs de CPAN sont notifiés par email de chaque téléchargement nouveau et répondent à la liste de diffusion par PASS, FAIL, NA (non applicable sur cette plate-forme) ou UNKNOWN (ne sait pas), avec les remarques appropriées. Vous pouvez trouver la liste de diffusion des testeurs de CPAN sur cpan-testers@perl.org ; les résultats des tests sont postés sur http://testers.cpan.org/.

Il ne s'agit que de la phase de test préliminaire, bien entendu. La véritable phase de test commence lorsque quelqu'un branche votre petit module sur un serveur web qui croule sous un million de pages vues par jour. Ou lors de l'utilisation de votre module pour mettre au point l'avion que vous emprunterez un jour prochain.

Alors foncez, sautez l'écriture de ces petits tests minables. Et voyez si l'on s'en fiche...

23. Sécurité

Que vous ayez affaire à un utilisateur assis à son clavier et tapant des commandes ou à quelqu'un envoyant des informations sur le réseau, la prudence s'impose pour ce qui est des données arrivant dans vos programmes, car l'autre personne peut, de manière malveillante ou accidentelle, vous envoyer des données qui feront plus de mal que de bien. Perl fournit un mécanisme spécial de vérification de la sécurité, appelé mode de marquage, permettant d'isoler des données marquées (N.d.T. Comme étant douteuses) afin de ne pas les utiliser mal à propos. Par exemple, si vous faites confiance par erreur à un nom de fichier marqué, vous pouvez vous retrouver en train d'ajouter une entrée à votre fichier de mots de passe alors que vous pensiez le faire sur un fichier de journal. Le mécanisme de marquage est exposé à la section Gestion des données non sûres.

Dans les environnements multitâches, des actions, dans les coulisses, par des acteurs que l'on ne voit pas peuvent affecter la sécurité de votre propre programme. Si vous présupposez que vous êtes propriétaire exclusif d'objets externes (en particulier des fichiers) comme si vos processus étaient les seuls sur le système, vous vous exposez à des erreurs substantiellement plus subtiles que celles provenant directement de la gestion de données ou de code dont la provenance est douteuse. Perl vous aide alors quelque peu en détectant des situations hors de votre contrôle. Mais pour celles dont vous avez le contrôle, il vous faudra comprendre quelles sont les approches à l'épreuve des étrangers invisibles. La section Gestion des problèmes de synchronisation discute de ces questions.

Si la donnée que vous avez reçue d'un étranger s'avère être un morceau de code source à exécuter, vous devez être encore plus prudent que vous ne le seriez avec une donnée. Perl fournit des vérifications pour intercepter du code furtif déguisé en donnée pour que vous ne l'exécutiez pas à votre insu. Si vous ne voulez pas exécuter de code étranger, le module Safe vous permet pourtant de mettre le code suspect en quarantaine là où il ne peut plus faire aucun mal et où il pourrait même faire du bien. Ce sont là les thèmes de la section Gestion du code non sûr.

23-1. Gestion des données non sûres

Perl facilite une programmation sécurisée même lorsque le programme est utilisé par quelqu'un de moins fiable que le programme lui-même. C'est-à-dire que certains programmes ont besoin d'accorder des privilèges limités à leurs utilisateurs, sans accorder inutilement d'autres privilèges. Les programmes setuid et setgid tombent dans cette catégorie sur Unix, comme d'autres programmes tournant avec des modes de privilèges variés sur d'autres systèmes d'exploitation qui implémentent de telles notions. Même sur les systèmes où ce n'est pas le cas, les mêmes principes s'appliquent aux serveurs réseau et à tout programme lancé par ces serveurs (comme les scripts CGI, les gestionnaires de listes de diffusion et les démons répertoriés dans /etc/inetd.conf). Tous ces programmes exigent un niveau de vigilance plus élevé que la moyenne.

Même les programmes lancés à partir de la ligne de commande sont parfois de bons candidats pour le mode de marquage, tout particulièrement s'ils sont destinés à être lancés par un utilisateur privilégié. Les programmes agissant sur des données non fiables, comme ceux qui génèrent des statistiques à partir de fichiers journaux ou qui utilisent LWP::* ou Net::* pour récupérer des données distantes, devraient probablement être lancés avec le marquage explicitement activé ; les programmes imprudents risquent d'être changés en « chevaux de Troie ». Puisque les programmes ne retirent aucune émotion à prendre des risques, il n'y a pas de raison pour qu'ils ne soient pas précautionneux.

Au regard des shells de la ligne de commande sur Unix, qui ne sont véritablement que des infrastructures pour appeler d'autres programmes, Perl est facile à programmer de manière sécurisée, car il est direct et autonome. À l'inverse de la plupart des langages de programmation shells, qui sont basés sur de nombreuses et mystérieuses passes de substitution sur chaque ligne du script, Perl emploie un modèle d'évaluation plus conventionnel comportant moins d'inconvénients cachés. De plus, comme le langage intègre plus de fonctionnalités internes, il dépend moins de programmes externes (dont la fiabilité peut être mise en doute) pour accomplir son devoir.

Sous Unix, le berceau de Perl, le meilleur moyen de compromettre la sécurité du système est de couvrir de flatteries un programme privilégié pour qu'il fasse quelque chose pour lequel il n'est pas prévu. Pour parer de telles attaques, Perl a développé une approche unique pour faire face aux environnements hostiles. Perl active automatiquement le mode de marquage chaque fois qu'il détecte que son programme tourne avec des ID d'utilisateur ou de groupe réels et effectifs différents.(166) Même si le fichier contenant votre script Perl ne possède pas de bit setuid ou setgid activé, ce script peut toujours se retrouver lancé en mode de marquage. Cela se produit si votre script a été invoqué par un autre programme, qui lui-même était lancé sous des ID différents. Les programmes Perl qui ne sont pas conçus pour fonctionner en mode de marquage tendent à expirer prématurément lorsqu'ils sont surpris à violer la politique de marquage de sécurité. Perl n'est pas si crédule.

Vous pouvez également activer explicitement le mode de marquage en utilisant l'option de la ligne de commande -T. Vous devriez le faire pour les démons, les serveurs et les programmes qui tournent pour le compte de quelqu'un d'autre, comme les scripts CGI. Les programmes qui se lancent à distance et anonymement par quelqu'un sur le Net sont exécutés dans le plus hostile des environnements. Vous ne devriez pas craindre de dire « Non ! » de temps en temps. Contrairement à la croyance populaire, vous pouvez faire preuve d'une grande prudence sans pour autant vous transformer en prude faussement effarouchée.

Sur les sites les plus consciencieux en matière de sécurité, lancer tous les scripts CGI avec l'option -T n'est pas seulement une bonne idée : c'est la règle. Nous ne prétendons pas que lancer les scripts en mode de marquage est suffisant pour les rendre sécurisés. Ce n'est pas le cas et cela prendrait un livre entier pour seulement mentionner tout ce qu'il faudrait faire. Mais si vous n'exécutez pas vos scripts CGI en mode de marquage, vous faites inutilement l'impasse sur la protection la plus forte que Perl puisse vous offrir.

Pendant qu'il se trouve en mode de marquage, Perl prend des précautions particulières appelées vérifications du marquage (taint checks) pour se prémunir contre les pièges qu'ils soient triviaux ou subtils. Certaines de ces précautions sont assez simples, comme de vérifier que les variables d'environnement dangereuses ne sont pas positionnées et que les répertoires de votre PATH ne sont pas modifiables par quelqu'un d'autre ; les programmeurs prudents ont toujours procédé ainsi. D'autres vérifications sont toutefois mieux assurées par le langage lui-même et ce sont celles-là qui contribuent à rendre un programme Perl privilégié plus sûr que le programme C correspondant ou un script CGI en Perl plus sécurisé que le même script dans un langage sans vérifications de marquage. (C'est-à-dire, pour autant que nous le sachions, tous les langages à part Perl.)

Le principe est simple : vous ne pouvez pas utiliser des données dérivées de l'extérieur d'un programme pour affecter quelque chose d'autre en dehors de ce programme — du moins, pas accidentellement. Tout ce qui provient de l'extérieur du programme est marqué, ce qui comprend tous les arguments de la ligne de commande, les variables d'environnement et les fichiers en entrée. Les données marquées ne peuvent être utilisées directement ou indirectement dans toute opération invoquant un sous-shell ou modifiant des fichiers, des répertoires ou des processus. Toute variable positionnée dans une expression ayant précédemment référencé une valeur marquée devient elle-même marquée, même s'il est logiquement impossible que la valeur marquée ait influencé la variable. Le marquage étant associé à chaque valeur scalaire, certaines valeurs individuelles d'un tableau ou d'un hachage peuvent être marqués et d'autres non. (Seules les valeurs dans un hachage peuvent être marquées et non les clefs.)

Le code suivant illustre comment le marquage fonctionnerait si vous exécutiez toutes ces instructions dans l'ordre. Les instructions indiquées « Non sûres » lèveraient une exception, contrairement à celles qui sont « OK ».

 
Sélectionnez
$arg = shift(@ARGV);     # $arg est maintenant marqué (à cause de @ARGV).
$cache = "$arg, 'truc'";     # $cache est aussi marqué (à cause de $arg).
$ligne = <>;              # Marqué (lu à partir d'un fichier externe).
$chemin = $ENV{PATH};        # Marqué à cause de %ENV, mais voir plus loin.
$perso = 'abc';              # Non marqué.

system "echo $perso";        # Non sûr avant que le PATH ne soit positionné.

system "echo $arg";          # Non sûr : utilise sh avec $arg marqué.
system "echo", $arg;         # OK une fois que le PATH est positionné
                             # (n'utilise pas sh).
system "echo $cache";        # Non sûr de deux façons : marqué et PATH.

$vieux_chemin = $ENV{PATH};  # $vieux_chemin est marqué (à cause de %ENV).
$ENV{PATH} = 'bin:/usr/bin'; # (R on d'autres programmes.) $nouveau_chemin = $ENV{PATH}; # $nouveau_chemin n'est PAS marqué.

delete @ENV{qw{IFS
               CDPATH
               ENV
               BASH_ENV}};   # Rend %ENV plus sûr.

system "echo $perso"; # OK, est sûr une fois que le PATH est réinitialisé.
system "echo $cache"; # Non sûr via $cache qui est marqué.

open(OUF, "< $arg"); # OK (ouvertures en lecture seule non vérifiées).
open(OUF, "> $arg");    # Non sûr (tentative d'écriture dans arg qui est
                        # marqué).

open(OUF, "echo $arg|") # Non sûr via $arg qui est marqué, mais...
    or die "impossible de faire un pipe sur echo : $!";

open(OUF,"-|")           # Considéré OK : voir ci-dessous pour
    or exec 'echo', $arg # l'exemption de marquage sur une liste
    or die "exec echo impossible : $!"; # exec-utée.

open(OUF,"-|", "echo", $arg) # Comme précédemment, considéré OK.
    or die "impossible de faire un pipe sur un echo : $!";

$cri = `echo $arg`;    # Non sûr via $arg qui est marqué.
$cri = `echo abc`;     # $cri est marqué à cause des apostrophes inverses.
$cri2 = `echo $cri`;   # Non sûr via $cri qui est marqué.

unlink $perso, $arg;   # Non sûr via $arg qui est marqué.
umask $arg;            # Non sûr via $arg qui est marqué.

exec "echo $arg";      # Non sûr via $arg qui est marqué et passé au shell.
exec "echo", $arg;     # Considéré OK ! (Mais voir ci-dessous.)
exec "sh", '-c', $arg; # Considéré OK, mais à tort !

Si vous essayez de faire quelque chose de non sûr, vous obtiendrez une exception (qui devient une erreur fatale si elle n'est pas interceptée) comme « Insecure dependency » ou « Insecure $ENV{PATH} » (« Dépendance non sûre » ou « $ENV{PATH} non sûr ». Voir la section Nettoyage de votre environnement plus loin.

Si vous passez une LISTE à un system, un exec ou un open de pipe, les arguments ne sont pas inspectés pour vérifier s'ils sont marqués, car, avec une LISTE d'arguments, Perl n'a pas besoin d'invoquer le shell, qui est potentiellement dangereux, pour lancer la commande. Vous pouvez toujours écrire facilement un system, un exec ou un open de pipe non sûr en utilisant la forme avec LISTE, comme le démontre le dernier exemple ci-dessus. Ces formes sont exemptes de vérification, car vous êtes supposés savoir ce que vous faites lorsque vous les employez.

Parfois, vous ne pouvez toutefois pas dire combien d'arguments vous êtes en train de passer. Si vous fournissez à ces fonctions un tableau(167) qui ne contient qu'un élément, c'est alors exactement comme si vous passiez une chaîne en premier et le shell pourrait alors être utilisé. La solution est de passer un chemin explicite dans l'emplacement pour un objet indirect :

 
Sélectionnez
system @args# N'appellera pas le shell sauf si @args == 1. 
system { $args[0] } @args# Court-circuite le shell même avec une liste
                            # d'un seul argument.
23-1-a. Détection et blanchiment des données marquées

Pour tester si une variable scalaire contient ou non une donnée marquée, vous pouvez utiliser la fonction est_marquee suivante. Elle utilise le fait que eval CHAINE lève une exception si vous essayez de compiler des données marquées. Cela ne fait rien que la variable $nada employée dans l'expression à compiler soit toujours vide ; elle sera encore marquée si $arg l'est. Le eval BLOC extérieur n'effectue aucune compilation. Il n'est ici que pour intercepter l'exception levée si l'on donne une donnée marquée à l'eval intérieur. Puisqu'il est garanti que la variable $@ n'est pas vide après chaque eval si une exception a été levée et vide sinon, nous renvoyons le résultat du test vérifiant si sa longueur était nulle :

 
Sélectionnez
sub est_marquee {
    my $arg = shift;
    my $nada = substr($arg, 0, 0); # longueur nulle
    local $@# préserve la version de l'appelant
    eval { eval "# $nada" };
    return length($@) != 0; 
}

Mais tester le marquage ne vous en dit pas plus. Généralement, vous savez parfaitement quelles variables contiennent des données marquées — il vous suffit de nettoyer le marquage des données. Le seul moyen officiel de court-circuiter le mécanisme de marquage consiste à référencer des variables de sous-motifs déterminés par une précédente recherche de correspondance d'expression régulière.(168) Lorsque vous écrivez un motif contenant des parenthèses capturantes, vous pouvez accéder aux sous-chaînes capturées par le biais des variables de correspondance comme $1, $2 et $+ ou en évaluant le motif dans un contexte de liste. Quel que soit le moyen employé, on présume que vous étiez au courant de ce que vous étiez en train de faire lorsque vous avez écrit le motif et que vous l'avez écrit de manière à éviter tout danger. Vous devez donc réfléchir un peu — ne blanchissez jamais aveuglément ou c'est tout le mécanisme qui sera invalidé.

Il vaut mieux vérifier que la variable ne contient que des caractères valides au lieu de regarder si elle contient des caractères invalides. Ceci, car il est beaucoup trop facile d'en rater auxquels vous n'auriez jamais pensé. Par exemple, voici un test destiné à s'assurer que $chaine ne contient que des caractères « de mot » (alphabétiques, numériques et soulignés), des tirets, des signes et des points :

 
Sélectionnez
if ($chaine =~ /^([-\@\w.]+)$/) {
    $chaine = $1;                    # $chaine est maintenant non marquée.
} else { 
   die "Mauvaises données dans $chaine"; # Enregistrez ceci quelque part. 
}

Ceci rend $chaine raisonnablement fiable pour l'utiliser ensuite dans une commande externe, puisque /\w+/ ne correspond généralement pas à des métacaractères du shell, et que les autres caractères ne veulent rien dire de particulier pour le shell.(169) Il aurait été dangereux d'utiliser, /(.+)/ car ce motif laisse tout passer, ce que Perl ne vérifie pas. Au moment du nettoyage, soyez extrêmement prudent avec vos motifs. Le blanchiment des données en utilisant des expressions régulières représente le seul mécanisme interne approuvé pour nettoyer de sales données. Et parfois, cette approche est entièrement mauvaise. Si vous êtes en mode de marquage, car vous tournez avez set-id et non parce que vous avez intentionnellement activé -T, vous pouvez limiter les risques en lançant avec fork un fils avec des privilèges moindres ; voir la section Nettoyage de votre environnement.

Le pragma use re 'taint' désactive le nettoyage implicite de toutes les correspondances de motifs jusqu'à la fin de la portée lexicale courante. Vous pourriez utiliser ce pragma si vous vouliez seulement extraire quelques sous-chaînes d'une donnée potentiellement marquée ; mais puisque ne vous êtes pas inquiété de la sécurité, vous feriez mieux de laisser les sous-chaînes marquées pour vous préserver des accidents malchanceux à venir.

Imaginez que vous trouviez une correspondance avec quelque chose comme ceci, avec $chemin_complet marqué :

 
Sélectionnez
($rep, $fichier) = $chemin_complet =~ m!(.*/)(.*)!s;

Par défaut, $rep et $fichier seraient maintenant marqués. Mais vous ne vouliez probablement pas faire cela de manière si cavalière, car vous n'avez jamais vraiment réfléchi aux problèmes de sécurité. Par exemple, vous ne seriez pas follement heureux si $fichier contenait la chaîne « ; rm -rf * ; », pour ne citer qu'un exemple tout à fait remarquable. Le code suivant laisse les deux variables renvoyées marquées si $chemin_complet l'était :

 
Sélectionnez
{
    use re 'taint';
    ($rep, $fichier) = $chemin_complet =~ m!(.*/)(.*)!s; 
}

Une bonne stratégie consiste à laissez les sous-correspondances marquées par défaut dans le fichier source entier et de ne permettre d'enlever le marquage que sélectivement dans des portées imbriquées si le besoin s'en fait sentir :

 
Sélectionnez
use re 'taint'; # le reste du fichier laisse maintenant $1, etc. marqués 
{
    no re 'taint';
    # ce bloc enlève maintenant le marquage dans les
    # correspondances d'expressions régulières
    if ($num =~ /^(\d+)$/) {
        $num = $1; 
    } 
}

Une entrée depuis un handle de fichier ou de répertoire est automatiquement marquée, sauf si elle provient du handle de fichier spécial DATA. Si vous le souhaitez, vous pouvez désigner d'autres handles comme des sources dignes de confiance via la fonction untaint du module IO::Handle :

 
Sélectionnez
use IO::Handle;

IO::Handle::untaint(*UN_HF);    # Soit de manière procédurale
UN_HF->untaint();                # soit en utilisant un style OO.

Désactiver le marquage sur un handle de fichier complet est un geste dangereux. Comment savez-vous vraiment qu'il est sûr ? Si vous vous apprêtez à faire cela, vous devriez au moins vérifier que personne à part le propriétaire ne peut écrire dans le fichier.(170) Si vous êtes sur un système de fichiers Unix (et un qui restreint prudemment chown (2) au super-utilisateur), le code suivant fonctionne :

 
Sélectionnez
use File::stat;
use Symbol 'qualify_to_ref';

sub handle_semble_sur(*) {
    my $hf = qualify_to_ref(shift, caller);
    my $info = stat($hf);
    return unless $info;

    # le propriétaire n'est ni le super-utilisateur, ni « moi »,
    # dont l'uid réel est dans la variable $<
    if ( $info->uid != 0 && $info->uid != $< ) {
        return 0;
    }
    # vérifie si le groupe ou les autres peuvent écrire dans le fichier
    # utilise 066 pour détecter également la possibilité de lire
    if ( $info->mode & 022 ) {
        return 0;
    }
    return 1;
}

use IO::Handle;
UN_HF->untaint() if handle_semble_sur(*UN_HF);

Nous avons appelé stat sur le handle de fichier et non sur le nom de fichier pour éviter une situation de concurrence (race condition) dangereuse. Voir la section Gestion des situations de concurrence plus loin dans ce chapitre.

Remarquez que cette routine n'est qu'un bon point de départ. Une version légèrement plus paranoïaque aurait également vérifié tous les répertoires parents, même si vous ne pouvez pas faire un stat sur un handle de répertoire de manière fiable. Mais si l'un des répertoires parents est en écriture pour tout le monde, vous savez que vous êtes en danger qu'il y ait ou non des situations de concurrence.

Perl a ses propres notions en ce qui concerne les opérations qui sont dangereuses, mais il est toujours possible de se plonger dans les ennuis avec d'autres opérations qui se moquent d'utiliser ou non des valeurs marquées. Il ne suffit pas toujours d'être prudent avec les entrées. Les fonctions de Perl pour les sorties ne testent pas si leurs arguments sont marqués ou non, mais, dans certains environnements, cela compte. Si vous ne faites pas attention à ce que vous écrivez en sortie, vous pourriez tout bonnement finir par cracher des chaînes ayant une signification inattendue pour quiconque traitant la sortie. Si vous tournez sur un terminal, les séquences d'échappement spéciales et les codes de contrôle pourraient provoquer un affichage erratique sur le terminal. Si vous êtes dans un environnement web et que vous recrachez sans précaution tout ce que l'on vous donne, vous pourriez produire de manière imprévisible des balises HTML qui déformeraient drastiquement l'apparence de la page. Pire encore, certaines balises peuvent même en retour exécuter du code dans le navigateur.

Imaginez le cas courant d'un livre d'or où les visiteurs entrent leurs propres messages à afficher lorsque d'autres viennent appeler. Un invité malicieux pourrait fournir des balises HTML disgracieuses ou mettre des séquences <SCRIPT>...</SCRIPT> exécutant du code (comme du JavaScript) en retour dans le navigateur des visiteurs suivants.

Tout comme vous avez vérifié que tous les caractères sont autorisés, lorsque vous recevez une donnée marquée qui pourrait accéder aux ressources de votre système, vous pouvez effectuer une vérification analogue dans un environnement web lorsque vous recevez des données d'un internaute. Par exemple, pour supprimer tous les caractères absents de la liste des bons caractères, essayez quelque chose comme :

 
Sélectionnez
$nouvelle_entree_du_livre_d_or =~ tr[_a-zA-Z0-9 ,.!?()@+*-][]dc;

Vous n'utiliseriez certainement pas cela pour nettoyer un nom de fichier, puisque vous ne voulez probablement pas de noms de fichiers avec des espaces ou des slashes, pour commencer. Mais il n'est pas suffisant de préserver votre livre d'or vierge de toute balise HTML sournoise et de toute entité perfide. Chaque cas de blanchiment de donnée est quelque peu différent, passez donc toujours du temps à décider ce qui est permis et ce qui ne l'est pas. Le mécanisme de marquage est prévu pour intercepter les erreurs stupides, pas pour se dispenser de réfléchir.

23-1-b. Nettoyage de votre environnement

Lorsque vous exécutez un autre programme depuis votre script Perl, peu importe comment, Perl vérifie pour s'assurer que votre variable d'environnement PATH soit sécurisée. Puisqu'il provient de votre environnement, votre PATH commence par être marqué, ainsi si vous essayez de lancer un autre programme, Perl lève une exception « Insecure $ENV{PATH} ». Lorsque vous le positionnez à une valeur connue, non marquée, Perl s'assure que chaque répertoire dans ce PATH ne soit pas accessible en écriture par quelqu'un d'autre que le propriétaire et le groupe du répertoire ; sinon, Perl lance une exception « Insecure directory ».

Vous seriez surpris d'apprendre que Perl se préoccupe de votre PATH même lorsque vous spécifiez le nom de chemin complet de la commande que vous voulez exécuter. Il est vrai qu'avec un nom de fichier absolu, le PATH n'est pas utilisé pour localiser l'exécutable. Mais Perl se méfie de ce que le programme exécuté pourrait en lancer un autre avec un PATH non sûr. Perl vous force donc à positionner un PATH sécurisé avant d'appeler un autre programme, quelle que soit la manière dont vous l'appelez.

Le PATH n'est pas la seule variable d'environnement pouvant causer du chagrin. Comme certains shells utilisent les variables IFS, CDPATH, ENV et BASH_ENV, Perl s'assure que toutes celles-ci sont soit vides, soit sans marquage avant de lancer une autre commande. Positionnez ces variables à quelque chose que l'on sait être sûr ou alors supprimez-les de l'environnement :

 
Sélectionnez
delete ENV{qw(IFS CDPATH ENV BASH_ENV)}; # Rend %ENV plus sûr

Des fonctionnalités accommodantes dans un environnement normal peuvent devenir des problèmes de sécurité dans un environnement hostile. Même si vous vous rappelez avoir interdit les noms de fichiers contenant des sauts de ligne, il est important de comprendre que open accède à autre chose de plus que seulement des noms de fichiers. Avec les ornements appropriés dans l'argument représentant le nom de fichier, les appels à open avec un ou deux arguments peuvent également lancer des commandes arbitraires externes via des pipes, « forker » des copies supplémentaires du processus courant, dupliquer des descripteurs de fichiers et interpréter le nom de fichier spécial « -» comme un alias de l'entrée ou de la sortie standard. Il peut également ignorer les espaces au début ou à la fin qui peuvent masquer de tels arguments fantaisistes à vos motifs de test. Bien qu'il soit vrai que les vérifications de marquage de Perl intercepteront les arguments marqués utilisés dans les ouvertures de pipe (sauf si vous utilisez un argument de liste séparé) et toutes les ouvertures de fichiers qui ne soient pas en lecture seule, l'exception levée par ceci est toujours susceptible de faire que votre programme se conduise mal.

Si vous avez l'intention d'utiliser une donnée dérivée de l'extérieur dans une partie d'un nom de fichier à ouvrir, incluez au moins un mode explicite séparé par un espace. Cependant, le plus sûr est certainement d'utiliser un sysopen de bas niveau ou la forme à trois arguments de open :

 
Sélectionnez
# Ouverture magique ---peut être n'importe quoi 
open(HF, $fichier) or die "open $fichier magique, impossible : $!"; 

# Garantit l'ouverture d'un fichier en lecture seule et non un pipe 
# ou un fork, mais interprète toujours les descripteurs de 
# fichiers et « - » et ignore les espaces aux extrémités du nom. 
open(HF, "< $fichier") or die "open $fichier, impossible : $!"; 

# Ouverture WYSIWYG : désactive toutes les fonctionnalités accommodantes. 
open(HF, "<", $fichier) or die "open $fichier, impossible : $!"; 

# Mêmes propriétés que la version à 3 arguments WYSIWYG. 
require Fcntl; 
sysopen(HF, $fichier, O_RDONLY) or die "sysopen $fichier, impossible : $!";

Et même ces étapes ne sont pas assez bonnes. Perl ne vous empêche pas d'ouvrir des noms de fichiers marqués en lecture, vous devez donc être prudent avec ce que vous montrez aux gens. Un programme qui ouvre en lecture un nom de fichier arbitraire, fourni par l'utilisateur, pour ensuite révéler le contenu de ce fichier, est toujours un problème de sécurité. Que se passe-t-il s'il s'agit d'une lettre privée ? Et s'il s'agit du fichier de mots de passe de votre système ? Ou des informations sur les salaires ou votre portefeuille d'actions ?

Examinez de près les noms de fichier fournis par un utilisateur potentiellement hostile(171) avant de les ouvrir. Par exemple, vous pourriez vouloir vérifier qu'il n'y a pas de répertoires sournois dans votre PATH. Les noms comme « ../../../../../../../etc/passwd » sont des coups fourrés du genre qui sont de notoriété publique. Vous pouvez vous protéger en vous assurant qu'il n'y a pas de slash dans le nom du chemin (en supposant qu'il s'agit du séparateur de répertoire de votre système). Une autre astuce courante est de mettre des sauts de ligne ou des points-virgules dans les noms de fichiers qui seront interprétés ensuite par de pauvres interpréteurs de commandes simples d'esprit, pouvant être bluffés en commençant une nouvelle commande au beau milieu du nom de fichier. C'est pourquoi le mode de marquage déconseille les commandes externes non inspectées.

23-1-c. Accès aux commandes et aux fichiers avec des privilèges restreints

La discussion qui suit est relative à des facilités astucieuses concernant la sécurité sur les systèmes Unix. Les utilisateurs d'autres systèmes peuvent en toute sécurité (ou plutôt en toute insécurité) passer ce paragraphe.

Si vous tournez avec set-id, essayez d'arranger cela, à chaque fois que c'est possible, pour que les opérations dangereuses soient accomplies avec les privilèges de l'utilisateur et non ceux du programme. C'est-à-dire qu'à chaque fois que vous allez appeler open, sysopen, system, des apostrophes inverses et toute autre opération sur un fichier ou sur un processus, vous pouvez vous protèger en repositionnant votre UID ou votre GID effectif avec l'UID ou le GID réel. En Perl, vous pouvez faire cela pour les scripts setuid en écrivant $> = $< (ou $EUID = $UID si vous avez fait use English) et pour les scripts setgid en écrivant $( = $) ($EGID = $GID). Si les deux ID sont positionnés, vous devez réinitialiser les deux. De toute façon, c'est parfois infaisable, car vous pourriez encore avoir besoin de ces privilèges accrus ultérieurement dans votre programme.

Pour ces cas-là, Perl procure un moyen relativement sûr d'ouvrir un fichier ou un pipe depuis un programme set-id. Tout d'abord, lancez un fils avec la syntaxe spéciale de open qui connecte le père et le fils par un pipe. Dans le fils, repositionnez ensuite ses ID d'utilisateur et de groupe à leur valeur originale ou à des valeurs connues et sûres. Vous devrez également modifier tous les attributs de processus du fils sans affecter le père, vous permettant de changer le répertoire de travail courant, positionner le masque de création de fichiers ou bricoler les variables d'environnement. Le processus fils, qui ne s'exécute plus sous des privilèges accrus, appelle finalement open et passe les données auxquelles il a accédé sous couvert d'un utilisateur ordinaire, mais dément, à son père, puissant, mais paranoïaque de manière justifiée.

Bien que system et exec n'utilisent pas le shell lorsque vous leur fournissez plus d'un argument, l'opérateur apostrophes inverses n'admet pas de telle convention d'appel alternative. En utilisant la technique lançant un processus fils, nous pouvons facilement émuler les apostrophes inverses sans nous soucier des séquences d'échappements du shell et avec des privilèges réduits (et donc plus sûrs) :

 
Sélectionnez
use English;  # pour employer $UID, etc. 
die "open fork impossible : $!"
    unless defined ($pid = open(DEPUIS_FILS, "-|")); 
if ($pid) {  # père
    while (<DEPUIS_FILS>) {
        # faire quelque chose 
    }
    close DEPUIS_FILS; 
} else {
    $EUID = $UID; # setuid(getuid())
    $EGID = $GID; # setgid(getgid()) et initgroups(2) sur getgroups(2)
    chdir("/")   or die "chdir à / impossible : $!";
    umask(077);
    $ENV{PATH} = "/bin:/usr/bin";
    exec 'mon_prog', 'arg1', 'arg2';
    die "exec mon_prog impossible : $!"; 
}

Il s'agit du meilleur moyen, et de loin, d'appeler un programme depuis un script set-id. Vous êtes sûr de ne pas utiliser le shell pour exécuter quoi que ce soit et vous abandonnez vos privilèges avant d'appeler vous-même exec pour lancer le programme. (Mais puisque les formes avec liste de system, exec et open sur un pipe sont spécifiquement exemptes des vérifications de marquage, vous devez toujours prendre garde à ce que vous leur passez.)

Si nous n'avez pas besoin d'abandonner vos privilèges et voulez seulement implémenter les apostrophes inverses ou un open sur un pipe sans risquer que le shell intercepte vos arguments, il vous suffit d'utiliser ceci :

 
Sélectionnez
open(DEPUIS_FILS, "-|") or exec("mon_prog", "arg1", "arg2")
    or die "impossible de lancer mon_prog : $!";

puis de lire dans DEPUIS_FILS dans le père. Avec la version 5.6.1 de Perl, vous pouvez l'écrire comme ceci :

 
Sélectionnez
open(DEPUIS_FILS, "-|", "mon_prog", "arg1", "arg2");

La technique consistant à lancer un fils n'est pas seulement utile pour lancer des commandes depuis un programme set-id. Elle s'avère également bonne pour ouvrir des fichiers sous l'ID de quiconque a lancé le programme. Imaginez que vous avez un programme setuid qui a besoin d'ouvrir un fichier en écriture. Vous ne voulez pas lancer le open avec vos privilèges supplémentaires, mais vous ne pouvez pas non plus les abandonner définitivement. Arrangez-vous alors pour avoir une copie « forkée » qui abandonne ses privilèges pour effectuer le open pour vous. Lorsque vous voulez écrire dans le fichier, écrivez au fils et ce dernier écrira dans le fichier à votre place.

 
Sélectionnez
use English;

defined( $pid = open( ECRIVAIN_SUR, "|-" ) )
  or die "fork impossible : $!";
if ($pid) {

    # Vous êtes dans le père.
    # Écrivez les données au fils ECRIVAIN_SUR.
    print ECRIVAIN_SUR "donnes_en_sortie\n";
    close ECRIVAIN_SUR
        or die $! ? "Erreur système en fermant ECRIVAIN_SUR : $!"
                  : "Statut d'attente $? depuis ECRIVAIN_SUR";
} else {
    # Vous êtes dans le fils.
    # Abandonnez donc vos privilèges supplémentaires.
    ( $EUID, $EGID ) = ( $UID, $GID );

    # Ouvrez le fichier avec les droits de l'utilisateur original.
    open( HF, ">/un/chemin/de/fichier" 
        or die "open /un/chemin/de/fichier en écriture, impossible : $!" );

    # Copiez depuis le père (maintenant stdin) vers le fichier.
    while (<STDIN>) {
        print HF, $_;
    }
    close(HF) or die "Échec de close : $!";
    exit;    # N'oubliez pas de faire disparaître ECRIVAIN_SUR.
}

Lorsqu'il échoue dans l'ouverture du fichier, le fils affiche un message d'erreur et se termine. Lorsque le père écrit dans le handle de fichier du fils maintenant décédé, cela déclenche un signal de pipe brisé (SIGPIPE) qui est fatal s'il n'est pas intercepté ou ignoré. Voir la section Signaux au chapitre 16, Communication interprocessus.

23-2. Gestion des problèmes de synchronisation

Parfois, le comportement de vos programmes est très sensible à la synchronisation d'événements extérieurs hors de votre contrôle. Il s'agit toujours d'un problème lorsque d'autres programmes, particulièrement ceux qui sont inamicaux, pourraient se battre avec votre programme pour les mêmes ressources (comme des fichiers ou des périphériques). Dans un environnement multitâche, vous ne pouvez pas prédire l'ordre dans lequel les processus en attente d'activation vont être autorisés à accéder au processeur. Les flux d'instructions parmi tous les processus éligibles s'entrecroisent, ainsi un processus dispose tout d'abord d'un peu de CPU, ensuite un autre, et ainsi de suite. Il semble aléatoire de savoir celui dont c'est le tour d'être actif et combien de temps il sera autorisé à l'être. Avec un seul programme, il n'y a pas de problème, mais avec plusieurs, partageant des ressources communes, cela peut en être un.

Les programmeurs de « threads » sont particulièrement sensibles à ces questions. Ils apprennent vite à ne plus écrire :

 
Sélectionnez
$var++ if $var == 0;

lorsqu'ils veulent dire :

 
Sélectionnez
{
    lock($var);
    $var++ if $var == 0; 
}

Le premier exemple donne des résultats imprévisibles lorsque plusieurs threads d'exécution tentent de lancer ce code en même temps. (Voir le chapitre 17, Threads.) Si vous pensez aux fichiers comme à des objets partagés et aux processus comme à des threads candidats pour l'accès à ces objets partagés, vous pourrez voir comment les mêmes questions sont soulevées. Un processus, après tout, n'est en réalité qu'un thread qui s'y croit. Ou vice versa.

L'imprévisibilité des synchronisations affecte à la fois les situations privilégiées et celles qui ne le sont pas. Nous décrirons tout d'abord comment se débrouiller avec un bogue de longue date dans les anciens noyaux Unix affectant tous les programmes set-id. Nous irons ensuite discuter des situations de concurrence en général, comment elles se transforment en brèches de sécurité et les étapes que vous devez suivre pour éviter de tomber dans ces trous.

23-2-a. Bogues de sécurité du noyau Unix

Au-delà des problèmes évidents découlant du passage de privilèges spéciaux à des interpréteurs aussi souples et impénétrables que les shells, les anciennes versions d'Unix souffrent d'un bogue du noyau qui rend tout script set-id non sûr avant même d'arriver jusqu'à l'interpréteur. Le problème ne réside pas dans le script lui-même, mais dans une situation de concurrence dans ce que fait le noyau lorsqu'il trouve un script set-id exécutable. (Le bogue n'existe pas sur les machines qui ne reconnaissent pas #! dans le noyau.) Entre le moment où le noyau ouvre un tel fichier pour savoir quel interpréteur lancer et celui où l'interpréteur (maintenant set-id) démarre et ouvre à nouveau le fichier, il existe une chance pour des entités malicieuses de changer le fichier en question, surtout si le système implémente les liens symboliques.

Heureusement, cette « caractéristique » du noyau peut parfois être désactivée. Malheureusement, il existe deux moyens pour ce faire. Le système peut rendre hors-la-loi les scripts avec les bits set-id positionnés, ce qui n'est pas d'une grande aide. Il peut également ignorer les bits set-id sur les scripts. Dans ce dernier cas, Perl peut émuler les mécanismes de setuid et de setgid quand il remarque les bits set_id (par ailleurs inutiles) sur les scripts Perl. Il effectue ceci grâce à un exécutable spécial appelé suidperl, qui est automatiquement appelé pour vous si besoin.(172)

Cependant, si la fonctionnalité du noyau concernant les scripts set-id n'est pas désactivée, Perl se plaint bruyamment de ce que le script n'est pas sûr. Vous devrez soit la désactiver, soit enrober le script dans un programme C. Il s'agit seulement d'un programme compilé qui ne fait rien d'autre qu'appeler votre programme Perl. Les programmes compilés ne sont pas sujets au bogue qui tourmente les scripts set-id.

Voici un simple enrobage écrit en C :

 
Sélectionnez
#define VRAI_FICHIER "/chemin/du/script" 
main(ac, av)
    char **av;
{
    execv(VRAI_FICHIER, av); 
}

Compilez cet enrobage en un exécutable binaire et rendez-le set-id au lieu de le faire sur votre script. Assurez-vous d'employer un nom de chemin absolu, car le C n'est pas assez malin pour vérifier le marquage sur votre PATH.

(Une autre approche possible consiste à employer le générateur expérimental de code C du compilateur Perl. Une image compilée de votre script ne connaîtra pas la situation de concurrence. Voir au chapitre 18, Compiler.)

Ces dernières années, certains fournisseurs ont finalement sorti des systèmes libérés de ce bogue de set-id. Sur de tels systèmes, lorsque le noyau passe le nom du script set-id à l'interpréteur, il ne s'agit plus d'un nom de fichier sujet à des interventions, mais d'un fichier spécial représentant le descripteur de fichier, comme /dev/fd/3. Ce fichier spécial est déjà ouvert sur le script de manière à ce qu'il ne puisse plus avoir de situation de concurrence que des scripts diaboliques pourraient exploiter.(173) La plupart des versions modernes d'Unix emploient cette approche pour éviter la situation de concurrence inhérente à l'ouverture d'un même fichier deux fois.

23-2-b. Gestion des situations de concurrence

Ce qui nous amène directement au thème des situations de concurrence. Que sont-elles réellement ? Les situations de concurrence se retrouvent fréquemment dans les discussions à propos de la sécurité. (Bien qu'on les retrouve moins que dans les programmes non sûrs. Malheureusement.) C'est parce qu'elles sont une source fertile d'erreurs subtiles de programmation et que de telles erreurs peuvent souvent se transformer en exploits (terme poli pour désigner le fait de bousiller la sécurité de quelqu'un) pour la sécurité. Une situation de concurrence arrive lorsque le résultat de plusieurs événements corrélés dépend de l'ordre de ces événements, mais que cet ordre ne peut être garanti à cause des effets d'une synchronisation non déterministe. Chaque événement se précipite pour être le premier accompli et l'état final du système est laissé à l'imagination de chacun.

Imaginez que vous ayez un processus écrivant par-dessus un fichier existant et un autre processus lisant le même fichier. Vous ne pouvez prédire si vous lisez les anciennes données, les nouvelles ou un mélange aléatoire des deux. Vous ne pouvez même pas savoir si vous avez lu toutes les données. Le lecteur pourrait avoir gagné la course jusqu'à la fin du fichier avant de se terminer. En même temps, si l'écrivain a continué après que le lecteur a rencontré la fin de fichier, le fichier aura grossi plus loin que l'endroit où le lecteur s'était arrêté et ce dernier ne l'aura jamais su.

Ici, la solution est simple : il suffit que les deux parties verrouillent le fichier avec flock. Le lecteur demande généralement un verrou partagé et l'écrivain un verrou exclusif. Tant que toutes les parties demandent et respectent ces verrous consultatifs, les lectures et écritures ne peuvent pas s'enchevêtrer et il n'y aucune chance de trouver des données mutilées. Voir la section Verrouillage de fichier au chapitre 16.

Vous risquez de rencontrer une forme bien moins évidente de situation de concurrence chaque fois que vous laissez des opérations sur des noms de fichier gouverner les opérations ultérieures sur ce fichier. Lorsqu'on les utilise sur des noms de fichier plutôt que sur des descripteurs de fichier, les opérateurs de test de fichier représentent parfois un terrain fertile menant droit à une situation de concurrence. Considérez ce code :

 
Sélectionnez
if ( -e $fichier ) {
    open( HF, "< $fichier" ) 
        or die "impossible d'ouvrir $fichier en lecture : $!";
}
else {
    open( HF, "> $fichier" )
        or die "impossible d'ouvrir $fichier en écriture : $!";
}

Le code paraît aussi simple qu'il ne l'est, mais il est toujours sujet à des situations de concurrence. Il n'y a aucune garantie que la réponse renvoyée par le test -e soit toujours valide au moment où l'un des open est appelé. Dans le bloc if, un autre processus pourrait avoir supprimé le fichier avant qu'il ne puisse être ouvert et vous ne trouveriez pas le fichier dont vous pensiez qu'il allait se trouver là. Dans le bloc else, un autre processus pourrait avoir créé le fichier avant que le tour du second open n'arrive pour qu'il puisse le créer et le fichier que vous pensiez donc ne pas être là, le serait. La simple fonction open crée de nouveaux fichiers, mais écrase ceux qui existaient. Vous pouvez penser que vous voulez écraser tout fichier existant, mais penser que le fichier existant pourrait très bien être un alias nouvellement créé ou un lien symbolique vers un fichier autre part sur le système que vous ne voudriez vraiment pas écraser. Vous pouvez penser que vous savez ce que signifie un nom de fichier à n'importe quel instant, mais vous ne pouvez jamais en être vraiment sûr, tant que d'autres processus ayant accès au répertoire du fichier tournent sur le même système.

Pour résoudre ce problème d'écrasement, vous devrez employer sysopen, qui fournit des contrôles individuels pour soit créer un nouveau fichier, soit pour en écraser complètement un qui existe. Et nous abandonnerons ce test -e d'existence de fichier puisqu'il n'est plus utile ici et ne fait qu'accroître notre exposition aux situations de concurrence.

 
Sélectionnez
use Fcntl qw/O_WRONLY O_CREAT O_EXCL/; 
open (HF, "<", $fichier)
    or sysopen(HF, $fichier, O_WRONLY | O_CREAT | O_EXCL)
    or die "impossible de créer le nouveau fichier $fichier : $!";

Maintenant, même si le fichier se met à éclore d'une manière ou d'une autre entre le moment où le open échoue et celui où le sysopen essaie d'ouvrir un nouveau fichier en écriture, aucun mal ne peut être fait, car avec les flags que nous lui avons fournis, sysopen refusera d'ouvrir un fichier qui existe déjà.

Si quelqu'un essaie de persuader par la ruse votre programme de mal se conduire, il a une bonne chance de réussir en faisant apparaître et disparaître les fichiers quand vous ne vous y attendez pas. Un moyen de réduire le risque de tromperie consiste à vous promettre à vous-même que vous ne manipulerez jamais un nom de fichier plus d'une fois. Dès que vous avez ouvert le fichier, oubliez son nom (à part peut-être pour les messages d'erreur) et ne manipulez plus que le handle représentant le fichier. C'est beaucoup plus sûr, car, même si quelqu'un pourrait s'amuser avec vos noms de fichier, il ne peut le faire avec les handles. (Ou s'il le peut, c'est parce que vous l'avez laissé faire — voir Passage de handles de fichiers au chapitre 16.)

Plus haut dans ce chapitre, nous avons présenté une fonction handle_semble_sur qui appelait la fonction stat de Perl sur un handle de fichier (et non un nom de fichier) pour vérifier son appartenance et ses permissions. L'emploi du handle de fichier est critique pour l'exactitude de la solution — si nous avions passé le nom du fichier, il n'y aurait eu aucune garantie que le fichier dont nous examinions les attributs ait été le même que celui que nous venions d'ouvrir (ou que nous étions sur le point d'ouvrir). Un minable mécréant aurait pu supprimer notre fichier et le remplacer rapidement par un fichier subversif, à un moment donné entre le stat et le open. Peut importe lequel aurait été appelé en premier ; il y aurait toujours une opportunité pour un jeu déloyal entre les deux. Vous pouvez penser que le risque est très réduit puisque la fenêtre l'est aussi, mais il existe de nombreux scripts pirates dans le monde qui seraient tout à fait heureux de lancer votre programme des milliers de fois pour s'en jouer la seule fois où il n'a pas été assez prudent. Un script pirate habile peut même baisser la priorité de votre programme pour qu'il soit interrompu plus souvent que d'ordinaire, simplement pour accélérer un peu les choses. Les gens travaillent dur sur ces questions — c'est pourquoi on les appelle des exploits.

En appelant stat sur un handle de fichier déjà ouvert, nous n'accédons au nom du fichier qu'une seule fois et évitons ainsi la situation de concurrence. Une bonne stratégie pour éviter les situations de concurrence entre deux événements consiste à combiner les deux d'une manière ou d'une autre pour n'en faire qu'un seul événement, en rendant l'opération atomique.(174) Puisque nous n'accédons au fichier qu'une seule fois par son nom, il ne peut y avoir de situation de concurrence entre plusieurs accès, ainsi ça ne fait pas grand-chose si le nom change. Même si notre pirate supprime le fichier que nous avons ouvert (oui, cela peut arriver) et le remplace par un autre pour s'amuser avec nous, nous avons toujours un handle sur le fichier réel et original.

23-2-c. Fichiers temporaires

À part autoriser les dépassements de tampons (ce contre quoi les scripts Perl sont virtuellement immunisés) et faire confiance à des données en entrée indignes de confiance (ce contre quoi le mode de marquage préserve), créer de manière incorrecte des fichiers temporaires est l'un des trous de sécurité les plus fréquemment exploités. Heureusement, les attaques contre les fichiers temporaires exigent des pirates qu'ils aient un compte d'utilisateur valide sur le système qu'ils tentent de pirater, ce qui réduit drastiquement le nombre de mécréants potentiels.

Les programmes imprudents ou désinvoltes utilisent des fichiers temporaires avec toutes sortes de moyens non sûrs, comme en les plaçant dans des répertoires accessibles en écriture par le monde entier, en utilisant des noms de fichier prévisibles et en ne s'assurant pas que le fichier n'existe pas déjà. Dès que vous trouvez un programme avec du code comme ceci :

 
Sélectionnez
open(TMP, ">/tmp/truc.$$")
    or die "impossible d'ouvrir /tmp/truc.$$ : $!";

vous venez de trouver les trois erreurs d'un coup. Ce programme est un accident en sursis.

Le moyen mis en œuvre par l'exploit consiste pour le pirate à tout d'abord planter un fichier du même nom que celui que vous utiliserez. Ajouter le PID à la fin ne suffit pas à garantir l'unicité ; aussi surprenant que cela puisse paraître, ce n'est vraiment pas difficile deviner des PID.(175) Maintenant arrive le programme avec l'appel imprudent à open et au lieu de créer un nouveau fichier temporaire pour ses propres besoins, il écrase celui du pirate à la place.

Quel mal cela peut-il donc faire ? Beaucoup ! Le fichier du pirate n'est pas vraiment un fichier pur, voyez-vous. C'est un lien symbolique (ou parfois un lien en dur), pointant certainement vers un fichier critique dans lequel le pirate ne pourrait normalement pas écrire tout seul, comme /etc/passwd. Le programme pensait avoir ouvert un fichier tout neuf dans /tmp mais à la place, il a écrasé un fichier existant quelque part ailleurs.

Perl offre deux fonctions abordant ce problème, si elles sont utilisées correctement. La première est POSIX::tmpnam, qui ne fait que renvoyer le nom de fichier que vous escomptiez ouvrir par vous-même :

 
Sélectionnez
# Essaie des noms jusqu'à en obtenir un qui soit tout neuf 
use POSIX; 
do {
    $nom = tmpnam(); 
} until sysopen(TMP, $nom, O_RDWR | O_CREAT | O_EXCL, 0600); 
# Maintenant faites des E/S en utilisant le handle TMP.

La seconde est IO::FILE::new_tmpfile, qui vous redonne un handle déjà ouvert :

 
Sélectionnez
# Ou sinon laissez le module le faire à votre place. 
use IO::File; 
my $hf = IO::File::new_tmpfile();    # Il s'agit de tmpfile(3) de POSIX 
# Maintenant faites des E/S en utilisant le handle $hf.

Aucune des deux approches n'est parfaite, mais entre les deux, la première est la meilleure. Le problème majeur avec la seconde est que Perl est tributaire des faiblesses de l'implémentation de tmpfile(3) dans la bibliothèque C de votre système et que vous n'avez aucune garantie que cette fonction ne fasse pas quelque chose d'aussi dangereux que le open que nous essayons de corriger. (Et c'est le cas, assez tristement, de certaines implémentations.) Un problème mineur est que cela ne vous donne pas du tout le nom du fichier. Bien que ce soit mieux si vous pouvez manipuler un fichier temporaire sans connaître son nom — car de cette manière vous ne provoquerez jamais de situation de concurrence en essayant de l'ouvrir à nouveau — bien souvent, vous ne pouvez pas.

Le problème majeur avec la première approche est que vous n'avez aucun contrôle sur l'endroit où se trouve le chemin du fichier, au contraire de la fonction mkstemp(3) de la bibliothèque C. Une première raison est qu'il faut éviter de mettre le fichier sur un système de fichiers monté en NFS. Il n'est pas garanti que le flag O_EXCL fonctionne correctement sous NFS, plusieurs processus demandant une création exclusive pratiquement au même moment peuvent donc tous réussir. Une autre raison est que le chemin renvoyé se situe probablement dans un répertoire où les autres ont les droits en écriture, quelqu'un pourrait avoir planté un lien symbolique pointant vers un fichier inexistant, vous forçant à créer votre fichier à un endroit qu'il préfère.(176) Si vous avez votre mot à dire là-dessus, ne mettez pas vos fichiers temporaires dans un répertoire où quelqu'un d'autre peut écrire. Si vous y êtes obligé, assurez-vous d'utiliser le flag O_EXCL dans le sysopen et essayez d'employer des répertoires avec le flag « seul-le-propriétairepeut-effacer » (le « sticky bit ») positionné.

Depuis la version de Perl 5.6.1, il existe une troisième manière. Le module standard File::Temp prend en compte toutes les difficultés que nous avons mentionnées. Vous pourriez utiliser les options par défaut ainsi :

 
Sélectionnez
use File::Temp "tempfile"; 
$handle = tempfile();

Ou vous pourriez spécifier certaines options ainsi :

 
Sélectionnez
use File::Temp "tempfile"; 
($handle, $nom_fichier) = tempfile("priseXXXXXX",
                                   DIR => "/var/spool/aventure",
                                   SUFFIX = '.dat');

Le module File::Temp fournit également des émulations se préoccupant de la sécurité pour les autres fonctions que nous avons mentionnées (bien que l'interface native soit meilleure, car elle vous donne un handle de fichier ouvert et pas seulement un nom de fichier, qui est sujet à des situations de concurrence). Voir le chapitre 32, Modules standards, pour une plus longue description des options et de la sémantique de ce module.

Une fois que vous avez votre handle de fichier, vous pouvez en faire tout ce que vous voulez. Il est ouvert à la fois en lecture et en écriture, vous pouvez donc écrire dans le handle, faire un seek pour revenir au début et ensuite, si vous le voulez, écraser tout ce que vous veniez d'y mettre ou le lire à nouveau. La chose que vous voudrez vraiment, vraiment éviter de faire est de ne jamais ouvrir ce fichier à nouveau, car vous ne pouvez être sûr qu'il s'agisse vraiment du même fichier que vous aviez ouvert la première fois.(177)

Lorsque vous lancez un autre programme depuis votre script, Perl ferme normalement pour vous tous les handles de fichiers pour éviter une autre vulnérabilité. Si vous utilisez fcntl pour nettoyer votre flag close-on-exec (comme dans la description d'open au chapitre 29, Fonctions), les autres programmes que vous appelez hériteront de ce nouveau descripteur de fichier ouvert. Sur les systèmes qui implémentent le répertoire /dev/fd, vous pourriez fournir à un autre programme un nom de fichier qui soit vraiment le descripteur de fichier en le construisant de cette façon :

 
Sélectionnez
$nom_virtuel = "/dev/fd" . fileno(TMP);

Si vous n'aviez besoin que d'appeler un sous-programme Perl ou un programme qui attend un nom de fichier comme argument et si vous saviez que ce sous-programme ou que ce programme l'utilisait avec un open ordinaire, vous pourriez alors passer le handle de fichier en employant la notation de Perl pour indiquer un handle de fichier :

 
Sélectionnez
$nom_virtuel = "=&" . fileno(TMP);

Lorsque le « nom » de fichier est passé avec un open ordinaire en Perl à un ou deux arguments (et non trois, ce qui enlèverait toute cette magie bien utile), vous obtenez l'accès au descripteur dupliqué. D'une certaine manière, ceci est plus portable que de passer un fichier depuis /dev/fd car cela fonctionne partout où Perl fonctionne ; tous les systèmes n'ont pas de répertoire /dev/fd. D'un autre côté, la syntaxe spéciale d'open en Perl pour accéder aux descripteurs de fichiers par des nombres ne fonctionne qu'avec des programmes Perl et non avec des programmes écrits dans d'autres langages.

23-3. Gestion du code non sûr

La vérification de marquage n'est qu'une sorte de couverture de survie dont vous avez besoin si vous voulez débusquer des données fausses que vous auriez dû déceler vous-même, mais que vous n'avez pas pu dépister avant de les abandonner au système. C'est un peu comme les avertissements optionnels que Perl peut vous donner — ils peuvent ne pas indiquer de problème réel, mais en moyenne le mal que l'on se donne à gérer les fausses alertes est moindre que celui de ne pas détecter les vrais problèmes. Avec le marquage, ce dernier mal est encore plus insistant, car l'utilisation de données fausses ne donne pas seulement les mauvaises réponses ; cela peut saccager votre système et vos deux dernières années de travail avec. (Et peut-être vos deux prochaines si vous n'avez pas fait de bonnes sauvegardes.) Le mode de marquage est utile lorsqu'il s'agit d'avoir confiance en vous-même pour écrire du code honnête, mais ne que vous ne faites pas systématiquement confiance à quiconque vous passe des données pour ne pas essayer de vous pousser à faire quelque chose de regrettable.

Les données sont une chose. Mais c'est tout à fait une autre affaire lorsque vous ne pouvez même pas faire confiance au code que vous êtes en train d'exécuter. Et si l'applet que vous avez récupérée sur le Net contenait un virus, une bombe à retardement ou un cheval de Troie ? La vérification de marquage est ici inutile, car, alors que les données qui sont fournies au programme peuvent être très bien, c'est le code qui est indigne de confiance. Vous vous mettez dans la situation de quelqu'un qui reçoit un appareil mystérieux de la part d'un étranger, avec une note disant en substance : « Placez ceci contre votre crâne et appuyez sur la détente. » Vous pouvez penser que cela va sécher vos cheveux, mais peut-être plus pour très longtemps.

Dans ce domaine, la prudence est synonyme de paranoïa. Il vous faut un système qui vous permette de placer le code suspect en quarantaine. Il peut continuer à exister et même accomplir certaines fonctions, mais vous ne le laissez pas se promener et faire tout ce dont il a envie. En Perl, vous pouvez imposer un genre de quarantaine en utilisant le module Safe.

23-3-a. Compartiments sécurisés

Le module Safe vous laisse mettre en place un bac à sable (sandbox), un compartiment spécial dans lequel toutes les opérations système sont interceptées et où l'accès à l'espace de noms est strictement contrôlé. Les détails techniques de bas niveau de ce module sont changeants nous adopterons donc ici une approche plus philosophique.

23-3-a-i. Restriction de l'accès à l'espace de noms

Au niveau le plus fondamental, un objet Safe ressemble à un coffre-fort sauf que l'idée de base est de laisser les truands à l'intérieur et non à l'extérieur. Dans le monde Unix, il existe un appel système connu sous le nom de chroot(2) qui peut consigner en permanence un processus dans un sous-répertoire du système de fichiers — dans son enfer personnel si vous voulez. Une fois que le processus y est installé, il ne peut absolument pas atteindre de fichiers à l'extérieur de ce répertoire, car il ne sait pas nommer de fichiers se trouvant à l'extérieur.(13) Un objet Safe ressemble à cela, mais au lieu d'être restreint à un sous-ensemble de la structure de répertoires du système de fichiers, il est restreint à un sous-ensemble de la structure des paquetages de Perl, qui est tout aussi hiérarchique que la structure du système de fichiers.

Une autre manière de voir un objet Safe est quelque peu similaire à ces salles d'observations avec des miroirs sans tain dans lesquelles la police met ses suspects. Les gens à l'extérieur peuvent regarder ceux dans la salle, mais non l'inverse.

Lorsque vous créez un objet Safe, vous pouvez lui donner un nom de paquetage si vous le voulez. Sinon, un nouveau nom sera choisi pour vous :

13. Certains sites font cela pour exécuter des scripts CGI, en utilisant des montages en boucle et en lecture seule. C'est quelque peu ennuyeux à configurer, mais si quelqu'un s'échappe un jour, il découvrira qu'il n'a nulle part où aller.

 
Sélectionnez
use Safe; 
my $salle = Safe->new("Donjon"); 
$Donjon::truc = 1; # Pourtant, l'accès direct est déconseillé.

Si vous qualifiez pleinement les variables et les fonctions utilisant le nom de paquetage donné à la méthode new, vous pouvez y accéder depuis l'extérieur, du moins dans l'implémentation actuelle. Cela peut toutefois changer, puisque le projet actuel est de cloner la table de symboles dans un nouvel interpréteur. Une solution pouvant être légèrement plus compatible consiste à positionner les choses avant de créer l'objet Safe, comme montré ci-dessous. Ceci est susceptible de continuer à fonctionner et c'est une manière habile de configurer un objet Safe qui doit démarrer avec beaucoup « d'états ». (Admettons-le, $Donjon::truc n'a pas beaucoup d'états.)

 
Sélectionnez
use Safe; 
$Donjon::truc = 1; # L'accès direct est toujours déconseillé. 
my $salle = Safe->new("Donjon");

Mais Safe fournit également un moyen d'accès aux événements globaux du compartiment si vous ne connaissez pas le nom de paquetage du compartiment. Ainsi, pour une compatibilité ascendante maximale (toutefois, la vitesse ne le sera pas, maximale), nous vous suggérons d'employer la méthode reval :

 
Sélectionnez
use Safe; 
my $salle = Safe->new(); 
$salle->reval('$truc = 1');

(En fait, il s'agit de la même méthode que vous utiliserez pour exécuter du code suspect.) Lorsque vous passez du code dans le compartiment pour le compiler et l'exécuter, ce code pense qu'il se trouve vraiment dans le paquetage main. Ce que le monde extérieur appelle $Donjon::truc, le code à l'intérieur pense qu'il s'agit de $main::truc ou $::truc ou simplement $truc si use strict n'est pas actif. Cela ne marchera pas si l'on écrit $Donjon::truc à l'intérieur du compartiment, car ceci accéderait en fait à $Donjon::Donjon::truc. En donnant à l'objet Safe sa propre notion de main, les variables et les sous-programmes dans le reste de votre programme sont protégés.

Pour compiler et exécuter du code dans le compartiment, utilisez la méthode reval (« eval restreint ») en passant une chaîne de code comme argument. Comme avec toute autre construction eval CHAINE, les erreurs à la compilation et les exceptions à l'exécution pour reval ne tueront pas votre programme. Elles ne feront qu'abandonner le reval et laisser l'exception dans $@, assurez-vous donc de la vérifier après chaque appel à reval.

En utilisant les initialisations données plus tôt, ce code affichera que « truc vaut maintenant 2 »:

 
Sélectionnez
$salle->reval('$truc++; print "truc vaut maintenant $main::truc\n"'); 
if ($@) {
    die "Impossible de compiler le code dans le compartiment : $@"; 
}

Si vous ne voulez que compiler du code sans l'exécuter, enrober votre chaîne dans une déclaration de sous-programme :

 
Sélectionnez
$salle->reval(q{
    our $truc; 
    sub affiche_truc {
        print "truc vaut maintenant $main::truc\n"; 
    }
    }, 1); 
die if $@; # vérification de la compilation

Cette fois, nous avons passé à reval un second argument qui, s'il est vrai, indique à re-val de compiler le code avec le pragma strict actif. Depuis l'intérieur de la chaîne de code, vous ne pouvez pas désactiver, strict car importer et ne pas importer sont justement deux des choses que vous ne pouvez pas faire normalement dans un compartiment Safe. Il existe encore beaucoup de choses que vous ne pouvez pas faire normalement dans un compartiment Safe — voir le paragraphe suivant.

Une fois que vous avez créé la fonction affiche_truc dans le compartiment, ces instructions sont à peu près équivalentes :

 
Sélectionnez
$salle->reval('affiche_truc()');    # Meilleure façon. 
die if $@; 
$salle->varglob('affiche_truc')->(); # Appel via un glob anonyme. 
Donjon::affiche_truc();             # Appel direct, fortement déconseillé.
23-3-a-ii. Restriction de l'accès aux opérateurs

L'autre chose importante à propos d'un objet Safe est que Perl limite les opérations disponibles à l'intérieur du bac à sable. (Vous pourriez très bien laisser votre enfant prendre un seau et une pelle dans le bac à sable, mais vous refuseriez certainement de le laisser jouer avec un bazooka.) Il ne suffit pas de protéger seulement le reste de votre programme ; vous devez aussi protéger le reste de votre ordinateur.

Lorsque vous compilez du code dans un objet Safe, avec reval ou rdo (la version restreinte de l'opérateur do FICHIER), le compilateur consulte une liste spéciale, par compartiment, de contrôle d'accès pour décider si chaque opération individuelle est considérée ou non comme sûre pour la compilation. De cette manière vous n'avez pas à (trop) vous inquiéter à propos des séquences d'échappement imprévues du shell, de l'ouverture de fichiers alors que vous ne le vouliez pas, des assertions de code étranges dans les expressions régulières ou de la plupart des problèmes d'accès depuis l'extérieur que la plupart des gens craignent habituellement. (Ou qu'ils devraient craindre.)

L'interface spécifiant quels opérateurs devraient être permis ou restreints est actuellement en cours de réécriture, nous ne montrerons donc ici que la manière d'en utiliser un ensemble par défaut. Pour plus de détails, consultez la documentation en ligne du module Safe.

Le module Safe n'offre pas une protection complète contre les attaques par saturation des serveurs (denial-of-service attacks), particulièrement lorsqu'on l'utilise dans ses modes les plus permissifs. Les attaques par saturation des serveurs prennent toutes les ressources d'un certain type disponibles sur le système, empêchant les autres processus d'accéder aux caractéristiques essentielles du système. Des exemples de telles attaques comprennent le bourrage de la table de processus du noyau, la mainmise sur la CPU en exécutant dans une frénétique boucle sans fin, l'épuisement de toute la mémoire disponible et le remplissage d'un système de fichiers. Ces problèmes sont très difficiles à résoudre, particulièrement de manière portable. Voir la fin de la section Du code déguisé en données pour une discussion plus approfondie sur les attaques par saturation des serveurs.

23-3-a-iii. Exemples sécurisés

Imaginez que vous ayez un programme CGI gérant un formulaire dans lequel l'utilisateur peut rentrer une expression Perl arbitraire et récupérer le résultat évalué.(178) Comme toutes les entrées venant de l'extérieur, la chaîne arrive marquée, Perl ne vous laissera donc pas encore lui appliquer un eval — vous devrez d'abord la nettoyer grâce à une reconnaissance de motif. Le problème est que vous ne serez jamais capables de concevoir un motif qui puisse détecter toutes les menaces possibles. Et vous n'oserez pas nettoyer ce que vous avez reçu et l'envoyer à l'eval interne. (Si vous osez, nous serons tentés de pénétrer dans votre système pour supprimer le script.)

C'est ici que reval entre en scène. Voici un script CGI qui traite un formulaire avec un simple champ, évalue (dans un contexte scalaire) la chaîne qu'il trouve et affiche le résultat formaté :

 
Sélectionnez
#!/usr/bin/perl -lTw 
use strict; 
use CGI::Carp 'fatalsToBrowser'; 
use CGI qw/:standard escapeHTML/; 
use Safe;

print header(-type => "text/html;charset=UTF-8"),
   start_html("Résultat de l'expression Perl"); 
my $expr = param("EXPR") =~ /^([^;]+)/
            ? $1 # renvoie la portion maintenant nettoyée
            : croak("aucun champ EXPR valide dans le formulaire"); 
my $reponse = Safe->new->reval($expr); 
die if $@;

print p("Le résultat de", tt(escapeHTML($expr)),
                     "est", tt(escapeHTML($reponse)));

Imaginez qu'un utilisateur malveillant vous passe « print `cat /etc/passwd` » (ou pire) comme chaîne d'entrée. Grâce à l'environnement restreint qui interdit les apostrophes inverses, Perl décèlera le problème à la compilation et quittera immédiatement. La chaîne dans $@ est « quoted execution (``, qx) trapped by operation mask » (exécution entre apostrophes inverses (``, qx) capturée par le masque des opérations), ainsi que les informations obligatoires à la fin, identifiant où le problème a eu lieu.

Puisque nous n'avons pas dit le contraire, les compartiments que nous avons créés utilisent tous l'ensemble par défaut d'opérations permises. La manière dont vous vous y prenez pour déclarer des opérations spécifiques comme permises ou interdites, importe peu ici. Et puisque vous pouvez créer plusieurs objets Safe dans votre programme, vous pouvez conférer différents degrés de confiance aux différents morceaux de code selon leur provenance.

14. Merci de ne pas rire. Nous avons vraiment vu des pages web faire cela. Sans Safe !

Si vous désirez vous amuser avec Safe, voici une petite calculatrice interactive en Perl. Vous pouvez lui passer des expressions numériques et voir immédiatement leur résultat. Mais elle n'est pas limitée aux nombres. Elle ressemble plus à l'exemple de boucle pour eval au chapitre 29, où vous pouvez prendre tout ce qu'on vous donne, l'évaluer et renvoyer le résultat. La différence est que la version avec Safe n'exécute pas aveuglément tout ce que vous souhaitez. Vous pouvez lancer cette calculatrice interactivement sur votre terminal, y taper quelques bouts de code en Perl et vérifier les réponses pour avoir un aperçu des types de protection fournis par Safe.

 
Sélectionnez
#!/usr/bin/perl -w 
# calcsécu -programme de démonstration pour jouer avec Safe 
use strict; 
use Safe; 
my $salle = Safe->new(); 
while (1) {
    print "Entrée : ";
    my $expr = <STDIN>;
    exit unless defined $expr;
    chomp($expr);
    print "$expr donne ";
    local $SIG{__WARN__} = sub { die @_ };
    my $result = $salle->reval($expr, 1);
    if ($@ =~ s/at \(eval \d+\).*//) {
        printf "[%s]: %s", $@ =~ /trapped by operation mask/
            ? "Violation de la sécurité" : "Exception", $@;
    } else {
        print "[Résultat normal] $result\n"; 
    } 
}

Avertissement : le module Safe est actuellement en cours de respécification pour exécuter chaque compartiment dans un interpréteur Perl complètement indépendant dans le même processus. (C'est la stratégie employée par le mod_perl d'Apache lorsqu'il exécute des scripts Perl précompilés.) Les détails sont encore vagues en ce moment, mais notre boule de cristal suggère que désigner des choses à l'intérieur du compartiment en utilisant un paquetage nommé ne vous conduira pas bien loin après la réécriture imminente. Si vous utilisez une version de Perl plus récente que 5.6, vérifier les notes concernant la version dans perldata(1) pour voir ce qui a changé ou consultez la documentation du module Safe lui-même. (Bien entendu, c'est toujours ce que vous faites, n'est-ce pas ?)

23-3-a-iv. Du code déguisé en données

Les compartiments Safe sont disponibles lorsque des choses vraiment effrayantes arrivent, mais cela ne veut pas dire que vous devriez laisser tomber votre garde lorsque vous vous occupez chez vous de choses quotidiennes. Vous devez cultiver une vigilance sur ce qui vous entoure et regarder les choses du point de vue de quelqu'un voulant s'introduire. Vous devez prendre des mesures proactives comme garder les choses bien éclairées et tailler les buissons qui peuvent dissimuler divers problèmes se tapissant dans l'ombre.

Perl essaie de vous aider dans ce domaine également. L'analyse conventionnelle et le schéma d'exécution de Perl évitent les pièges dont les langages de programmation shell sont souvent la proie. Il existe beaucoup de fonctionnalités extrêmement puissantes dans les langages, mais par de par leur conception, ils sont syntaxiquement et sémantiquement limités de façon que le programmeur garde le contrôle. À quelques exceptions près, Perl n'évalue chaque token qu'une seule fois. Quelque chose semblant être utilisé comme une variable pour une simple donnée ne s'enracinera pas soudainement quelque part dans votre système de fichiers.

Malheureusement, ce genre de chose peut arriver si vous appelez le shell pour lancer d'autres programmes pour vous, car vous tournez alors avec les règles du shell à la place de celles de Perl. Le shell est cependant facile à éviter — vous n'avez qu'à utiliser les formes avec un argument de liste pour les fonctions system, exec ou open sur un pipe. Bien que les apostrophes inverses ne disposent pas d'une forme avec un argument de liste qui soit sécurisée contre le shell, vous pouvez toujours les émuler comme décrit à la section Accès aux commandes et aux fichiers avec des privilèges restreints. (Alors qu'il n'y a pas de moyen syntaxique pour donner aux apostrophes inverses un argument de liste, une forme à plusieurs arguments de l'opérateur readpipe sous-jacent est en cours de développement ; mais au moment où nous écrivons ces lignes, elle n'est pas encore prête pour le devant de la scène.)

Lorsque vous utilisez une variable dans une expression (y compris lorsque vous l'interpolez dans une chaîne entre guillemets), il n'y a aucune chance pour que la variable contienne du code Perl qui fasse quelque chose auquel vous ne vous attendiez pas.(179) Au contraire du shell, Perl n'a jamais besoin de guillemets ou d'apostrophes protecteurs autour des variables, peu importe ce qu'elles peuvent contenir.

 
Sélectionnez
$nouveau = $ancien# pas besoin de guillemets ou d'apostrophes. 
print "$nouveau items\n";     # $nouveau ne peut pas vous faire de mal. 

$phrase = "$nouveau items\n"; # ici non plus. 
print $phrase# toujours parfaitement OK.

Perl adopte une approche « WYSIWYG » (what you see is what you get, ce que vous voyez est ce que vous obtenez). Si vous ne voyez pas de niveau supplémentaire d'interpolation, alors il n'y en a pas. Il est possible d'interpoler arbitrairement des expressions Perl dans des chaînes, mais seulement si vous demandez spécifiquement à Perl de le faire. (Et même ainsi, le contenu est toujours sujet aux vérifications de marquage si vous êtes en mode de marquage.)

 
Sélectionnez
$phrase = "Vous avez perdu @{[ 1 + int rand(6) ]} points\n";

L'interpolation n'est cependant pas récursive. Vous ne pouvez pas cacher une expression arbitraire dans une chaîne :

 
Sélectionnez
$compteur = '1 + int rand(6)';        # Du code aléatoire. 
$dire     = "$compteur points";       # Plutôt un littéral. 
$dire     = "@{[$compteur]} points";  # Également un littéral.

Les deux affectations à $dire produiraient « 1 + rand(6) points », sans évaluer le contenu interpolé de $compteur en tant que code. Pour obliger Perl à faire cela, vous devez appeler eval CHAINE explicitement :

 
Sélectionnez
$code = '1 + int rand(6)'; 
$lance_de_des = eval $code; 
die if $@;

Si $code était marqué, cet eval CHAINE aurait levé sa propre exception. Bien entendu, il ne faut presque jamais évaluer du code aléatoire provenant de l'utilisateur — mais si vous le faites, vous devriez jeter un œil au module Safe. Vous avez dû en entendre parler.

Il existe un endroit où Perl peut parfois traiter des données comme du code ; à savoir, lorsque le motif dans un opérateur qr//, m// ou s/// contient l'une des nouvelles assertions des expressions régulières, (?{ CODE }) ou (??{ CODE }). Celles-ci ne posent pas de problèmes de sécurité lorsqu'elles sont utilisées comme des littéraux dans des correspondances de motifs :

 
Sélectionnez
$cmpt =$n= 0; 
while ($donne =~ /( \d+ (?{ $n++ }) | \w+ )/gx) {
    $cmpt++; 
} 
print "$cmpt mots trouvés, dont $n nombres.\n";

Mais du code existant interpolant des variables dans des correspondances a été écrit en présupposant que les données étaient des données, et non du code. Les nouvelles constructions pourraient avoir introduit un trou de sécurité dans des programmes auparavant sécurisés. Ainsi, Perl refuse d'évaluer un motif si une chaîne interpolée contient une assertion de code et lève à la place une exception. Si vous avez vraiment besoin de cette fonctionnalité, vous pouvez toujours l'activer avec le pragma de portée lexicale use re 'eval'. (Vous ne pouvez cependant toujours pas utiliser de données marquées en tant qu'assertions de code interpolé.)

Un type complètement différent de soucis de sécurité se produisant avec les expressions régulières réside dans les problèmes de saturation des serveurs (denial-of-service). Ceux-ci peuvent faire sortir votre programme trop tôt, ou tourner trop longtemps, ou encore épuiser toute la mémoire disponible — et même parfois faire un core dump, selon l'âge du capitaine.

Lorsque vous traitez des motifs fournis par l'utilisateur, vous n'avez pas à vous inquiéter à propos de l'interprétation de code Perl quelconque. Cependant, le moteur d'expressions régulières possède ses propres petits compilateur et interpréteur et le motif fourni par l'utilisateur est capable d'infliger un infarctus au compilateur d'expressions régulières. Si un motif interpolé n'est pas valide, une exception est levée à l'exécution, ce qui est fatal à moins de l'intercepter. Si vous essayez effectivement de l'intercepter, assurez-vous d'utiliser seulement eval BLOC, et non eval CHAINE, car le niveau d'évaluation supplémentaire de cette dernière forme permettrait en fait l'exécution de code Perl aléatoire. À la place, faites quelque chose comme ceci :

 
Sélectionnez
if (not eval { "" =~ /$corresp/; 1 }) {
    # (Maintenant faites ce que vous voulez avec un mauvais motif.) 
} else { 
    # Nous savons que le motif est au moins sécurisé pour la compilation.
    if ($donnee =~ /$corresp/) {...}
}

Un problème plus troublant de saturation des serveurs est qu'étant donné la bonne donnée et le bon motif de recherche, votre programme peut sembler se bloquer à jamais. Car certaines correspondances de motifs exigent un temps de calcul exponentiel et cela peut facilement excéder le taux de MTBF (Mean Time Between Failures, N.d.T. Temps moyen de fonctionnement entre deux incidents) de notre système solaire. Si vous êtes particulièrement chanceux, ces motifs demandant des calculs intensifs exigeront également un espace de stockage exponentiel. Si c'est le cas, votre programme épuisera toute la mémoire virtuelle disponible, écroulera votre système, embêtera vos utilisateurs et soit décédera par die avec une erreur ordinaire « Out of memory! » (dépassement de mémoire), soit laissera derrière lui un fichier core dump vraiment énorme, peut-être cependant pas aussi gros que le système solaire.

Comme la plupart des attaques par saturation des serveurs, celle-ci n'est pas facile à résoudre. Si votre plate-forme implémente la fonction alarm, vous pouvez donner un délai limite pour la correspondance de motifs. Malheureusement, Perl ne peut pas (actuellement) garantir que le simple acte de gérer un signal ne va jamais lever un core dump. (Nous avons prévu de le résoudre dans une prochaine version.) Vous pouvez cependant toujours l'essayer et même si le signal n'est pas géré élégamment, au moins le programme ne bouclera pas à jamais.

Si votre système implémente des limites de ressources par processus, vous pourriez configurer celles-ci dans votre shell avant d'appeler le programme Perl ou utiliser le module BSD::Ressource de CPAN pour le faire directement en Perl. Le serveur web Apache vous permet de fixer des limites de temps, de mémoire et de taille de fichiers sur les scripts CGI qu'il lance.

Finalement, nous espérons que nous vous avons laissé un sentiment non résolu d'insécurité. Souvenez-vous que si vous êtes paranoïaque, cela ne veut pas dire pour autant qu'il n'y a personne à vos trousses. Alors vous feriez mieux de vous en accommoder.

24. Techniques couramment employées

Adressez-vous à n'importe quel programmeur Perl et il sera heureux de vous donner des pages et des pages de conseil sur la manière de programmer. Nous ne sommes pas différents (au cas où vous ne l'ayez pas remarqué). Dans ce chapitre, plutôt que d'essayer de vous exposer les caractéristiques spécifiques de Perl, nous prendrons la direction opposée en distillant quelques descriptions des techniques idiomatiques en Perl. Nous espérons qu'en rassemblant divers petits morceaux qui semblent sans rapport les uns avec les autres, vous parviendrez à vous imbiber du sentiment de ce qu'est vraiment « penser en Perl ». Après tout, lorsque vous programmez, vous n'écrivez pas un paquet d'expressions, puis un paquet de sous-programmes, puis un paquet d'objets. Vous devez tout appréhender d'un seul coup, plus ou moins. Voilà donc à quoi ressemble ce chapitre.

Il y a tout de même une organisation rudimentaire dans ce chapitre, en ce que nous commencerons par les conseils négatifs et tracerons le chemin jusqu'aux conseils positifs. Nous ne savons pas si vous vous sentirez mieux grâce à cela, mais cela nous aidera, nous, à nous sentir mieux.

24-1. Étourderies courantes des débutants

La plus grande étourderie est d'oublier d'utiliser use warnings, qui relève de nombreuses erreurs. La deuxième est de ne pas utiliser use strict quand il le faut. Ces deux pragmas peuvent vous économiser des heures de casse-tête lorsque votre programme commence à grossir. (Et ce sera le cas.) Un autre faux pas consiste à oublier de consulter la FAQ en ligne. Imaginez que vous vouliez trouver si Perl dispose d'une fonction round. Vous devriez essayer de chercher d'abord dans la FAQ :

 
Sélectionnez
% perlfaq round

À part ces « métaétourderies », il y a plusieurs types de pièges de programmation. Certains pièges dans lesquels presque tout le monde tombe et d'autres dans lesquels vous ne tombez que si vous êtes originaire d'une culture particulière possédant des pratiques différentes. Nous les avons répartis dans les sections suivantes.

24-2. Erreurs universelles

  • Mettre une virgule après le handle de fichier dans une instruction print. Bien qu'il paraisse tout à fait rationnel et élégant d'écrire :
 
Sélectionnez
print STDOUT, "adieu, monde ", $adj, " !\n"; # MAUVAIS

c'est néanmoins incorrect, à cause de la première virgule. Ce qu'il faut à la place est la syntaxe d'objet indirect :

 
Sélectionnez
print STDOUT "adieu, monde ", $adj, " !\n"# OK

La syntaxe est ainsi faite que l'on puisse écrire :

 
Sélectionnez
print $handle_fichier "adieu, monde ", $adj, " !\n";

$handle_fichier est un scalaire contenant le nom d'un handle de fichier à l'exécution. Ce qui est différent de :

 
Sélectionnez
print $pas_un_handle_fichier, "adieu, monde ", $adj, " !\n";

$pas_un_handle_fichier n'est qu'une chaîne qui est ajoutée à la liste de ce qui

doit être imprimé. Voir « objet indirect » dans le glossaire.

  • Utiliser == au lieu de eq et != au lieu de ne. Les opérateurs == et != sont des tests numériques. Les deux autres sont des tests sur les chaînes. Les chaînes "123" et "123.00" sont égales en tant que nombres, mais pas en tant que chaînes. De même, les chaînes non numériques sont numériquement égales à zéro. À moins que vous ne traitiez des nombres, vous utilisez presque toujours à la place les opérateurs de comparaison de chaînes.
  • Oublier le point-virgule final. Chaque instruction Perl est terminée par un point-virgule ou la fin d'un bloc. Les sauts de ligne ne sont pas des terminateurs d'instruction comme en awk, en Python ou en FORTRAN. Souvenez-vous que Perl est comme C.
    Une instruction contenant un document « ici-même » (here document) est tout particulièrement apte à perdre son point-virgule. Cela devrait ressembler à ceci :
 
Sélectionnez
print <<'FINIS'; 
Sans une grande roideur et une continuelle 
attention à toutes ses paroles, on est exposé à 
dire en moins d'une heure le oui et le non sur une 
même chose ou sur une même personne, déterminé 
seulement par un esprit de société et de commerce 
qui entraîne naturellement à ne pas contredire 
celui-ci et celui-là qui en parlent différemment.

                                   -- Jean de La Bruyère 
FINIS
  • Oublier qu'un BLOC exige des accolades. Les instructions seules ne forment pas de BLOC par elles-mêmes. Si vous créez une structure de contrôle comme un while ou un if qui demande un ou plusieurs BLOC s, vous devez utiliser des accolades autour de chaque BLOC. Souvenez-vous que Perl n'est comme C.
  • Ne pas sauver $1, $2, etc., dans les expressions régulières. Souvenez-vous que chaque nouvelle correspondance (m//) ou substitution (s///) modifie (ou remet à zéro ou écrase) vos variables $1, $2. . . , ainsi que $`, $&, $' et consorts. Une manière de les sauvegarder consiste à évaluer la correspondance dans un contexte de liste, comme dans :
    my ($un, $deux) = /(\w+) (\w+)/;
  • Ne pas se rendre compte qu'un local change aussi la valeur de la variable telle que la voient les autres sous-programmes appelés depuis l'intérieur de la portée du local. Il est facile d'oublier que local est une instruction d'exécution qui travaille dans une portée dynamique, car il n'en existe aucun équivalent dans des langages comme C. Voir la section Déclarations avec portée au chapitre 4, Instructions et déclarations. De toute façon, il est en général préférable d'utiliser un my.
  • Perdre la trace des appariements d'accolades. Un bon éditeur vous aidera à trouver les paires correspondantes. Prenez-en un. (Ou deux.)
  • Employer des instructions de contrôle de boucle dans un do {} while. Bien que les accolades dans cette structure de contrôle ont l'air de faire partie d'un BLOC de boucle, ce n'est en fait pas le cas.
  • Utiliser $truc[1] alors que vous voulez dire $truc[0]. Les tableaux commencent par défaut à zéro en Perl.
  • Écrire @truc[0] alors que vous vouliez dire $truc[0]. La référence à @truc[0] est une tranche de tableau, représentant un tableau constitué du seul élément $truc[0]. Parfois, cela ne fait aucune différence, comme dans : print "la réponse est @truc[0]\n";
    Mais la différence est énorme pour des choses comme :
    @truc[0] = <STDIN>; qui va absorber tout STDIN en affectant la première ligne à $truc[0] et en rejetant tout le reste, ce qui n'est probablement pas ce que vous aviez prévu. Prenez l'habitude de considérer que $ représente une valeur unique, alors que @ représente une liste de valeurs, et tout ira bien.
  • Oublier les parenthèses avec un opérateur de liste comme my : my$x,$y =(4, 8);#FAUXmy ($x, $y) = (4, 8); # OK
  • Oublier de sélectionner le bon handle de fichier avant de positionner $^, $~ ou $|. Ces variables dépendent du handle de fichier actuellement sélectionné, comme le détermine select (HANDLE_FICHIER). Le handle de fichier initialement sélectionné est STDOUT. Vous devriez vraiment plutôt utiliser les méthodes de handles de fichiers du module FileHandle. Voir le chapitre 28, Noms spéciaux.
24-2-a. Conseils fréquemment ignorés

Les programmeurs Perl Pratiquants devraient prendre note de ce qui suit :

  • Souvenez-vous que de nombreuses opérations se comportent différemment dans un contexte de liste et dans un contexte scalaire. Par exemple :
 
Sélectionnez
($x) = (4, 5, 6);        # Contexte de liste ; 
$x vaut 4 $x=(4,5,6);    # Co escalaire;$x vaut 6

@a = (4, 5, 6);
$x = @a;                 # Contexte scalaire ; $x vaut 3
                         # (la longueur du tableau)
  • Évitez si possible les mots simples, surtout s'ils sont entièrement en minuscules. Il est impossible de savoir du premier coup d'œil si un mot est une fonction ou une chaîne brute. Vous supprimerez toute confusion en protégeant les chaînes par des guillemets et des parenthèses autour des arguments d'appels de fonction. En fait, le pragma use strict au début de votre programme donne une erreur de compilation si l'on utilise un mot simple — ce qui est probablement une bonne chose.
  • On ne peut pas distinguer au premier coup d'œil les fonctions internes qui sont des opérateurs unaires (comme chop et chdir), celles qui sont des opérateurs de liste (comme print et unlink) et celles qui n'ont pas d'argument (comme time). Il vous faut les apprendre au chapitre 29. Comme toujours, utilisez des parenthèses si vous n'êtes pas sûrs — et même si vous n'êtes pas sûrs d'être sûrs. Remarquez également que les sous-programmes définis par l'utilisateur sont par défaut des opérateurs de liste, mais peuvent être déclarés comme opérateurs unaires avec un prototype valant ($) ou sans arguments avec un prototype valant ().
  • On a souvent du mal à se souvenir que certaines fonctions emploient par défaut $_, ou @ARGV ou autre chose, alors que d'autres non. Prenez le temps d'apprendre lesquelles, ou évitez les arguments par défaut.
  • <HF> n'est pas le nom d'un handle de fichier, mais un opérateur d'angle qui effectue une opération d'entrée par ligne sur le handle. Cette confusion se manifeste généralement quand des gens essayent un print sur l'opérateur angle :
 
Sélectionnez
print <HF> "salut"; # MAUVAIS, omettre les angles
  • Souvenez-vous également que les données lues par l'opérateur d'angle ne sont affectées à $_ que lorsque le fichier lu est la seule condition d'une boucle while :
 
Sélectionnez
while (<HF>) { }   # Données affectées à $_. 
<HF>;              # données lues et éliminées !
  • N'utilisez pas = lorsque vous avez besoin de =~ ; les deux constructions sont très différentes :
 
Sélectionnez
$x =  /truc/;      # recherche "truc" dans $_, met le résultat dans $x $x =~ /truc/;      # recherche "truc" dans $x, élimine le résultat
  • Utilisez my pour les variables locales chaque fois que vous pouvez vous en accommoder. local donne en fait une valeur temporaire à une variable globale, ce qui vous expose grandement aux effets secondaires dus à la portée dynamique
  • N'utilisez pas local sur les variables exportées par un module. La localisation d'une variable exportée ne fait pas changer sa valeur. Le nom local devient l'alias d'une nouvelle valeur, mais le nom externe reste l'alias de l'original.
24-2-b. Pièges du C

Les programmeurs Cérébraux du C devraient prendre note de ce qui suit :

  • Les accolades sont obligatoires pour les blocs if et while.
  • Vous devez utiliser elsif plutôt que « else if » ou de « elif ». Une syntaxe comme :
 
Sélectionnez
if (expression) {
    bloc; 
} 
else if (autre_expression) { # MAUVAIS
    autre_bloc; 
}

est illégale. La partie else est toujours un bloc et un if tout nu n'est pas un bloc. Ne vous attendez pas à ce que Perl se comporte exactement comme le C. À la place, il faut écrire :

 
Sélectionnez
if (expression) {
    bloc;
} elsif (autre_expression) {
    autre_bloc; 
}

Remarquez également que « elif » est « file » écrit à l'envers. Seuls des fanatiques d'Algol pourraient tolérer un mot-clef identique à un autre mot écrit à l'envers.

  • Les mots-clefs break et continue de C deviennent respectivement last et next en Perl. En revanche, ces derniers ne fonctionnent pas dans une construction do {} while.
  • Il n'existe pas d'instruction switch. (Mais il est facile d'en élaborer une au vol ; voir les paragraphes Blocs simples et Structures de cas au chapitre 4.
  • Les variables commencent par $, @ ou % en Perl.
  • Les commentaires commencent par # et non par /*.
  • Vous ne pouvez pas obtenir l'adresse de n'importe quoi, bien qu'un opérateur similaire en Perl soit l'antislash, qui crée une référence.
  • ARGV doit être en majuscules. $ARGV[0] est le argv[1] de C et le argv[0] de C devient $0.
  • Les appels système comme link, unlink et rename renvoient vrai en cas de succès et non 0.
  • Les gestionnaires de signaux de %SIG traitent des noms de signaux et non des nombres.
24-2-c. Pièges du shell

Les programmeurs Shérifs du Shell devraient prendre note de ce qui suit :

  • Les variables sont préfixées par $ ou @ tant du côté gauche d'une affectation que du côté droit. Une affectation « à la shell » comme :
 
Sélectionnez
chameau='dromadaire';   # MAUVAIS

ne sera pas analysée comme prévu. Vous devez mettre :

 
Sélectionnez
$chameau='dromadaire'; # OK
  • La variable de boucle pour un foreach exige également un $. Même si csh préfère :
 
Sélectionnez
foreach bosse (une deux)
    remplir $bosse 
end

en Perl, ceci s'écrit plutôt :

 
Sélectionnez
foreach $bosse ("une", "deux") {
    remplir($bosse); 
}
  • L'opérateur apostrophe inverse interpole les variables sans se préoccuper de la présence d'apostrophes dans la commande.
  • L'opérateur apostrophe inverse ne convertit pas la valeur de retour. En Perl, il faut explicitement éliminer le saut de ligne, comme ceci :
 
Sélectionnez
chop($mon_hote = `hostname`);
  • Les shells (surtout csh) offrent plusieurs niveaux de substitution sur la ligne de commande. Perl n'effectue de substitution que dans certaines constructions comme les guillemets, les apostrophes inverses, les signes inférieurs et supérieurs et les motifs de correspondance.
  • Les shells ont tendance à interpréter les scripts petit à petit. Perl compile le programme entier avant de l'exécuter (hormis les blocs BEGIN, qui s'exécutent avant que la compilation soit achevée).
  • Les arguments sont accessibles via @ARGV et non $1, $2, etc.
  • L'environnement n'est pas automatiquement accessible sous forme de variables scalaires séparées. Utilisez le module standard Env si vous voulez que ce soit le cas.
24-2-d. Pièges des anciens Perl

Les programmeurs Pénitents de Perl 4 (et des Précédents) devraient prendre note des modifications suivantes entre la version 4 et la version 5 qui pourraient affecter les anciens scripts :

 
Sélectionnez
sub Ciao { die "Hasta la vista, baby !" } 
$SIG{'QUIT'} = Ciao;
  • @ interpole maintenant systématiquement un tableau dans des chaînes protégées par guillemets. Certains programmes doivent maintenant utiliser l'antislash pour protéger les @ qui ne devraient pas être interpolées.
  • Les mots simples qui ressemblaient à des chaînes à Perl ont maintenant l'air d'appels à un sous-programme si un sous-programme de ce nom est défini avant que le compilateur ne les voie. Par exemple :
    Dans les précédentes versions de Perl, ce code mettait en place le gestionnaire de signal. Il appelle maintenant la fonction ! Vous pouvez utiliser l'option -w pour détecter cette construction à risque ou use strict pour la rendre illégale.
 
Sélectionnez
print "$a::$b::$c\n"
 
Sélectionnez
print "$var::abc::xyz\n";
  • Les identificateurs commençant par « _ » ne se trouvent plus forcément dans le paquetage main, sauf le symbole souligné brut lui-même (comme dans $_, @_, etc.).
  • Le double deux-points est maintenant un séparateur de paquetage valide dans un identificateur. Ainsi, l'instruction :
    analyse maintenant $a:: comme la référence à la variable, alors que dans les versions précédentes, seul le $a était considéré comme la référence à la variable. De même :
    est maintenant interprété comme une variable unique $var::abc::xyz, alors que dans les versions précédentes, la variable $var aurait été suivie par le texte constant ::abc::xyz.
  • s'$motif'remplacement' n'interpole plus $motif. (Le $ serait interprété comme une assertion de fin de ligne.) Ce comportement n'arrive que dans l'utilisation d'apostrophes en tant que séparateur de substitution ; dans les autres substitutions, $motif est toujours interpolé.
  • Le deuxième et le troisième argument de splice sont maintenant évalués dans un contexte scalaire plutôt que dans contexte de liste.
  • Des erreurs sémantiques apparaissent maintenant en raison de la précédence :
 
Sélectionnez
Shift @liste + 20; # maintenant analysé comme shift(@liste + 20), 
                   # illégal ! 
$n = keys %map + 20; # maintenant analysé comme keys(%map + 20), 
                     # illégal !

Parce que si ces derniers fonctionnaient, ceci ne le pourrait pas :

 
Sélectionnez
sleep $sieste + 20;
  • La précédence des opérateurs d'affectation est maintenant la même que celle de l'affectation. Les versions antérieures de Perl leur donnaient par erreur la précédence de l'opérateur associé. Il faut donc maintenant les encadrer entre parenthèses dans des expressions comme :
 
Sélectionnez
/truc/ ? ($a += 2) : ($a -= 2);

Sinon

 
Sélectionnez
/truc/?$a+= 2 : $a -= 2;

serait analysé par erreur en :

 
Sélectionnez
(/truc/ ? $a +=2 : $a) -= 2;

En revanche,

 
Sélectionnez
$a+= /truc/ ? 1 : 2;

fonctionne maintenant comme s'y attendrait un programmeur C.

  • open TRUC || die est maintenant incorrect. Il faut des parenthèses autour du handle de fichier, car open a la précédence d'un opérateur de liste.
  • Les éléments des listes d'arguments pour les formats sont maintenant évalués dans un contexte de liste. Cela signifie que vous pouvez maintenant interpoler des valeurs de liste.
  • Vous ne pouvez pas aller par un goto dans un bloc que l'optimisation a supprimé. Damnation.
  • Il n'est plus légal d'employer un espace comme nom de variable ou comme délimiteur de construction protégée. Enfer et damnation.
  • La fonction caller renvoie maintenant une valeur fausse dans un contexte scalaire s'il n'y a pas d'appelant. Cela permet aux modules de déterminer s'ils sont requis ou lancés directement.
  • m//g rattache maintenant son état à la chaîne recherchée plutôt qu'à l'expression régulière. Voir le chapitre 5, Correspondance de motifs pour plus de détails.
  • reverse n'est plus autorisé en tant que nom de routine pour de tri pour sort.
  • taintperl n'est plus un exécutable séparé. Il existe maintenant une option -T qui active le marquage quand il ne l'est pas automatiquement.
  • Les chaînes entre guillemets ne peuvent plus se terminer par un $ ou un @ non protégé.
  • La syntaxe archaïque if BLOC BLOC n'est plus supportée.
  • Les indices de tableau négatifs comptent maintenant depuis la fin du tableau.
  • Il est maintenant garanti que l'opérateur virgule, dans un contexte scalaire, procure un contexte scalaire à ses arguments.
  • L'opérateur ** lie maintenant plus étroitement que le moins unaire.
  • Diminuer $#tableau élimine maintenant immédiatement des éléments du tableau.
  • Il n'est pas garanti que delete renvoie la valeur détruite des tableaux liés par, tie car l'implémentation de cette fonctionnalité serait onéreuse pour certains modules.
  • La construction "ceci est $$x", qui interpolait l'ID du processus à cet endroit, essaie maintenant de déréférencer $x. Toutefois, $$ tout seul, fonctionne toujours très bien.
  • Le comportement de foreach a légèrement changé lorsqu'il parcourt itérativement dans une liste qui n'est pas un tableau. Il affectait la liste à un tableau temporaire, mais ne le fait plus pour des raisons d'efficacité. Cela signifie que l'on parcourt maintenant les valeurs actuelles et non les copies des valeurs. Les modifications de la variable de boucle peuvent changer les valeurs originelles, même après un grep ! Par exemple
 
Sélectionnez
% perl4 -e '@a = (1,2,3); for (grep(/./, @a)) { $_++ }; print "@a\n"' 1 2 3 
% perl5 -e '@a = (1,2,3); for (grep(/./, @a)) { $_++ }; print "@a\n"' 
2 3 4

Pour conserver l'ancien comportement de Perl, vous devrez affecter explicitement la liste à un tableau temporaire et ensuite parcourir ce dernier. Par exemple, vous pourriez avoir besoin de changer :

 
Sélectionnez
foreach $var (grep /x/, @liste) { ... }

en

 
Sélectionnez
foreach $var (my @tmp = grep /x/, @list) { ... }

Autrement, la modification de $var écrasera aussi les valeurs de @liste. (Cela se produit le plus souvent lorsque vous utilisez $_ comme variable de boucle et que vous appelez des sous-programmes dans la boucle qui ne mettent pas correctement $_ en local).

  • Certains messages d'erreur sont différents.
  • Certains bogues ont été éliminés par inadvertance.

24-3. Efficacité

Alors que l'essentiel du travail de programmation peut se résumer à un fonctionnement correct, vous pouvez vous retrouver à vouloir parfois un meilleur rapport qualité/prix pour votre programme Perl. Le vaste ensemble d'opérateurs, les types de données et les structures de contrôle de Perl ne sont pas forcément intuitifs quant à la rapidité d'exécution et à l'optimisation de l'espace mémoire. De nombreux compromis ont été nécessaires pendant la conception de Perl et ces décisions sont profondément ancrées dans les entrailles du code. En général, plus un programme est court et simple, plus il est rapide, mais il existe des exceptions. Ce paragraphe tente de vous aider à le faire fonctionner juste un petit peu mieux.

Si vous voulez qu'il tourne beaucoup mieux, vous pouvez jouer avec la sortie (backend) du compilateur de Perl, décrite au chapitre 18, Compiler ou réécrire votre boucle interne dans une extension C, comme l'illustre le chapitre 21, Mécanismes internes et externes.

Vous remarquerez que l'optimisation de la vitesse d'exécution est parfois coûteuse en termes d'espace mémoire ou d'efficacité pour le programmeur (on s'en rend compte en constatant que certaines des astuces ci-dessous sont incompatibles entre elles). Voilà le hic. Si la programmation était si simple, il n'y aurait pas besoin de quelque chose d'aussi compliqué qu'un être humain pour s'en occuper, n'est-ce pas?

24-3-a. Efficacité du temps d'exécution
  • Utilisez des hachages au lieu de recherches linéaires. Par exemple, au lieu de chercher dans @mots_cles pour voir si $_ est un mot-clef, construisez un hachage avec :
 
Sélectionnez
my %mots_cles; 
for (@mots_cles) {
    $mots_cles{$_}++; 
}

Vous pouvez alors savoir si $_ contient un mot-clef en testant si

 
Sélectionnez
$mots_cles{$_}

donne une valeur non nulle.

  • Évitez d'indicer alors qu'un foreach ou un opérateur de liste fait l'affaire. Non seulement l'indiçage est une opération supplémentaire, mais si votre variable d'indiçage se trouve être un nombre à virgule flottante après avoir fait de l'arithmétique, une conversion supplémentaire vers un entier est nécessaire. Il y a souvent mieux à faire. Essayez d'utiliser les opérations foreach, shift et splice. Pensez à écrire use integer.
  • Évitez goto. Il va chercher l'étiquette correspondante hors de l'endroit où vous vous trouvez.
  • Évitez printf lorsque print fait l'affaire.
  • Évitez $& et ses deux copains, $` et $'. La moindre occurrence dans votre programme fait que toutes les correspondances sauvegardent la chaîne recherchée pour une référence ultérieure possible (cela dit, une fois que c'est fait, vous pouvez en mettre autant que vous voulez).
  • Évitez d'utiliser eval sur une chaîne. Un eval d'une chaîne (mais pas d'un bloc) force la recompilation à chaque passage. L'analyseur syntaxique de Perl est plutôt rapide pour un analyseur, ce qui ne veut pas dire grand-chose. De nos jours, il existe presque toujours un meilleur moyen d'arriver à vos fins. En particulier, tout code utilisant eval pour construire des noms de variable est obsolète, puisque vous arrivez maintenant à la même chose en utilisant directement des références symboliques :
 
Sélectionnez
no strict 'refs'; 
$nom = "variable"; 
$$nom = 7# Positionne $variable à 7
  • Évitez eval CHAINE à l'intérieur d'une boucle. Mettez plutôt la boucle dans l'eval, pour éviter des compilations redondantes du code. Voir l'opérateur study au chapitre 29 pour en trouver un exemple.
  • Évitez les motifs compilés à l'exécution. Utilisez le modificateur de motif /motif/o (une seule fois) pour éviter les recompilations du motif quand celui-ci ne change pas durant la vie du processus. Pour les motifs qui changent occasionnellement, vous pouvez utiliser le fait qu'un motif nul se réfère au motif précédent, comme ceci :
 
Sélectionnez
"chaine_trouvee" =~ /$motif_courant/; # Correspondance bidon
                                     # (doit réussir). 
while (<>) {
    print if //; 
}

Ou alors, vous pouvez précompiler votre expression régulière en utilisant la construction de protection qr. Vous pouvez également utiliser eval pour recompiler un sous-programme réalisant la recherche de correspondance (si vous ne recompilez qu'occasionnellement). Cela fonctionne encore mieux si vous compilez une flopée de recherches de correspondances dans un seul sous-programme, en amortissant ainsi la surcharge due à l'appel de sous-programme.

  • Un court-circuit entre deux choix possibles est souvent plus rapide que l'expression régulière correspondante. Ainsi :
 
Sélectionnez
print if /une-bosse/ || /deux/;

est susceptible d'être plus rapide que

 
Sélectionnez
print if /une-bosse|deux/;

du moins pour certaines valeurs de une-bosse et de deux. En effet, l'optimiseur aime loger certaines opérations simples de recherche de correspondance dans des parties hautes de l'arbre syntaxique et effectue une recherche de correspondance très rapide grâce à un algorithme de Boyer-Moore. Un motif compliqué en fait perdre le bénéfice.

  • Rejetez les cas fréquents le plus tôt possible avec next if. Tout comme pour les expressions régulières simples, l'optimiseur préfère cela. Et cela a toujours un sens d'éviter le travail superflu. Vous pouvez typiquement éliminer les lignes de commentaire et les lignes vides avant même de faire un split ou un chop :
 
Sélectionnez
while (<>) {
    next if /^#/;
    next if /^$/;
    chop; 
    @gorets = split(/,/);
    ... 
}
  • Évitez les expressions régulières comportant de nombreux quantificateurs ou de grands nombres {MIN,MAX} pour des expressions entre parenthèses. De tels motifs peuvent engendrer un comportement de retour arrière d'une lenteur exponentielle à moins que les sous-motifs quantifiés ne correspondent à la première « passe ». Vous pouvez également utiliser la construction (?>...) pour forcer un sous-motif à correspondre complètement ou à échouer sans retour arrière.
  • Essayez de maximiser la longueur de toute chaîne non optionnelle dans les expressions régulières. Ce principe n'est pas intuitif, mais les longs motifs trouvent souvent plus vite une correspondance que les courts. En effet, l'optimiseur recherche les chaînes constantes et les passe à une recherche de Boyer-Moore, qui fonctionne mieux avec de longues chaînes. Compilez votre motif avec l'option de débogage -Dr pour voir ce que Dr. Perl pense être la chaîne constante la plus longue.
  • Évitez les appels de sous-programmes coûteux dans les boucles courtes. L'appel de sous-programmes engendre une certaine surcharge, surtout quand vous leur passez de longues listes de paramètres ou que les valeurs renvoyées sont longues. Essayez, par ordre croissant de désespoir, de passer les valeurs par référence, de passer les valeurs en tant que variables globales de portée dynamique, d'insérer le sous-programme littéralement (inlining) ou de réécrire toute la boucle en C. (Si vous pouvez mettre le sous-programme hors d'état de nuire en utilisant un algorithme plus malin, c'est encore mieux que toutes ces solutions.)
  • N'employez getc que pour les entrées/sorties caractère par caractère sur le terminal. En fait, ne l'utilisez pas du tout. Employez sysread.
  • Évitez les substr fréquents sur les longues chaînes, surtout si la chaîne contient de l'UTF-8. Il n'y a pas de problème pour utiliser substr au début d'une chaîne et, pour certaines tâches, vous pouvez garder le substr au début : utilisez la forme à quatre arguments de substr et « croquez » la chaîne en remplaçant la partie que vous avez happée par "" :
 
Sélectionnez
while ($tampon) {
    traiter(substr($tampon, 0, 10, "")); 
}
  • Utilisez pack et unpack au lieu de plusieurs substr.
  • Utilisez substr en tant que lvalue au lieu de concaténer des sous-chaînes. Par exemple, pour remplacer les caractères allant du quatrième au septième de $truc avec le contenu de la variable $machin, ne faites pas :
 
Sélectionnez
$truc = substr($truc,0,3) . $machin . substr($truc,7);

Contentez-vous à la place d'identifier la partie de la chaîne à remplacer et affectez-y une valeur, comme dans :

 
Sélectionnez
substr($truc, 3, 4) = $machin;

Mais faites attention que si $truc est une chaîne énorme et que $machin n'est pas exactement de même longueur que le « trou », cela entraîne également beaucoup de copies. Perl essaie de minimiser cela en copiant soit le début, soit la fin, mais il y a trop de travail à accomplir si le substr est au milieu.

  • Utilisez s/// au lieu d'une concaténation de sous-chaînes. Surtout si vous pouvez remplacer une constante par une autre de la même taille. La substitution se fait alors sur place.
  • Utilisez les modificateurs et les opérateurs équivalents and et or, au lieu des constructions conditionnelles complètes. Les modificateurs d'instructions (comme $alliance = 0 unless $fiance) et les opérateurs logiques évitent la surcharge due à l'entrée et à la sortie d'un bloc. De plus, ils sont souvent plus lisibles.
  • Utilisez $truc = $a || $b || $c. C'est beaucoup plus rapide (et plus court) que :
 
Sélectionnez
if ($a) {
    $truc = $a; 
} 
elsif ($b) {
    $truc = $b; 
} 
elsif ($c) {
    $truc = $c; 
}

De même, mettez les valeurs par défaut avec :

 
Sélectionnez
$pi ||= 3;
  • Regroupez tous les tests qui demandent la même chaîne initiale. Au moment de tester une chaîne pour y chercher divers préfixes ressemblant un tant soit peu à un branchement conditionnel multiple (N.d.T. switch structure), rassemblez tous les motifs /^a/, tous les /^b/, et ainsi de suite.
  • Ne testez pas de choses pour lesquelles vous savez qu'on ne peut pas trouver de correspondance. Utilisez last ou elsif pour éviter que la recherche de correspondance échoue pour le cas suivant dans votre instruction switch.
  • Utilisez des opérateurs spéciaux comme study, les opérations logiques sur les chaînes, les formats pack 'u' et unpack '%'.
  • Attention à ne pas utiliser un marteau-pilon pour écraser une mouche. Des instructions mal conçues comme (<STDIN>)[0] peuvent exiger de Perl un travail inutile. C'est en plein accord avec la philosophie UNIX que Perl vous donne la corde pour vous pendre.
  • Factorisez les opérations hors des boucles. L'optimiseur de Perl n'essaie pas d'enlever le code invariant des boucles. Il vous demande un certain bon sens.
  • Les chaînes peuvent être plus rapides que les tableaux.
  • Les tableaux peuvent être plus rapides que les chaînes. Tout dépend de la réutilisation ultérieure des chaînes ou des tableaux et de quelles opérations il s'agit. Des modifications importantes de chaque élément conviennent mieux aux tableaux, alors qu'une modification occasionnelle de quelques éléments convient mieux aux chaînes. Mais le plus souvent, vous devez essayer et aviser.
  • Les variables my sont plus rapides que les variables local.
  • Le tri selon une clef de tableau fabriquée peut être plus rapide qu'un joli sous-programme de tri. Une valeur de tableau donnée sera généralement comparée plusieurs fois; donc si le sous-programme doit effectuer de nombreux recalculs, il vaut mieux factoriser ce calcul dans une passe séparée avant le tri réel.
  • Si vous supprimez des caractères, tr/abc//d est plus rapide que s/[abc]//g.
  • print avec une virgule comme séparateur peut être plus rapide que la concaténation de chaînes. Par exemple :
 
Sélectionnez
print $nom_complet{$nom} . " a un nouveau répertoire home " .
    $home{$nom} . "\n";

doit coller ensemble les deux hachages et les deux chaînes constantes avant de les passer aux routines de bas niveau, ce qui n'est pas le cas de :

 
Sélectionnez
print $nom_complet{$nom}, " a un nouveau répertoire home ",
    $home{$nom}, "\n";

Cela dit, et selon les valeurs et l'architecture, la concaténation peut être plus rapide. Essayez.

  • Préférez join("", ...) à une série de chaînes concaténées. Des concaténations multiples peuvent effectuer des copies de chaînes incessantes, ce qu'évite l'opérateur join.
  • split sur une chaîne constante est plus rapide que split sur un motif. C'est-à-dire qu'il vaut mieux utiliser split(/ /, ...) plutôt que split(/ +/, ...) si vous savez qu'il n'y aura qu'un espace. Cependant, les motifs /\s+/, /^/ et // sont particulièrement optimisés, de même que le split spécial sur du blanc.
  • Il peut être rentable de préétendre un tableau ou une chaîne. Au fur et à mesure de l'accroissement des chaînes et des tableaux, Perl les étend en allouant une nouvelle copie avec de l'espace pour la croissance et en y copiant l'ancienne valeur. La préextension d'une chaîne avec l'opérateur x ou d'un tableau en positionnant $#tableau peut prévenir cette surcharge occasionnelle, ainsi que minimiser la fragmentation mémoire.
  • Ne undefinissez pas de longues chaînes et de longs tableaux s'ils doivent être réutilisés dans le même but. Ceci afin de ne pas devoir réallouer quand la chaîne ou le tableau devra être ré-étendu.
  • Préférez "\0" x 8192 à unpack("x8192",()).
  • system("mkdir ...") peut être plus rapide sur plusieurs répertoires si l'appel système mkdir(2) n'est pas disponible.
  • Évitez d'employer eof() si les valeurs de retour l'indiquent déjà.
  • Mettez dans un cache les entrées des fichiers (comme les fichiers passwd et group) qui sont susceptibles d'être réutilisées. Il est particulièrement important de mettre dans un cache les entrées provenant du réseau. Par exemple, pour mettre dans un cache la valeur de retour de gethostbyaddr lorsque vous convertissez des adresses numériques (comme 204.148.40.9) en noms (comme « www.ora.com »), vous pouvez utiliser quelque chose comme :
 
Sélectionnez
sub numennom {
    local($_) = @_;
    unless (defined $num_en_nom{$_}) {
        my(@a) = gethostbyaddr(pack('C4', split(/\./)), 2);
        $num_en_nom{$_} = @a > 0 ? $a[0] : $_; 
    }
    return $num_en_nom{$_}; 
}
  • Évitez les appels système inutiles. Les appels au système d'exploitation sont souvent coûteux. Par exemple, n'appelez pas l'opérateur time lorsqu'une valeur, $maintenant, en cache fait l'affaire. Utilisez le handle de fichier spécial _ pour éviter des appels inutiles à stat(2). Sur certains systèmes, même un appel système minimal peut exécuter un millier d'instructions.
  • Évitez les appels inutiles à system. L'opérateur system doit créer un sous-processus et exécuter le programme spécifié — ou pis encore, exécuter un shell pour exécuter le programme spécifié. On en arrive facilement à un million d'instructions.
  • Inquiétez-vous de lancer des sous-processus, mais seulement s'ils sont fréquents. Le lancement d'un simple processus pwd, hostname ou find ne vous fera pas beaucoup de mal — après tout, un shell n'arrête pas de lancer des sous-processus toute la journée. Il nous arrive d'encourager l'approche boîte à outils. Mais si, mais si.
  • Gardez la trace de votre répertoire de travail courant au lieu d'appeler sans arrêt pwd. (Un module est fourni dans la bibliothèque standard à cet effet. Voyez le module Cwd au chapitre 30, Bibliothèque standard.)
  • Évitez les métacaractères du shell dans les commandes — passez au besoin des listes à system et à exec.
  • Mettez le sticky bit sur l'interpréteur Perl sur les machines ne disposant pas de pagination à la demande :
 
Sélectionnez
chmod +t /usr/bin/perl
  • Pour mettre dans un cache les résultats de fonction, utilisez le module Memoize de CPAN.
24-3-b. Efficacité de l'espace mémoire
  • Vous pouvez utiliser vec pour un stockage compact des tableaux d'entiers si les entiers sont de taille fixe. (Les entiers de taille variable peuvent être stockés dans des chaînes UTF8.)
  • Préférez les valeurs numériques aux valeurs de chaînes équivalentes — elles demandent moins de mémoire.
  • Employez substr pour stocker des chaînes de longueur constante dans une chaîne plus grande.
  • Employez le module Tie::SubstrHash pour un stockage très compact d'un hachage, si les longueurs de clef et de valeur sont fixées.
  • Utilisez __END__ et le handle de fichier DATA pour éviter de stocker des données de programme à la fois comme chaîne et comme tableau.
  • Préférez each à keys lorsque l'ordre n'a pas d'importance.
  • Supprimez ou undef inissez les variables globales qui ne sont plus utilisées.
  • Utilisez un type de DBM pour stocker les hachages.
  • Utilisez des fichiers temporaires pour stocker les tableaux.
  • Utilisez des pipes pour décharger des traitements vers d'autres outils.
  • Évitez les opérations de listes et les absorptions de fichiers.
  • Évitez d'utiliser tr///. Chaque expression tr/// doit stocker une table de traduction d'une taille non négligeable.
  • Ne déroulez pas vos boucles, n'insérez pas littéralement (inline) vos sous-programmes.
24-3-c. Efficacité pour le programmeur
  • Utilisez des valeurs par défaut.
  • Utilisez les raccourcis géniaux donnés par les options de ligne de commande comme -a, -n, -p, -s et -i.
  • Utilisez for pour signifier foreach.
  • Lancez des commandes système avec des apostrophes inverses.
  • Utilisez <*>, etc.
  • Utilisez des motifs créés à l'exécution.
  • Utilisez *, + et {} sans modération dans vos motifs.
  • Traitez des tableaux complets à tour de bras et avalez des fichiers entiers à gogo.
  • Utilisez getc.
  • Utilisez $`, $& et $'.
  • Ne vérifiez pas les valeurs de retour sur open, puisque <HANDLE> et print HANDLE ne feront rien lorsqu'ils recevront un handle invalide.
  • Ne fermez pas vos fichiers avec close — ils le seront au prochain open.
  • Ne passez d'arguments aux sous-programmes. Utilisez des variables globales.
  • Ne nommez pas vos paramètres de sous-programmes. Vous pouvez y accéder directement par $_[EXPR].
  • Utilisez ce à quoi vous pensez en premier.
24-3-d. Efficacité pour le mainteneur
  • N'utilisez pas de valeurs par défaut.
  • Utilisez foreach pour signifier foreach.
  • Utilisez des étiquettes de boucles significatives avec next et last.
  • Utilisez des noms de variables significatifs.
  • Utilisez des noms de sous-programmes significatifs.
  • Mettez ce qui est important en premier sur la ligne en utilisant and, or et les modificateurs d'instruction (comme exit if $fini).
  • Fermez vos fichiers dès que vous en avez fini avec eux.
  • Utilisez des paquetages, des modules et des classes pour camoufler les détails d'implémentation.
  • Passez des arguments en tant que paramètres de sous-programme.
  • Nommez vos paramètres de sous-programmes avec my.
  • Mettez des parenthèses dans un souci de clarté.
  • Mettez de nombreux commentaires (utiles).
  • Incluez de la documentation pod dans votre code.
  • Utilisez use warnings.
  • Utilisez use strict.
24-3-e. Efficacité pour le porteur
  • Agitez sous son nez un généreux pourboire sous son nez.
  • Évitez les fonctions qui ne sont pas implémentées partout. Vous pouvez utiliser des tests avec eval pour voir ce qui est disponible.
  • Utilisez le module Config ou la variable $^O pour trouver sur quelle machine vous êtes en train de tourner.
  • Ne vous attendez pas à ce que les flottants et les doubles existent en natif pour pack et unpack sur des machines exotiques.
  • Employez l'ordre des octets pour le réseau (les formats « n » et « N » de pack) pour envoyer des données binaires sur le réseau.
  • N'envoyez pas de données binaires sur le réseau. Envoyez de l'ASCII. Ou mieux, envoyez de l'UTF8. Ou encore mieux, envoyez de l'argent.
  • Vérifiez $] ou $^V pour savoir si la version courante implémente toutes les fonctionnalités que vous utilisez.
  • N'employez pas $] ni $^V. Utilisez require ou use avec un numéro de version.
  • Mettez une bidouille eval exec même si vous n'en avez pas l'usage, votre programme pourra ainsi fonctionner sur le peu de systèmes qui disposent de shells à la Unix, mais qui ne reconnaissent pas la notation #!.
  • Mettez la ligne #!/usr/bin/perl même si vous ne l'utilisez pas.
  • Testez les variantes de commandes Unix. Certains programmes find ne comprennent pas l'option -xdev, par exemple.
  • Évitez les variantes des commandes Unix si l'équivalent peut être fait en interne. Les commandes Unix ne fonctionnent pas très bien sous MS-DOS ou VMS.
  • Mettez tous vos scripts et toutes vos pages de manuel sur un système de fichiers réseau unique, monté sur toutes vos machines.
  • Publiez votre module sur CPAN. Vous recevrez énormément de commentaires s'il n'est pas portable.
24-3-f. Efficacité pour l'utilisateur
  • Au lieu d'imposer aux utilisateurs d'entrer les données ligne par ligne, mettez-les dans leur éditeur favori.
  • Encore mieux, utilisez une interface graphique (GUI) comme Tk, où ils peuvent contrôler l'ordre des événements. (Perl/Tk est disponible sur CPAN.)
  • Donnez aux utilisateurs quelque chose à lire pendant que vous continuez à faire votre travail.
  • Employez l'autochargement afin que le programme paraisse tourner plus vite.
  • Donnez la possibilité d'avoir des messages d'aide à chaque invite.
  • Donnez un conseil d'utilisation utile si les utilisateurs ne donnent pas d'entrées correctes.
  • Affichez l'action par défaut à chaque invite, et peut-être quelques autres possibilités.
  • Choisissez les valeurs par défaut pour les débutants. Permettez aux experts de les modifier.
  • Utilisez une entrée d'un seul caractère là où cela a un sens.
  • Modelez la dynamique d'interaction d'après d'autres choses que l'utilisateur connaît bien.
  • Rendez les messages d'erreurs explicites sur ce qu'il faut corriger. Fournissez toutes les informations pertinentes comme le nom de fichier et le code d'erreur, comme ceci :
 
Sélectionnez
open(FICHIER, $fichier)
    or die "$0: Ouverture de $fichier en lecture impossible : $!\n";
  • Utilisez fork && exit pour vous détacher lorsque le reste du script fait du traitement en batch.
  • Permettez aux arguments de provenir soit de la ligne de commande, soit de l'entrée standard.
  • Ne mettez pas de restrictions arbitraires dans votre programme.
  • Préférez les champs de longueur variable aux champs de longueur fixe.
  • Utilisez des protocoles réseau orientés texte.
  • Dites à tout le monde d'utiliser des protocoles réseau orientés texte !
  • Dites à tout le monde de dire à tout le monde d'utiliser des protocoles réseau orientés texte !
  • Soyez paresseux par procuration.
  • Soyez sympa.

24-4. Programmation stylée

Vous aurez certainement vos propres préférences en matière de formatage, mais quelques lignes de conduite rendront vos programmes plus faciles à lire, à comprendre et à maintenir.

Le plus important est de toujours lancer vos programmes sous le pragma use warnings. (Vous pouvez désactiver les avertissements dont vous ne voulez pas avec no warnings.) Vous devriez également tourner sous le pragma use strict ou avoir une bonne excuse pour ne pas le faire. Le pragma use sigtrap et même use diagnostics peuvent aussi être bénéfiques.

Quant à l'esthétique du code, la seule chose à laquelle Larry prête une certaine importance est que l'accolade fermante d'un BLOC multiligne devrait se trouver sur la même colonne que le mot-clef qui a commencé la construction. Par ailleurs, il a d'autres préférences qui ne sont pas si impératives. Les exemples de ce livre respectent ces conventions (ou du moins le devraient) :

  • Indentation sur 4 colonnes.
  • Une accolade ouvrante doit se trouver sur la même ligne que le mot-clef qui la précède, si possible ; sinon, il faut les aligner verticalement.
 
Sélectionnez
while ($condition) {    # ici, court : sur la même ligne
    # faire quelque chose 
}

# si la condition déborde, aligner les accolades 
# l'une au-dessus de l'autre. 
while ($une_condition and $autre_condition
       and $encore_une_longue_condition) 
{
    # faire quelque chose 
}
  • Mettez un espace avant l'accolade ouvrante d'un BLOC multiligne.
  • Un BLOC court peut être mis sur une seule ligne, accolades comprises.
  • Omettez le point-virgule dans un BLOC court d'une seule ligne.
  • Entourez la plupart des opérateurs avec des espaces.
  • Entourez un indice « complexe » (à l'intérieur de crochets) par des espaces.
  • Mettez des lignes blanches entre les morceaux de code qui font des choses différentes.
  • Mettez un saut de ligne entre une accolade fermante et else.
  • Ne mettez pas d'espace entre un nom de fonction et sa parenthèse ouvrante.
  • Ne mettez pas d'espace avant un point-virgule.
  • Mettez un espace après chaque virgule.
  • Divisez les longues lignes après un opérateur (mais avant and et or, même lorsqu'ils s'écrivent && et ||).
  • Alignez verticalement les items correspondants.
  • Omettez les ponctuations redondantes tant que la clarté n'en souffre pas.
  • Larry a ses raisons concernant chacun de ces points, mais il ne prétend pas que le cerveau de tout le monde fonctionne (ou ne fonctionne pas) comme le sien. Voici d'autres questions de style plus substantielles, qui donnent matière à réflexion :
  • Ce n'est pas parce que vous pouvez faire quelque chose d'une certaine manière que vous devez le faire ainsi. Perl est conçu pour fournir plusieurs façons de faire tout ce que l'on veut, il convient donc de choisir la plus lisible. Par exemple :
 
Sélectionnez
open(TRUC,$truc) or die "ouverture de $truc impossible : $!";

vaut mieux que

 
Sélectionnez
die "Ouverture de $truc impossible : $!" unless open(TRUC,$truc);

car la deuxième manière cache l'essentiel de l'instruction dans un modificateur. À l'inverse,

 
Sélectionnez
print "Début de l'analyse\n" if $verbeux;

vaut mieux que

 
Sélectionnez
$verbeux and print "Début de l'analyse\n";

puisque l'essentiel n'est pas de savoir si l'utilisateur a ou non tapé -v.

  • De même, ce n'est pas parce qu'un opérateur vous permet d'utiliser des arguments par défaut que vous devez en faire usage. Ils sont réservés aux programmeurs paresseux qui écrivent des scripts vite fait, ne servant qu'une fois. Si vous voulez que vos programmes soient lisibles, pensez à fournir les arguments.
  • Dans la même lignée, ce n'est pas parce que vous pouvez omettre les parenthèses dans de nombreux cas que vous devez le faire :
 
Sélectionnez
return print reverse sort num values %hachage; 
return print(reverse(sort num (values(%hachage))));

En cas de doute, mettez des parenthèses. Au pire, cela permettra aux pauvres idiots

de taper sur la touche % dans vi. Même si vous n'avez aucun doute, imaginez l'état mental de la personne qui devra maintenir le code après vous et qui mettra probablement des parenthèses au mauvais endroit.

  • Ne vous livrez pas à un exercice stupide de contorsionniste pour sortir en haut ou en bas d'une boucle. Perl fournit l'opérateur last qui vous permet d'en sortir au milieu. Vous pouvez optionnellement l'« exdenter » un peu pour le rendre plus visible :
 
Sélectionnez
LIGNE:
    for (;;) \{
        instructions;
    last LIGNE if $machin;
        next LIGNE if /^#/;
        instructions;
    }
  • N'ayez pas peur d'utiliser des étiquettes de boucles — elles sont là tant pour améliorer la lisibilité que pour permettre les ruptures de boucles sur plusieurs niveaux. Voir l'exemple précédent.
  • Évitez d'utiliser grep, map ou les apostrophes inverses dans un contexte vide, c'est-à-dire lorsque vous ne vous occupez pas des valeurs de retour. Ces fonctions ont toutes des valeurs de retour, qui ne demandent qu'à être utilisées. Sinon, employez une boucle foreach ou la fonction system.
  • Dans un but de portabilité, quand vous employez des fonctionnalités qui peuvent ne pas être implémentées sur toutes les machines, testez-les dans un eval pour savoir si elles échouent. Si vous connaissez la version ou le patch level (N.d.T. Niveau de correction) d'une fonctionnalité particulière, vous pouvez tester $] ($PERL_VERSION avec le module English) pour savoir si telle fonctionnalité est présente. Le module Config permet également de retrouver les valeurs déterminées par le programme Configure quand Perl a été installé.
  • Choisissez des identificateurs mnémoniques. Si vous ne vous souvenez pas de la signification du terme mnémonique, il y a un problème.
  • Alors que des identificateurs courts comme $reussi conviennent souvent parfaitement, employez des soulignés pour séparer les mots. Il est généralement plus facile de lire un $nom_de_variable_comme_ceci qu'un $NomDeVariableCommeCeci, surtout pour des lecteurs dont la langue d'origine n'est pas l'anglais. Même chose pour $NOM_DE_VARIABLE_COMME_CECI.
  • Les noms des paquetages représentent parfois une exception à cette règle. Perl réserve de façon informelle les noms en minuscule pour des modules de pragmas comme integer et strict. Les autres modules devraient commencer par une majuscule et mélanger les casses, mais de préférence sans caractère souligné en raison des restrictions de nommage de certains systèmes de fichiers primitifs.
  • Il peut s'avérer utile d'utiliser les casses de caractères pour indiquer la visibilité ou la nature d'une variable. Par exemple :
 
Sélectionnez
$TOUT_EN_MAJUSCULES    # constantes uniquement (attention aux
                       # conflits avec les vars de Perl!)
$Quelques_Majuscules   # vars globales/statiques sur tout un paquetage 
$pas_de_majuscules     # vars my() ou local(), dont la portée ne
                       #dépasse pas une fonction

Pour diverses raisons plutôt vagues, les noms de méthodes et de fonctions semblent plus lisibles lorsqu'ils sont entièrement en minuscules. Par exemple, $obj->en_chaine().

Vous pouvez faire précéder le nom d'un caractère souligné pour indiquer qu'une variable ou une fonction ne devrait pas être utilisée hors du paquetage qui l'a définie. (Perl ne force pas ceci ; ce n'est qu'une forme de documentation.)

  • Dans le cas d'une expression régulière réellement inextricable, utilisez le modificateur /x et insérez des espaces pour améliorer le rapport signal/bruit.
  • N'employez pas de slash comme délimiteurs lorsque votre expression en comporte déjà trop ou contient trop d'antislashs.
  • N'utilisez pas les apostrophes ou les guillemets comme délimiteurs lorsque votre chaîne en contient déjà. Utilisez plutôt les pseudofonctions q//, qq// ou qx//.
  • Utilisez les opérateurs and et or pour éviter de mettre trop de parenthèses autour des opérateurs de liste, et pour réduire l'incidence des opérateurs de ponctuation comme && et ||. Appelez vos sous-programmes comme s'il s'agissait de fonctions ou d'opérateurs de liste afin d'éviter un excès d'esperluettes et de parenthèses.
  • Utilisez des documents « ici-même » (here documents) au lieu d'instructions print répétées.
  • Alignez verticalement les choses correspondantes, surtout si elles sont trop longues pour tenir sur une ligne :
 
Sélectionnez
$IDX = $ST_MTIME; 
$IDX = $ST_ATIME      if $opt_u; 
$IDX = $ST_CTIME      if $opt_c; 
$IDX = $ST_SIZE       if $opt_s; 

mkdir $rep_tmp, 0700 or die "mkdir $rep_tmp impossible : $!"; chdir($rep_tmp)     or die "chdir $rep_tmp impossible : $!"; 
mkdir 'tmp',    0777 or die "mkdir $rep_tmp/tmp impossible : $!";
  • Ce que nous vous répétons trois fois est vrai :

Vérifiez toujours les codes de retour des appels système.

Vérifiez toujours les codes de retour des appels système .

VÉRIFIEZ TOUJOURS LES CODES DE RETOUR DES APPELS SYSTÈME.

  • Les messages d'erreur devaient aller vers STDERR et devraient indiquer de quel programme provient l'erreur, ainsi que l'appel de fonction et les arguments. Le plus important est qu'ils devraient contenir, lorsque les appels système ont échoué, le message d'erreur système standard sur ce qui ne va pas. En voici un exemple simple, mais suffisant :
 
Sélectionnez
opendir(R, $rep) or die "opendir $rep impossible : $!";
  • Alignez vos traductions si cela a un sens :
 
Sélectionnez
tr [abc]
   [xyz];
  • Pensez à la réutilisabilité. Pourquoi gâcher votre énergie cérébrale sur des scripts jetables alors que vous êtes susceptible de les réécrire plus tard? Vous pouvez rendre votre code générique. Vous pouvez écrire un module ou une classe d'objets. Vous pouvez faire tourner proprement votre code en activant use strict et -w. Vous pouvez donner votre code aux autres. Vous pouvez changer votre vision globale du monde. Vous pouvez. . . oh et puis tant pis.
  • Soyez cohérent.
  • Soyez sympa.

24-5. Perl avec aisance

Nous avons abordé quelques idiomes dans les paragraphes précédents (pour ne pas citer les chapitres précédents), mais il y en a beaucoup d'autres que vous verrez couramment si vous lisez des programmes réalisés par des programmeurs Perl accomplis. Lorsque nous parlons de Perl idiomatique dans ce contexte, nous ne parlons pas seulement d'un ensemble arbitraire d'expressions Perl au sens figé. Nous parlons plutôt de code Perl montrant une compréhension des méandres du langage, que vous pouvez avaler tel quel sans vous poser de question. Et quand l'avaler.

Nous n'imaginons même pas énumérer tous les idiomes que vous pourriez rencontrer — cela prendrait tout un livre, aussi épais que celui-ci. Et peut-être même deux. (Voir Perl en action, par exemple.) Mais voici quelques idiomes importants, où l'on pourrait définir « important » comme « ce qui incite les persiflages des gens qui pensent déjà savoir comment les langages informatiques devraient fonctionner ».

  • Utilisez => en lieu et place d'une virgule partout où vous pensez que cela améliore la lisibilité :
 
Sélectionnez
return bless $pagaille => $classe;

Vous devez lire cela comme « Consacrer cette pagaille dans la classe spécifiée. » Faites juste attention à ne pas l'employer après un mot dont vous ne voudriez pas qu'il soit automatiquement mis entre guillemets :

 
Sélectionnez
sub truc()  { "TRUC" } 
sub machin() { "MACHIN"} 
print truc => machin;    # affiche trucMACHIN et non TRUCMACHIN.

Un autre endroit approprié à l'emploi de => est à proximité d'une virgule littérale que l'on pourrait confondre visuellement :

 
Sélectionnez
join(", " => @tableau);

Perl vous offre plus d'une manière de le faire (N.d.T. There's More Than One Way To Do It, le slogan de Perl) vous pouvez donc faire preuve de créativité. Faites-en preuve !

  • Utilisez le pronom singulier pour améliorer la lisibilité :
 
Sélectionnez
for (@lignes) {
    $_ .= "\n"; 
}

La variable $_ est la version de Perl d'un pronom et il signifie essentiellement « il ».(180) Ainsi, le code ci-dessus signifie « pour chaque ligne, ajoutez-lui un saut de ligne. » De nos jours, vous pouvez même écrire cela comme :

 
Sélectionnez
$_ .= "\n" for @lignes;

Le pronom $_ est tellement important pour Perl qu'il est obligatoire de l'utiliser dans grep et map. Voici un moyen de configurer un cache pour les résultats communs d'une fonction coûteuse :

 
Sélectionnez
%cache = map { $_ => couteuse($_) } @args_communs; 
$xval = $cache{$x} || couteuse($x);
  • Omettez le pronom pour améliorer encore plus la lisibilité.(181)
    Utilisez des contrôles de boucles avec des modificateurs d'instructions.
 
Sélectionnez
while (<>) {
    next if /^=pour\s+(index|après)/;
    $cars += length;
    $mots += split;
    $lignes += y/\n//; 
}

Il s'agit d'un extrait de code que nous avons utilisé pour compter les pages de ce livre. Lorsque vous vous apprêtez à travailler souvent avec la même variable, il est souvent plus lisible de laisser complètement tomber les pronoms, contrairement à la croyance populaire.

Cet extrait démontre également l'utilisation idiomatique de next avec un modificateur d'instruction pour court-circuiter une boucle. La variable $_ est toujours la variable de contrôle de boucle dans grep et, map mais la référence du programme pointant dessus est souvent implicite :

 
Sélectionnez
$longueur_hachage = grep { length } @aleatoire;

Nous prenons ici une liste de scalaires aléatoires et ne choisissons que ceux qui ont une longueur supérieure à 0.

  • Utilisez for pour positionner l'antécédent d'un pronom :
 
Sélectionnez
for ($episode) {
    s/fred/barney/g;
    s/wilma/betty/g;
    s/pebbles/bambam/g; 
}

Mais que se passe-t-il s'il n'y a qu'un seul élément dans la boucle ? Il s'agit d'un moyen pratique de positionner « le », c'est-à-dire $_. En linguistique, cela s'appelle une topicalisation. Ce n'est pas de la triche, c'est de la communication.

  • Référencez implicitement le pronom pluriel, @_.
  • Utilisez les opérateurs de contrôle de f lux pour fixer les valeurs par défaut :
 
Sélectionnez
sub aboyer {
    my Chien $medor = shift;
    my $qualite = shift || "jappant";
    my $quantite = shift || "sans_arrêt";
    ... 
}

Nous avons employé ici l'autre pronom de perl, @_, qui signifie « eux ».(182) Les arguments d'une fonction arrivent toujours comme « eux ». L'opérateur shift sait qu'il doit travailler sur @_ si vous l'omettez, tout comme l'employé de Disneyland crierait « Au suivant ! » sans préciser quelle file d'attente est supposée avancer. (Il n'y a pas lieu de spécifier, car il n'y a qu'une seule queue qui importe.) Le || peut être utilisé pour fixer les valeurs par défaut malgré ses origines d'opérateur booléen, puisque Perl renvoie la première valeur qui soit vraie. Les programmeurs Perl font souvent preuve d'une attitude assez cavalière avec la vérité ; la ligne ci-dessus ne fonctionnerait pas si, par exemple, vous essayiez de spécifier une quantité nulle. Mais tant que vous ne voulez jamais positionner $qualite ou $quantite à une valeur fausse, l'idiome marche parfaitement. Il n'y a pas lieu de devenir superstitieux et de lancer partout des appels à defined et exists. Tant que la valeur ne devient pas accidentellement fausse, tout ira bien.

  • Utilisez les formes d'affectation des opérateurs, y compris les opérateurs de contrôle de flux :
 
Sélectionnez
$xval = $cache{$x} ||= couteuse($x);

Ici nous n'initialisons pas du tout notre cache. Nous ne faisons que nous reposer sur l'opérateur ||= pour appeler couteuse($x) et ne l'affecter à $cache{$x} que si $cache{$x} est faux. Le résultat de ceci est la nouvelle valeur de $cache{$x} quelle qu'elle soit. Encore une fois, nous avons adopté une approche quelque peu cavalière de la vérité, en ce que si nous mettons en cache une valeur fausse, couteuse($x) sera à nouveau appelée. Le programmeur sait peut-être que cela fonctionne, car couteuse{$x} n'est pas coûteuse lorsqu'elle renvoie une valeur fausse. Ou peut-être que le programmeur a simplement tendance à bâcler son travail. Ce qui peut être analysé comme une forme de créativité.

  • Utilisez les contrôles de boucles en tant qu'opérateurs, pas seulement en tant qu'instructions. Et. . .
  • Utilisez les virgules comme de petits points-virgules :
 
Sélectionnez
while (<>) { 
    $commentaires++, next if /^#/;
    $blancs++, next       if /^\s*$/; 
    last                  if /^__END__/;
    $code++; 
} 
print "commentaires = $commentaires\nblancs = $blancs\ncode = $code\n";

Ceci montre que nous comprenons que les modificateurs d'instructions modifient les instructions, alors que next est un simple opérateur. Cela montre également que la virgule, lorsqu'elle est utilisée de façon idiomatique pour séparer les expressions, exactement comme vous le feriez avec un point-virgule. (La différence est que la virgule garde les deux expressions comme faisant partie de la même instruction, sous le contrôle de l'unique modificateur d'instruction.)

  • Utilisez le contrôle de flux à votre avantage :
 
Sélectionnez
while (<>) { 
    /^#/          and $commentaires++; 
    /^s*$/        and $blancs++;
    /^__END__/    and last;
    $code++; 
}
print "commentaires = $commentaires\nblancs = $blancs\ncode = $code\n";

Il s'agit exactement de la même boucle, seulement cette fois-ci, les motifs sont au début. Le programmeur Perl perspicace comprend que la compilation donne exactement le même code interne que l'exemple précédent. Le modificateur if n'est qu'une conjonction and (ou &&) inversée et le modificateur unless est une conjonction or (ou ||) inversée.

  • Utilisez les boucles implicites fournies par les options -n et -p.
  • Ne mettez pas de point-virgule à la fin d'un bloc d'une seule ligne :
 
Sélectionnez
#!/usr/bin/perl -n 
$commentaires++, next LINE if /^#/; 
$blancs++, next LINE       if /^\s*$/; 
last LINE                  if /^__END__/; 
$code++; 

END { print "commentaires = $commentaires\nblancs = $blancs\ncode = $code\n" }

Il s'agit essentiellement du même programme que précédemment. Nous avons mis une étiquette LINE (LIGNE en anglais) explicite dans les opérateurs de contrôle de boucle parce que nous en avions envie, mais nous n'y étions pas obligés, puisque la boucle LINE implicite, fournie par -n est la boucle encadrante la plus intérieure. Nous avons employé END pour avoir l'instruction print finale à l'extérieur de la boucle principale implicite, exactement comme dans awk.

  • Utilisez des documents « ici-même » (here documents) lorsque les affichages deviennent monstrueux.
  • Utilisez un délimiteur significatif dans les documents « ici-même » :
 
Sélectionnez
END { print <<"COMPTEURS" } commentaires = $commentaires 
blancs = $blancs 
code = $code 
COMPTEURS

Au lieu d'utiliser plusieurs print, le programmeur parlant Perl couramment emploie une chaîne multiligne avec interpolation. Bien que nous ayons appelé ceci une « étourderie courante » auparavant, nous avons effrontément enlevé le point-virgule final, car il n'est pas nécessaire à la fin du bloc END. (Si nous le changeons un jour en bloc multiligne, nous remettrons le point-virgule.)

  • Faites des substitutions et des traductions en passant(183) sur un scalaire :
 
Sélectionnez
($nouveau = $ancien) =~ s/mauvais/bon/g;

Puisque les lvalues sont lvaluables(184), pour ainsi dire, vous verrez souvent les gens changer une valeur en passant alors qu'elle est en train d'être affectée. Ceci peut en fait éviter une copie de chaîne en interne (si nous arrivons jamais à implémenter l'optimisation) :

 
Sélectionnez
chomp($reponse = <STDIN>);

Toute fonction qui modifie un argument sur place peut utiliser l'astuce en passant. Mais attendez, il y a mieux !

  • Ne vous limitez pas à modifier les scalaires en passant :
 
Sélectionnez
for (@nouveau = @ancien) { s/mauvais/bon/g }

Ici, nous copions @ancien dans @nouveau, en modifiant tout en passant (pas tout d'un coup, bien entendu — le bloc est exécuté de manière répétitive, un « il » (c'est-à-dire $_) à la fois).

  • Passez les paramètres nommés en utilisant l'opérateur virgule de fantaisie, =>.
  • Fiez-vous à l'affectation à un hachage pour traiter un argument pair/impair :
 
Sélectionnez
sub aboyer {
    my Chien $medor = shift;
    my %param = @_
    my $qualite = $param{QUALITE} || "jappant";
    my $quantite = $param{QUANTITE} || "sans_arrêt";
    ... 
} 
$fido->aboyer( QUANTITE => "une_fois",
                QUALITE => "ouaf" );

Les paramètres nommés sont souvent un luxe abordable. Et avec Perl, vous les avez pour rien, si vous ne comptez pas le coût de l'affectation de hachage.

  • Répétez les expressions booléennes jusqu'à obtenir une valeur fausse.
  • Utilisez les recherches de correspondances minimales lorsqu'elles sont appropriées.
  • Utilisez le modificateur /e pour évaluer une expression de remplacement :
 
Sélectionnez
#!/usr/bin/perl -p
while s/^(.*)?(\t+)/$1 . ' ' x length($2) *4 -length($1) %4)/e;

Ce programme corrige tout fichier que vous recevez de la part de quelqu'un pensant par erreur qu'il peut redéfinir les tabulations matérielles pour qu'elles occupent 4 espaces au lieu de 8. Il utilise plusieurs idiomes importants. Tout d'abord, l'idiome 1 est commode lorsque tout le travail que vous voulez accomplir dans la boucle est en fait déjà réalisé par la condition. (Perl est assez malin pour ne pas vous avertir que vous êtes en train d'utiliser 1 dans un contexte vide.) Nous devons répéter cette substitution, car chaque fois que nous remplaçons un certain nombre d'espaces par des tabulations, nous devons recalculer depuis le début la colonne où doit se trouver la prochaine tabulation.

Le (.*?) correspond à la plus petite chaîne possible jusqu'à la première tabulation, en utilisant le modificateur de correspondance minimal (le point d'interrogation). Dans ce cas, nous aurions pu utiliser un * gourmand comme d'ordinaire, ainsi : ([^\t]*). Mais cela ne marche que parce qu'une tabulation est un seul caractère, nous avons donc employé une classe de caractères avec une négation pour éviter de dépasser la première tabulation. En général, la correspondance minimale est plus élégante et elle n'échoue pas si la prochaine chose qui doit correspondre fait plus d'un caractère.

Le modificateur /e réalise une substitution en utilisant une expression plutôt qu'une chaîne. Cela nous permet d'effectuer les calculs dont nous avons besoin au moment exact où nous en avons besoin.

  • Utilisez une mise en page et des commentaires créatifs pour les substitutions complexes :
 
Sélectionnez
#!/usr/bin/perl -p 
while s{
    ^           # attachement au début 
    (           # commence le premier regroupement
         .*?    # correspond au nombre de caractères minimal 
    )           # termine le premier regroupement 
    (           # commence le second regroupement
        \t+     # correspond à une ou plusieurs tabulations 
    )           # termine le second regroupement 
} 
{
    my $lg_espaces = length($2) * 4;    # comptage des tabulations 
                                        # complètes
    $lg_espaces -= length($1) % 4# longueur de la tabulation 
                                        # incomplète
    $1 . ' ' x $lg_espaces# calcule le nombre d'espaces 
                                        # correct 
}ex;

Nous en faisons probablement un peu trop, mais certaines personnes trouvent cet exemple plus impressionnant que le précédent qui tenait sur une seule ligne. À vous de voir.

Allez de l'avant et utilisez $` si vous le sentez :

 
Sélectionnez
while s/(\t+)/' ' x (length($1) * 4 -length($`) % 4)/e;

Il s'agit ici d'une version plus courte, qui utilise $`, bien connu pour son impact sur les performances. Sauf que nous n'utilisons que sa longueur, alors ça ne compte pas vraiment.

Utilisez les décalages directement à partir des tableaux @-(@LAST_MATCH_START) et

 
Sélectionnez
@+ (@LAST_MATCH_END): 1 while s/\t+/' ' x (($+[0] -$-[0]) * 4 -$-[0] % 4)/e;

Cet exemple-ci est encore plus court. (Si vous ne voyez pas de tableaux ici, essayez à la place de rechercher des éléments de tableaux.) Voir @- et @+ au chapitre 28.

Utilisez eval avec une valeur de retour constante :

 
Sélectionnez
sub est_un_motif_valide {
    my $motif = shift;
    return eval { "" =~ /$motif/; 1 } || 0; 
}

Vous n'avez pas à utiliser l'opérateur eval {} pour renvoyer une valeur réelle. Ici, nous renvoyons toujours 1 si la correspondance parvient à se terminer. Toutefois, si le motif contenu dans $motif explose, l'eval intercepte ceci et renvoie undef à la condition booléenne de l'opérateur ||, qui transforme cela vers la valeur définie 0 (simplement pour être poli, puisque undef est également faux, mais pourrait conduire quelqu'un à penser que la routine est_un_motif_valide s'est mal comportée et nous ne voudrions pas que cela se produise, n'est-ce pas ?).

  • Utilisez des modules pour faire le sale boulot.
  • Utilisez des usines d'objets.
  • Utilisez des fonctions de rappel (callbacks).
  • Utilisez des piles pour garder une trace du contexte.
  • Utilisez des indices négatifs pour accéder à la fin d'un tableau ou d'une chaîne :
 
Sélectionnez
use XML::Parser;
                    
$p = new XML::Parser Style => 'subs'; 
setHandlers $p Char => sub { $sorties[-1] .= $_[1] }; 

push @sorties, "";

sub literal {
    $sorties[-1] .= "C<";
    push @sorties, ""; 
}

sub literal_ {
    my $texte = pop @sorties;
    $sorties[-1] .= $texte . ">"; 
} 
...

Il s'agit d'un court extrait d'un programme de 250 lignes que nous avons utilisé pour retraduire la version XML de l'ancienne édition de ce livre(185) en format pod afin que nous puissions l'ouvrir pour la présente édition dans un Véritable Éditeur de Texte.

La première chose que vous remarquerez est que nous nous appuyons sur le module XML::Parser (de CPAN) pour analyser notre XML correctement, nous n'avons donc pas à comprendre comment le faire. Cela soulage déjà notre programme de quelque milliers de lignes (en supposant que nous réimplémentions en Perl tout ce que XML::Parser accomplit pour nous,(186) y compris la traduction de presque tous les ensembles de caractères vers de l'UTF-8).

XML::Parser utilise un idiome de haut niveau appelé une usine d'objets. Dans ce cas, il s'agit d'une usine d'analyseur. Lorsque nous créons un objet XML::Parser, nous lui disons quel style d'interface d'analyseur nous voulons et il en crée un pour nous. C'est un excellent moyen de construire une plate-forme de test pour une application lorsque vous n'êtes pas sûr de savoir quelle interface sera la meilleure à long terme. Le style subs ne représente qu'une des interfaces de XML::Parser. En fait, il s'agit de l'une des plus anciennes interfaces et probablement pas la plus populaire de nos jours.

La ligne setHandlers montre un appel de méthode sur l'analyseur, pas en notation f léchée, mais dans la notation « d'objet indirect », qui vous permet, entre autres, d'omettre les parenthèses autour des arguments. La ligne utilise également l'idiome des paramètres nommés que nous avons vu plus tôt.

La ligne montre également un autre concept très puissant, la notion de fonction de rappel (callback). Plutôt que d'appeler l'analyseur pour obtenir l'élément suivant, nous lui demandons de nous appeler. Pour les balises XML nommées, comme <literal>, ce style d'interface appellera automatiquement un sous-programme de ce nom (ou le nom avec un souligné à la fin pour la balise fermante correspondante). Mais les données entre les balises n'ont pas de nom, nous configurons donc une fonction de rappel, Char, avec la méthode setHandlers. Ensuite, nous initialisons le tableau @sorties, qui est une pile des affichages. Nous y mettons une chaîne vide pour représenter le fait que nous n'avons collecté aucun texte à ce niveau d'imbrication des balises (0, initialement).
C'est maintenant que réapparaît la fonction de rappel. Dès que nous voyons un texte, il est automatiquement ajouté après l'élément final du tableau, via l'idiome $sortie[-1] dans la fonction de rappel. Au niveau de balise le plus extérieur, $sorties[-1] est équivalent à $sortie[0], $sorties[0] termine donc notre affichage complet. (Éventuellement. Mais nous devons d'abord traiter les balises.)

Supposez que nous voyions une balise <literal>. Alors le sous-programme literal est appelé, ajoute du texte à l'affichage courant, puis pousse un nouveau contexte dans la pile @sorties. Dès lors, tout texte rencontré jusqu'à la balise fermante se voit ajouté à cette nouvelle fin de la pile. Lorsque nous rencontrons la balise fermante, nous dépilons (avec pop) le $texte que nous avons collecté hors de la pile @sorties et nous ajoutons le reste des données métamorphosées à la nouvelle (c'est-à-dire, l'ancienne) fin de la pile, dont le résultat est la traduction de la chaîne XML, <literal>texte</literal>, vers la chaîne pod correspondante, C<texte>.

Les sous-programmes pour les autres programmes sont exactement les mêmes, à part qu'ils sont différents.

  • Utilisez my sans affectation pour créer un tableau ou un hachage vide.
  • Éclatez la chaîne par défaut selon les espaces qu'elle contient.
  • Affectez aux listes de variables pour en collecter autant que vous voulez.
  • Utilisez l'autovivification des références indéfinies pour les créer.
  • Autoincrémentez les éléments indéfinis de tableau ou de hachage pour les créer.
  • Utilisez l'autoincrémentation d'un hachage %vus pour déterminer l'unicité.
  • Affectez à une variable temporaire my bien pratique dans la condition.
  • Utilisez le comportement de protection automatique des accolades.
  • Utilisez un mécanisme de protection alternatif pour interpoler les guillemets.
  • Utilisez l'opérateur ?: pour choisir entre deux arguments d'un printf.
  • Alignez verticalement les arguments de printf avec leur champ % :
 
Sélectionnez
my %vus; 
while (<>) {
    my ($a, $b, $c) = split;
    print unless $vus{$a}{$b}{$c}++; 
} 
if (my $tmp = $vus{nifnif}{nafnaf}{noufnouf}) {
    printf qq("nifnif nafnaf noufnouf" vu %d coup%s\n), 
                                          $tmp, $tmp == 1 ? "" : "s"; 
}

Ces neuf lignes débordent d'idiomes. La première ligne donne un hachage vide, car nous n'y affectons rien. Nous parcourons itérativement les lignes entrées en positionnant « le », c'est-à-dire $_, implicitement, puis nous utilisons un split sans arguments qui « l' » éclate selon les espaces qu'« il » contient. Ensuite nous prenons les trois premiers mots avec une affectation de liste, en rejetant les mots suivants. Puis nous mémorisons les trois premiers mots dans un hachage à trois dimensions, qui créent automatiquement (si nécessaire) les deux premiers éléments de référence et le dernier élément de comptage pour l'autoincrémentation à incrémenter. (Avec use warnings, l'autoincrémentation ne vous avertira jamais que vous utilisez des valeurs indéfinies, car l'autoincrémentation est un moyen accepté pour définir des valeurs indéfinies.) Nous imprimons alors la ligne si nous n'en avons jamais vu avant commençant par ces trois mots, car l'autoincrémentation est une postincrémentation, qui, en plus d'incrémenter la valeur du hachage, renverra l'ancienne valeur vraie s'il y en avait une.

Après la boucle, nous testons %vus à nouveau pour voir si une combinaison particulière de trois mots a été vue. Nous utilisons le fait que nous pouvons mettre un identificateur littéral entre accolades pour qu'il soit automatiquement protégé, comme avec des guillemets. Sinon, nous aurions dû écrire $vus{"nafnaf"} {"nifnif"}{"noufnouf"}, ce qui est une corvée même lorsque vous n'êtes pas poursuivi par un grand méchant loup.

Nous affectons le résultat de $vus{nifnif}{nafnaf}{noufnouf} à une variable temporaire avant même de la tester dans le contexte booléen fourni par le if. Comme l'affectation renvoie sa valeur de gauche, nous pouvons toujours tester la valeur pour voir si elle était vraie. Le my vous fait sauter aux yeux qu'il s'agit d'une nouvelle variable et que nous ne vérifions pas une égalité, mais réalisons une affectation. Cela aurait également parfaitement marché sans le my et un programmeur Perl expert aurait encore remarqué immédiatement que nous utilisions un = au lieu de deux ==. (Un programmeur Perl intermédiaire aurait cependant pu être bluffé. Le programmeur Pascal de n'importe quel niveau aura de l'écume aux lèvres.)

Si l'on passe à l'instruction printf, vous pouvez voir la forme qq() des guillemets que nous avons employée afin de pouvoir interpoler les guillemets ordinaires ainsi que le retour à la ligne. Nous aurions aussi bien pu interpoler directement ici $tmp, puisqu'il s'agit effectivement d'une chaîne entre guillemets, mais nous avons choisi de pousser plus loin l'interpolation via printf. Notre variable temporaire $tmp est maintenant bien pratique, particulièrement depuis que nous ne voulons plus seulement l'interpoler, mais également la tester dans la condition d'un opérateur ?: pour voir si nous devions mettre le mot « coup » au pluriel. Enfin, remarquez que nous avons aligné verticalement les deux champs avec leur marqueur % correspondant dans le format de printf. Si un argument est trop long pour coïncider, vous pouvez toujours aller à la ligne suivante pour l'argument suivant bien que nous n'ayons pas eu à le faire dans ce cas.

Wouah ! Vous en avez assez ? Il existe encore beaucoup d'autres idiomes dont nous aurions pu débattre, mais ce livre est déjà suffisamment lourd. Mais nous aimerions encore parler d'un usage idiomatique de Perl, l'écriture de générateurs de programmes.

24-6. Générer des programmes

Depuis le jour, ou presque, où les gens se sont aperçus pour la première fois qu'ils pouvaient écrire des programmes, ils ont commencé à écrire des programmes qui écrivaient d'autres programmes. Nous les appelons souvent des générateurs de programmes. (Les historiens se souviennent que RPG signifiait Report Program Generator bien avant Role Playing Game.) De nos jours, on l’appellerait probablement des « usines à programmes », mais les gens des des générateurs sont arrivés les premiers, c'est donc leur nom qui a prévalu.

Quiconque a déjà écrit un générateur de programmes sait que cela peut vous donner un strabisme divergent quand bien même vous portez des verres correcteurs. Le problème vient de ce qu'une grande partie des données du programme ressemble à du code, a la couleur du code, mais n'est pas du code (du moins pas encore). Le même fichier texte contient à la fois des choses qui font quelque chose et des choses qui y ressemblent, mais qui ne font rien. Perl comporte de nombreuses fonctionnalités qui facilitent son intégration avec d'autres langages, textuellement parlant.

(Bien sûr, ces fonctionnalités facilitent l'écriture de Perl en Perl, mais c'est le moins auquel on puisse s'attendre dès à présent.)

24-6-a. Générer d'autres langages en Perl

Perl est (entre autres) un langage de traitement de texte et la plupart des langages informatiques sont textuels. De plus, l'absence de limites arbitraires et les divers mécanismes de protection et d'interpolation de Perl facilitent la séparation visuelle du code d'un autre langage que l'on génère. Par exemple, voici un petit morceau de s2p, le traducteur sed-vers-perl :

 
Sélectionnez
print &q(< <"FIN");
:      #!$bin/perl
:      eval 'exec $bin/perl -S \$0\${1+"\$@"}'
:            if \$lance_sous_un_shell;
:
FIN

Il se trouve que le texte encadré est légal en deux langages, Perl et sh. Nous avons utilisé dès le début un idiome qui préservera votre santé mentale lors de l'écriture d'un générateur de programme : l'astuce de mettre un caractère « de bruit » et une tabulation au début de chaque ligne protégée, ce qui isole visuellement le code inclus, vous pouvez ainsi dire en un clin d'œil qu'il ne s'agit pas du code qui est en train de s'exécuter. Une variable, $bin, est interpolée en deux endroits de la chaîne multiligne protégée, puis la chaîne est passée à travers une fonction qui élimine le deux-points et la tabulation.

Bien sûr, il est inutile d'utiliser des chaînes protégées multilignes. On voit souvent des scripts CGI contenant des millions d'instructions print, une par ligne. C'est un peu comme aller au supermarché en Mirage III, mais bon, tant que l'on arrive à destination... (Nous admettrons qu'une colonne d'instructions print possède ses propres propriétés pour ressortir visuellement.)

Lorsque vous encadrez une grosse chaîne protégée multiligne contenant un autre langage (comme du HTML), il est parfois utile de faire comme si vous programmiez à l'envers, en encadrant plutôt du Perl dans l'autre langage, exactement comme vous le feriez avec des langages ouvertement inversés comme PHP :

 
Sélectionnez
print <<"XML";
   <truc>
   <insense>
    bla bla bla @{[ scalar EXPR ]} bla bla bla
    bla bla bla @{[ LISTE ]} bla bla bla
   </insense>
   </truc> 
XML

Vous pouvez utiliser l'une de ces deux astuces pour interpoler des valeurs d'expressions arbitrairement complexes dans une grosse chaîne.

Certains générateurs de programmes ne ressemblent pas beaucoup à des générateurs de programmes, selon la quantité d'opérations qu'ils vous cachent. Au chapitre 22, CPAN, nous avons vu comment un petit programme Makefile.PL pouvait être utilisé pour écrire un Makefile. Makefile peut facilement être 100 fois plus gros que le Makefile.PL qui l'a généré. Pensez à l'usure que cela épargne à vos doigts. Ou n'y pensez plus — après tout, c'est le but.

24-6-b. Générer du Perl dans d'autres langages

Il est facile de générer d'autres langages en Perl, mais l'inverse est également vrai. Perl peut facilement être généré par d'autres langages parce qu'il est à la fois concis et malléable. Vous pouvez choisir votre manière de protéger les chaînes entre guillemets afin de ne pas interférer avec les mécanismes de protection des autres langages. Vous n'avez pas à vous soucier de l'indentation, ni où placer vos ruptures de lignes, ni si vous devez (encore) mettre un antislash avant vos antislashs. Vous n'avez pas besoin de définir un paquetage à l'avance par une seule chaîne de caractères, puisque vous pouvez glisser dans l'espace de noms du paquetage de façon répétée à chaque fois que vous voulez évaluer plus de code dans ce paquetage.

Une autre chose qui facilite l'écriture de Perl dans d'autres langages (y compris Perl) est la directive #line. Perl sait comment la traiter comme une directive spéciale qui reconfigure l'idée qu'il se faisait du nom de fichier courant et du numéro de ligne. Ceci peut s'avérer utile dans les messages d'erreur ou d'avertissement, en particulier pour les chaînes traitées avec eval (ce qui, lorsqu'on y pense, n'est rien d'autre que du Perl écrivant du Perl). La syntaxe de ce mécanisme est celle utilisée par le préprocesseur C : lorsque Perl rencontre un symbole # et le mot line, suivi par un numéro et un nom de fichier, il positionne __LINE__ à ce numéro et __FILE__ à ce nom de fichier.(187)

Voici quelques exemples que vous pouvez tester en tapant dans perl directement. Nous avons employé un Ctrl-D pour indiquer la fin de fichier, ce qui est typique d'Unix. Les utilisateurs de DOS/Windows et de VMS peuvent taper Ctrl-Z. Si votre shell utilise autre chose, vous devrez l'employer pour dire à perl que vous avez terminé. Sinon, vous pouvez toujours taper __END__ pour dire au compilateur qu'il n'y a plus rien à analyser.

Voici comment la fonction interne de Perl, warn, affiche le nouveau nom de fichier est le numéro de ligne :

 
Sélectionnez
% perl 
# line 2000 "Odyssée" 
# le "#" sur la ligne précédente doit être le premier caractère de la ligne 
warn "les portes du compartiment pod"; # or die 
^D
les portes du compartiment pod at Odyssée line 2001.

Ici, l'exception levée par le die à l'intérieur de l'eval retrouve ses petits dans la variable $@ ($EVAL__ERROR), accompagnée du nouveau nom de fichier temporaire et du numéro de ligne :

 
Sélectionnez
# line 1996 "Odyssée" 
eval qq { 
#line 2025 "Hal"
    die "portes du compartiment pod"; 
}; 
print "Problèmes avec $@"; 
warn "J'ai peur de ne pouvoir faire cela", 
^D 
Problème avec les portes du compartiment pod at Hal line 2025. 
J'ai peur de ne pouvoir faire cela at Odyssée line 2001.

Ceci montre comment une directive #line n'affecte que l'unité de compilation courante (le fichier ou l'evalCHAINE) et que, lorsque la compilation de cette unité est terminée, la configuration précédente est automatiquement restaurée. De cette manière vous pouvez positionner vos propres messages à l'intérieur d'un eval CHAINE ou d'un do FICHIER sans affecter le reste de votre programme.

Perl possède une option -P qui invoque le préprocesseur C, qui émet les directives #line. Le préprocesseur C était l'élan initial pour implémenter #line, mais il est rarement utilisé de nos jours, depuis qu'il existe de meilleurs moyens pour faire ce pour quoi nous nous reposions sur lui. Perl possède toutefois de nombreux autres préprocesseurs, y compris le module AutoSplit. Le préprocesseur JPL (Java Perl Lingo) change les fichiers .jpl en fichiers .java, .pl, .h et .c. Il utilise #line pour conserver la précision des messages d'erreurs.

Un des tout premiers préprocesseurs de Perl était le traducteur sed-vers-perl, s2p. En fait Larry a retardé la première version de Perl pour terminer s2p et awk-vers-perl (a2p), car il pensait qu'ils amélioraient la manière dont Perl serait perçu. Hum, peut-être que cela a été le cas.

Voir les documentations en ligne pour plus d'information à ce sujet, ainsi que sur le traducteur find2perl.

24-6-c. Filtres de code source

Si vous savez écrire un programme pour traduire des choses quelconques en Perl, alors pourquoi ne pas disposer d'un moyen pour invoquer ce traducteur depuis Perl ?

La notion de filtre de code source est née avec l'idée qu'un script ou un module devrait être capable de se décrypter lui-même à la volée, comme ceci :

 
Sélectionnez
#!/usr/bin/perl 
use MonFiltreDeDecryptage; @*x$]'0uN&k^Zx02jZ^X{.?s!(f;9Q/^A^@~~8H]|,%@^P:q-= 
...

Mais l'idée a évolué depuis et maintenant un filtre de code source peut être défini pour faire toutes les transformations que vous désirez sur un texte en entrée. Mettez ceci en rapport avec la notion d'option -x mentionnée au chapitre 19, L'interface de la ligne de commande, et vous obtiendrez un mécanisme général pour extraire d'un message n'importe quel morceau de programme et pour l'exécuter, indépendamment du fait qu'il soit écrit ou non en Perl.

En utilisant le module Filter de CPAN, on peut même maintenant faire des choses comme programmer du Perl en awk :

 
Sélectionnez
#!/usr/bin/perl 
use Filter::exec "a2p";    # le traducteur awk-vers-perl 
1,30 { print $1 }

Maintenant voilà précisément ce que vous pourriez qualifier d'idiomatique. Mais nous ne prétendons pas une seule seconde qu'il s'agit d'une technique couramment employée.

25. Portabilité

Un monde ne comportant qu'un seul système d'exploitation rendrait la portabilité facile et la vie ennuyeuse. Nous préférons un large champ génétique de systèmes d'exploitation, tant que l'écosystème ne se divise pas trop clairement en prédateurs et en proies. Perl tourne sur des douzaines de systèmes d'exploitation et puisque les programmes Perl ne sont pas dépendants de la plate-forme, le même programme peut tourner sur tous ces systèmes sans modification.

Enfin, presque. Perl essaie d'offrir au programmeur autant de fonctionnalités que possible, mais si vous utilisez des fonctionnalités spécifiques à un certain système d'exploitation, vous réduirez forcément la portabilité de votre programme sur d'autres systèmes. Dans ce paragraphe, nous donnerons quelques lignes de conduite pour écrire du code Perl portable. Une fois que vous aurez pris votre décision sur le degré de portabilité que vous souhaitez, vous saurez où tirer un trait et vous saurez quelles limites vous décidez de ne pas franchir.

Si l'on regarde sous un autre angle, l'écriture de code portable entraîne une limitation volontaire des choix disponibles. Il n'existe aucune raison pour ne pas utiliser Perl pour lier ensemble des outils Unix, ou pour prototyper une application Macintosh, ou pour gérer la base de registre de Windows. Si cela a un sens de sacrifier la portabilité, allez-y.(188)

En général, remarquez que les notions de UID, de répertoire « maison » et même l'état d'être « logué » n'existeront que sur des plates-formes multiutilisateurs.

La variable spéciale $^O vous indique sur quel système d'exploitation Perl a été compilé. Ceci permet d'accélérer le code qui autrement aurait dû faire use Config pour obtenir la même information via $Config{osname}. (Même si vous avez chargé Config pour d'autres raisons, cela vous économise une recherche dans un hachage lié.)

Pour obtenir des informations plus détaillées sur la plate-forme, vous pouvez rechercher le reste de l'information dans le hachage %Config, qui est disponible grâce au module standard Config. Par exemple, pour vérifier si la plate-forme dispose de l'appel lstat, vous pouvez tester $Config{d_lstat}. Voir la documentation en ligne de Config pour une description complète des variables accessibles et la page de manuel perlport pour une liste des comportements des fonctions internes de Perl sur différentes plates-formes. Voici les fonctions Perl dont le comportement varie le plus entre les plates-formes.

-X (tests de fichiers), accept, alarm, bind, binmode, chmod, chown, chroot, connect, crypt, dbmclose, dbmopen, dump, endgrent, endhostent, endnetent, endprotoent, endpwent, endservent, exec, fcntl, fileno, flock, fork, getgrent, getgrgid, getgrnam, gethostbyaddr, gethostbyname, gethostent, getlogin, getnetbyaddr, getnetbyname, getnetent, getpeername, getpgrp, getppid, getpriority, getprotobyname, getprotobynumber, getprotoent, getpwent, getpwnam, getpwuid, getservbyport, getservent, getservbyname, getsockname, getsockopt, glob, ioctl, kill, link, listen, lstat, msgctl, msgget, msgrcv, msgsnd, open, pipe, qx, readlink, readpipe, recv, select, semctl, semget, semop, send, sethostent, setgrent, setnetent, setpgrp, setpriority, setprotoent, setpwent, setservent, setsockopt, shmctl, shmget, shmread, shmwrite, shutdown, socket, socketpair, stat, symlink, syscall, sysopen, system, times, truncate, umask, utime, wait, waitpid

25-1. Sauts de ligne

Sur la plupart des systèmes d'exploitation, les lignes dans les fichiers sont terminées par un ou deux caractères qui signalent la fin de ligne. Les caractères varient d'un système à l'autre. Unix utilise traditionnellement \012 (c'est-à-dire le caractère octal 12 en ASCII), un type d'entrées/sorties à la DOS utilise \015\012 et les Mac utilisent \015. Perl emploie \n pour représenter un saut de ligne « logique », indépendamment de la plate-forme. Sur MacPerl, \n signifie toujours \015. Sur les Perl à la DOS, \n signifie généralement \012, mais lorsqu'on accède à un fichier en « mode texte », il est traduit vers (ou depuis) \015\012, selon que vous lisez ou que vous écrivez. Unix fait la même chose sur les terminaux en mode canonique. On se réfère généralement à \015\012 en l'appelant CRLF.

Comme DOS fait une distinction entre les fichiers texte et les fichiers binaires, les Perl à la DOS ont des restrictions sur l'utilisation de seek et tell sur un fichier en « mode texte ». Pour obtenir les meilleurs résultats possible, ne faites seek que sur des emplacements obtenus depuis tell. Si toutefois vous utilisez la fonction interne de Perl binmode sur le handle de fichier, vous pouvez généralement faire seek et tell en toute impunité.

Un malentendu fréquent dans la programmation de sockets est que \n vaudra \012 partout. Dans beaucoup de protocoles Internet habituels, \012 et \015 sont spécifiés et les valeurs de \n et \r de Perl ne sont pas fiables puisqu'elles varient d'un système à l'autre :

 
Sélectionnez
print SOCKET "Salut, le client !\015\012"; # correct 
print SOCKET "Salut le client !\r\n";      # FAUX

Toutefois, l'utilisation de \015\012 (ou \cM\cJ, ou encore \x0D\x0A, ou même v13.10) peut devenir pénible et disgracieux, et semer la confusion chez ceux qui maintiennent le code. Le module Socket fournit quelques Choses Correctes pour ceux qui en veulent :

 
Sélectionnez
use Socket qw(:DEFAULT :crlf); 
print SOCKET "Salut le client !$CRLF" # correct

Lors d'une lecture sur une socket, souvenez-vous que le séparateur d'enregistrement d'entrée par défaut $/ est \n, ce qui signifie que vous avez encore du travail à faire si vous n'êtes pas sûrs que de ce que vous verrez à travers la socket. Un code de socket robuste devrait reconnaître soit \012 ou \015\012 comme étant la fin de ligne :

 
Sélectionnez
use Socket qw(:DEFAULT :crlf); 
local ($/) = LF # pas nécessaire si $/ vaut déjà /012 

while (<SOCKET>) {
    s/$CR?$LF/\n/;   # remplace LF ou CRLF par un saut de ligne logique 
}

De même, un code renvoyant des données textuelles — comme un sous-programme qui va chercher une page web — devrait souvent traduire les sauts de ligne. Souvent, une seule ligne de code suffit :

 
Sélectionnez
$donnees =~ s/\015?\012/\n/g; 
return $donnees;

25-2. Boutisme et taille des nombres

Les ordinateurs stockent les entiers et les nombres à virgule flottante dans des ordres différents (gros-boutiste, big-endian ou petit-boutiste, little-endian) et dans des tailles différentes (32 bits et 64 bits étant les tailles les plus courantes de nos jours). Normalement, vous n'aurez pas à y penser. Mais si votre programme envoie des données binaires à travers une connexion réseau ou s'il écrit sur le disque des données destinées à être lues par un ordinateur différent, il se peut que vous deviez prendre des précautions

Des ordres rentrant en conflit peuvent semer une pagaille absolue dans les nombres. Si un hôte petit-boutiste (comme une CPU Intel) stocke 0x12345678 (305 419 896 en décimal), un hôte grand-boutiste (comme une CPU Motorola) le lira comme 0x78563412 (2 018 915 346 en décimal). Pour éviter ce problème dans les connexions réseau (sur une socket), utilisez pack et unpack avec les formats n et N, qui écrivent des nombres entiers courts et longs dans l'ordre gros-boutiste (également appelé ordre « réseau ») quelle que soit la plate-forme.

Vous pouvez explorer le boutisme de votre plate-forme en dépaquetant une structure de données empaquetée en format natif, comme ceci :

 
Sélectionnez
print unpack("h*", pack("s2", 1, 2)), "\n"; 
# '10002000' sur par ex. Intel x86 ou Alpha 21064 en mode petit-boutiste 
# '00100020' sur par ex. Motorola 68040

Pour déterminer votre boutisme, vous pourriez utiliser l'une de ces deux instructions :

 
Sélectionnez
$est_gros_boutiste = unpack("h*", pack("s", 1)) =~ /01/; 
$est_petit_boutiste = unpack("h*", pack("s", 1)) =~ /^1/;

Même si deux systèmes ont le même boutisme, il peut encore y avoir des problèmes lors du transfert de données entre des plates-formes 32-bits et 64-bits. Il n'existe pas de bonne solution autre que d'éviter de transférer ou de stocker des nombres binaires bruts. Soit vous transférez et vous stockez les nombres en texte plutôt qu'en binaire, soit vous utilisez des modules comme Data::Dumper ou Storable pour le faire à votre place. Vous avez tout intérêt à utiliser des protocoles orientés texte en toutes circonstances — ils sont plus robustes, plus maintenables et encore plus extensibles que les protocoles binaires.

Bien entendu, avec l'avènement de XML et d'Unicode, notre définition d'un protocole orienté texte devient encore plus souple. Par exemple, vous pouvez transférer, entre deux systèmes où Perl 5.6.0 (ou une version ultérieure) est installé, une séquence d'entiers encodés en tant que caractères en utf8 (la version UTF-8 de Perl). Si les deux extrémités tournent sur une architecture avec des entiers 64-bits, vous pouvez échanger des entiers 64-bits. Sinon, vous êtes limités à des entiers 32-bits. Utilisez pack avec un canevas U* pour envoyer et unpack avec un canevas U* pour recevoir.

25-3. Fichiers et systèmes de fichiers

Les composants des chemins de fichiers sont séparés par / sur Unix, \ sur Windows et : sur Mac. Certains systèmes n'implémentent ni les liens en dur (link), ni les liens symboliques (symlink, readlink, lstat). Certains systèmes attachent une importance à la casse (majuscules/minuscules) des noms de fichiers, d'autres non et d'autres encore n'y font attention que lors de la création de fichiers, mais pas lors de leur lecture.

Il existe des modules qui peuvent vous aider. Les modules standards File::Spec fournissent des fonctions qui Font La Bonne Chose :

 
Sélectionnez
use File::Spec::Functions; 
chdir( updir() );  # monter dans le répertoire supérieur 
$fichier = catfile( curdir(), 'temp', 'fichier.txt');

La dernière ligne lit dans ./temp/fichier.txt sur Unix et Windows ou :temp:fichier.txt sur Mac ou [.temp]fichier.txt sur VMS et stocke le contenu du fichier dans $fichier.

Le module File::Basename, un autre module livré avec Perl qui est également indépendant de la plate-forme, éclate un nom de chemin en ses composants : le nom de fichier de base, le chemin complet vers le répertoire et le suffixe du fichier.

Voici quelques astuces pour écrire des programmes Perl manipulant des fichiers qui soient portables :

  • N'utilisez pas deux fichiers du même nom avec des casses différentes, comme test.pl et Test.pl, puisque certaines plates-formes ignorent la casse.
  • Limitez vos noms de fichiers à la convention 8.3 (noms de huit lettres et extensions de trois lettres) partout où c'est possible. Vous pouvez souvent vous en sortir avec des noms de fichiers plus longs pour autant que vous soyez sûrs qu'ils resteront uniques lorsqu'on les aura poussés à travers un trou dans le mur de taille 8.3. (Hé, cela s'avère plus facile que de pousser un chameau à travers le chas d'une aiguille.)
  • Réduire l'emploi de caractères non alphanumériques dans les noms de fichiers. L'utilisation de soulignés est souvent correcte, mais elle gaspille un caractère qui pourrait être mieux employé pour l'unicité sur les systèmes 8.3. (Souvenez-vous que c'est la raison pour laquelle nous ne mettons habituellement pas de souligné dans les noms de modules.)
  • De même, lorsque vous utilisez le module AutoSplit, essayez de limiter vos noms de sous-programmes à huit caractères au plus et ne donnez pas le même nom avec une casse différente à deux sous-programmes. Si vous avez besoin de plus de huit caractères, arrangez-vous pour qu'il y ait unicité sur les huit premiers.
  • Utilisez toujours < explicitement pour ouvrir un fichier en lecture ; sinon, sur les systèmes qui autorisent la ponctuation dans les noms de fichiers, un fichier préfixé par un caractère > pourrait être effacé et un fichier préfixé par | pourrait engendrer une ouverture de pipe. Car la forme à deux arguments d'open est magique et elle interprétera les caractères comme >, < et |, ce qui peut être la mauvaise chose à faire. (Sauf si c'est la bonne).

     
    Sélectionnez
    open(FICHIER,  $fichier_existant) or die $!# MAUVAIS 
    open(FICHIER,   "<$fichier_existant") or die $!;  # mieux 
    open(FICHIER, "<", $fichier_existant) or die $!; # encore mieux
  • Ne présupposez pas que les fichiers texte finiront par un saut de ligne. Ils le devraient, mais les gens oublient quelquefois, particulièrement lorsque leur éditeur de texte les y aide.

25-4. Interactions avec le système

Les plates-formes reposant sur une interface graphique sont parfois démunies de toute espèce de ligne de commande, ainsi les programmes exigeant une interface en ligne de commande pourraient ne pas fonctionner partout. Vous n'y pouvez pas grand-chose, à part mettre à jour.

Quelques autres astuces :

  • Certaines plates-formes ne peuvent pas supprimer ou renommer des fichiers en train d'être utilisés, souvenez-vous donc de fermer avec close les fichiers lorsque vous en avez fini avec eux. Ne faites pas unlink ou rename sur un fichier ouvert. Ne faites pas tie ou open sur un fichier déjà lié avec tie ou déjà ouvert ; faites d'abord untie ou close sur ce fichier.
  • N'ouvrez pas le même fichier plusieurs fois simultanément, car certains systèmes posent d'office des verrous sur les fichiers ouverts.
  • Ne dépendez pas d'une variable d'environnement spécifique existant dans %ENV et ne présupposez pas que quoi que ce soit dans %ENV soit insensible à la casse ou préserve la casse. Ne présumez pas de la sémantique de l'héritage d'Unix pour les variables d'environnement ; sur certains systèmes, elles peuvent être visibles par tous les processus.
  • N'utilisez pas les signaux, ni %SIG.
  • Tâchez d'éviter les globs de noms de fichiers. Utilisez opendir, readdir et closedir à la place. (Depuis la version 5.6.0 de Perl, le glob basique de noms de fichiers est bien plus portable qu'il ne l'était, mais certains systèmes peuvent encore se frotter aux « Unixismes » de l'interface par défaut si vous essayez d'être original.)
  • Ne présumez pas des valeurs spécifiques pour les numéros d'erreur ou les chaînes stockées dans $!.

25-5. Communication interprocessus (IPC)

Pour maximiser la portabilité, n'essayez pas de lancer de nouveaux processus. Cela signifie que vous devriez éviter system, exec, fork, pipe, ``, qx// ou open avec un |.

Le problème principal ne réside pas dans les opérateurs eux-mêmes : les commandes lançant des processus externes sont généralement implémentées sur la plupart des plates-formes (bien que certaines n'implémentent aucun type de fork). Les problèmes sont davantage susceptibles de se produire lorsque vous invoquez des programmes externes qui ont des noms, des emplacements, des sorties ou des sémantiques pour les arguments qui varient entre les plates-formes.

Un morceau de code Perl particulièrement populaire est l'ouverture d'un pipe vers sendmail pour que votre programme puisse envoyer un mail :

 
Sélectionnez
open(MAIL, '|/usr/lib/sendmail -t') or die "fork sendmail impossible: $!";

Ceci ne marchera pas sur les plates-formes sans sendmail. Pour une solution portable, utilisez l'un des modules de CPAN pour envoyer votre mail, comme Mail::Mailer et Mail::Send dans la distribution MailTools ou Mail::Sendmail.

Les fonctions des IPC System V d'Unix (msg*(), sem*(), shm*()) ne sont pas toujours disponibles, même sur certaines plates-formes Unix.

25-6. Sous-programmes externes (XS)

Il est généralement possible d'élaborer un code XS pour qu'il fonctionne avec n'importe quelle plate-forme, mais les bibliothèques et les fichiers d'en-tête pourraient ne pas être disponibles partout. Si les bibliothèques et les fichiers d'en-tête sont portables, alors il y a une chance raisonnable pour que le code XS le soit également.

Un problème de portabilité différent se produit lors de l'écriture de code XS : la disponibilité d'un compilateur C sur la plate-forme de l'utilisateur final. C apporte avec lui ses propres problèmes de portabilité et l'écriture de code XS vous exposera à certains d'entre eux. L'écriture en Perl pur est un moyen facile d'atteindre la portabilité, car le processus de configuration de Perl traverse des supplices extrêmes pour vous cacher les tares de portabilité du C.(189)

25-7. Modules standard

En général, les modules standard (ceux qui sont livrés avec Perl) fonctionnent sur toutes les plates-formes. Les exceptions notables sont le module CPAN.pm (qui réalise en fait des connexions vers des programmes externes qui peuvent ne pas être disponibles), les modules spécifiques à une plate-forme (comme ExtUtils::MM_VMS) et les modules DBM.

Il n'existe pas un seul module DBM qui soit disponible sur toutes les plates-formes.

SDBM_File et les autres sont généralement disponibles sur tous les portages Unix et à la DOS, mais pas dans MacPerl, où seuls NBDM_File et DB_File sont disponibles.

La bonne nouvelle est qu'au moins un module DBM devrait être disponible et que AnyDBM_File utilisera n'importe quel module qu'il sera capable de trouver. Avec une telle incertitude, vous ne devriez utiliser que les fonctionnalités communes à toutes les implémentations DBM. Par exemple, limitez vos enregistrements à 1 K octet, pas plus. Voir la documentation du module AnyDBM_File pour plus de détails.

25-8. Dates et heures

Là où c'est possible, utilisez le standard ISO-8601 (« AAAA-MM-JJ ») pour représenter les dates. Les chaînes comme « 1971-06-21 » peuvent facilement être converties vers une valeur spécifique au système avec un module comme Date::Parse. Une liste de valeurs de date et d'heure (comme celle renvoyée par la fonction interne localtime) peut être convertie vers une représentation spécifique au système en utilisant Time::Local.

La fonction interne time renverra toujours le nombre de secondes depuis le commencement de l'« epoch » (l'origine, voir la fonction time au chapitre 29), mais les systèmes d'exploitation ont des opinions divergentes concernant la date à laquelle cela s'est produit. Sur la plupart des systèmes, l'origine est fixée au 1er janvier 1970 à 00:00:00 UTC, mais elle a commencé 66 ans plus tôt sur Mac et sur VMS elle a démarré le 17 novembre 1858 à 00:00:00. Ainsi, pour obtenir des dates portables, vous voudrez peut-être calculer un décalage pour l'origine :

 
Sélectionnez
require Time::Local; 
$decalage = Time::Local::timegm(0, 0, 0, 1, 0, 70);

La valeur de $decalage sur Unix et Windows vaudra toujours, 0 mais sur Macs et VMS, elle pourra valoir un nombre plus grand. Il est alors possible d'ajouter $decalage à une valeur de date et d'heure Unix pour obtenir ce qui devrait être la même valeur sur tous les systèmes.

La représentation par le système de l'heure du jour et de la date calendaire peut être contrôlée par des moyens largement différents. Ne présupposez pas que le fuseau horaire soit stocké dans $ENV{TZ}. Et même s'il l'est, ne présupposez pas que vous pouvez le contrôler via cette variable.

25-9. Internationalisation

Utilisez de l'Unicode dans vos programmes. Faites toutes les conversions depuis et vers d'autres jeux de caractères dans vos interfaces vers le monde extérieur. Voir le chapitre 15, Unicode.

En dehors du monde d'Unicode, vous devriez présumez de peu en ce qui concerne les jeux de caractères et de rien du tout à propos des valeurs de ord pour les caractères. Ne présupposez pas que les caractères alphabétiques aient des valeurs de ord séquentielles. Les lettres minuscules peuvent venir avant ou après les majuscules ; les majuscules et les minuscules peuvent être mêlées de manière à ce qu'à la fois a et A viennent avant b ; les caractères accentués et les autres caractères internationaux peuvent être mêlés pour que ä vienne avant b.

Si votre programme opère sur un système POSIX (une hypothèse plutôt large), consultez la page de manuel de perllocale pour plus d'informations sur les locales de POSIX. Les locales affectent les jeux de caractères et les encodages ainsi que la date et l'heure, entre autres choses. Une utilisation correcte des locales rendra votre programme un peu plus portable ou au moins, plus pratique et nativement amical pour les utilisateurs non anglo-saxons. Mais soyez conscients que les locales et Unicode ne se mélangent pas encore très bien.

25-10. Style

Lorsqu'il est nécessaire d'avoir du code spécifique à la plate-forme, pensez à le garder à un endroit qui facilite le portage vers d'autres plates-formes. Utiliser le module Config et la variable spéciale $^O pour marquer les différences entre les plates-formes.

Faites attention aux tests que vous fournissez avec votre module ou vos programmes. Le code d'un module peut être totalement portable, mais ses tests peuvent ne pas l'être tout à fait. Ceci arrive souvent lorsque les tests engendrent d'autres processus ou appellent des programmes externes pour faciliter les contrôles ou lorsque (comme on l'a remarqué ci-dessus) les tests présument de certaines choses concernant le système de fichiers et les chemins. Prenez garde à ne pas dépendre d'un style d'affichage spécifique pour les erreurs, même en vérifiant $! pour les erreurs « standards » après un appel système. Utilisez plutôt le module Errno.

Souvenez-vous qu'un bon style transcende à la fois le temps et les cultures, ainsi, pour une portabilité maximale, vous devez chercher à comprendre ce qui est universel parmi les exigences de votre existence. Les personnes les plus équilibrées ne sont pas prisonnières et n'ont pas à l'être, car elles ne se soucient pas d'être à la mode eu égard à leur propre culture, de programmation ou autre. La mode est variable, mais le style est constant.

26. POD

Un des principes sous-jacents dans la conception de Perl est que les choses simples devraient rester simples et que les choses difficiles devraient être possibles. La documentation devrait rester simple.

Perl implémente un format de balises simple appelé pod qui peut avoir son existence propre ou être librement entremêlé avec votre code source pour créer une documentation insérée. Pod peut être converti vers différents formats pour l'impression ou la consultation ou vous pouvez simplement le lire directement, car il est à plat (plain en anglais).

Pod n'est pas aussi expressif que des langages comme XML, LATEX, troff(1) ou même HTML. C'est une volonté de notre part : nous avons sacrifié l'expressivité au profit de la simplicité et de la commodité. Certains langages à balises font écrire aux auteurs plus de balises que de texte, ce qui rend l'écriture plus difficile qu'elle ne l'était et la lecture proche de l'impossible. Un bon format, comme une bonne musique de film, reste en arrière-plan sans distraire le spectateur.

Amener les programmeurs à écrire de la documentation est presque aussi difficile que de les amener à porter des cravates. Pod a été conçu pour être si simple à écrire que même un programmeur puisse le faire — et il devrait le faire. Nous ne prétendons pas que pod est suffisant pour écrire un livre, bien qu'il fût suffisant pour écrire celui-ci.

26-1. Pod en quelques mots

La plupart des formats de document exigent que le document entier soit dans ce format. Pod est plus indulgent : vous pouvez insérer du pod dans tout type de fichier, en vous reposant sur les traducteurs de pod pour l'extraire. Certains fichiers sont entièrement constitués de pod pur à 100 %. Mais d'autres, notamment les programmes Perl et les modules, peuvent contenir une belle quantité de pod dispersée de-ci de-là au bon vouloir de l'auteur. Il suffit à Perl de sauter le texte en pod lorsqu'il analyse le fichier en vue de l'exécuter.

L'analyseur lexical de Perl sait où commencer à sauter le pod lorsqu'à un certain endroit où il trouverait d'ordinaire une instruction, il rencontre à la place une ligne commençant par un signe égal et un identificateur, comme ceci :

 
Sélectionnez
=head1 Ici se trouvent les pods !

Ce texte, ainsi que tout ce qui se trouve jusqu'à et y compris une ligne commençant par =cut, sera ignoré. Cela vous permet d'entremêler votre code source et votre documentation en toute liberté, comme dans :

 
Sélectionnez
=item guinde

La fonction guinde() se comportera de la façon la plus spectaculairement 
coincée que vous puissiez imaginer, avec aussi peu de naturel qu'une 
pyrotechnie cybernétique.

=cut

sub guinde() {
    my $arg = shift; ... 
}

=item guincher

La fonction guincher() permet la génération autodidact d'épistémologie.
 
=cut

sub guincher {
    print "La génération d'épistémologie n'est pas implémentée sur
           cette plate-forme.\n";
}

Pour d'autres exemples, regardez dans n'importe quel module standard ou venant de CPAN. Ils sont tous supposés venir avec du pod, et pratiquement tous le font, excepté ceux qui ne le font pas.

Puisque le pod est reconnu par l'analyseur lexical de Perl et rejeté, vous pouvez également utiliser une directive pod appropriée pour inhiber rapidement une section de code arbitrairement longue. Utilisez un bloc pod =for pour inhiber un paragraphe ou une paire =begin/=end pour une section plus grosse. Nous détaillerons la syntaxe de ces directives pod plus tard. Souvenez-vous cependant que dans les deux cas, vous êtes toujours dans le mode pod après ceci, vous avez donc besoin de faire un =cut pour revenir au compilateur.

 
Sélectionnez
print "reçu 1\n";

=for comment

Ce paragraphe seul est ignoré par tout le monde sauf le mythique traducteur « comment » (N.d.t commentaire). Lorsque c'est fait, vous êtes toujours en mode pod et non en mode programme. 
print "reçu 2\n"; 

=cut 

# OK, à nouveau dans le programme réel 
print "reçu 3\n"; 

=begin commentaire 

print "reçu 4\n"; 
tout ce qui se trouve ici 
sera ignoré 
par tout le monde 
print "reçu 5\n"; 

=end commentaire 

=cut 

print "reçu 6\n";

Ceci affichera qu'on a reçu 1, 3 et 6. Souvenez-vous que ces directives pod ne peuvent pas aller partout. Vous devez les mettre seulement là où l'analyseur s'attend à trouver une nouvelle instruction, pas juste au milieu d'une expression, ni à d'autres endroits arbitraires.

Du point de vue de Perl, tout ce qui est balisé par pod est rejeté, mais du point de vue des traducteurs de pod, c'est le code qui est rejeté. Les traducteurs de pod voient le texte qui reste comme une séquence de paragraphes séparés par des lignes blanches. Tous les traducteurs de pods modernes analysent le pod de la même manière, en utilisant le module standard Pod::Parser. Ils ne se différencient que par leur sortie, puisque chaque traducteur se spécialise dans un format de sortie.

Il existe trois sortes de paragraphes : les paragraphes « tels quels » (verbatim), les paragraphes de commandes et les paragraphes de prose.

26-1-a. Paragraphes « tels quels »

Les paragraphes « tels quels » sont utilisés pour du texte littéral que vous voulez voir apparaître tel qu'il est, comme des extraits de code. Un paragraphe « tel quel » doit être indenté ; c'est-à-dire qu'il doit commencer par un caractère espace ou de tabulation. Le traducteur devrait le reproduire exactement, généralement avec une fonte de largeur fixe, et en alignant les tabulations sur des colonnes de huit caractères. Il n'y a pas de formatage spécial des séquences d'échappement, vous ne pouvez donc pas jouer sur les fontes pour mettre en italique ou en relief. Un caractère < signifie un < littéral et rien d'autre.

26-1-b. Directives pod

Toutes les directives pod commence par = suivi d'un identificateur. Ce dernier peut être suivi par autant de texte arbitraire que la directive peut utiliser de quelque manière qui lui plaise. La seule exigence syntaxique est que le texte doit être tout entier dans un paragraphe. Les directives actuellement reconnues (appelées parfois commandes pod) sont :

=head1

=head2

...

  • Les directives =head1, =head2,... produisent des en-têtes du niveau spécifié. Le reste du texte dans le paragraphe est traité comme étant la description de l'en-tête. Elles sont identiques aux en-têtes de section et de sous-section .SH et .SS dans man(7) ou aux balises <h1>...</h1> et <h2>...</h2> en HTML. En fait, ces traducteurs convertissent ces directives exactement comme cela.

=cut

  • La directive =cut indique la fin du mode pod. (Il peut y avoir encore du pod plus loin dans le document, mais, si c'est le cas, il sera introduit par une autre directive pod.)

=pod

  • La directive =pod ne fait rien d'autre que dire au compilateur d'arrêter d'analyser le code jusqu'au prochain =cut. Elle est utile pour ajouter un autre paragraphe au document si vous mélangez beaucoup du code et du pod.

=over NOMBRE

=item SYMBOLE

=back

  • La directive =over commence un paragraphe spécifiquement pour la génération d'une liste utilisant la directive =item. À la fin de votre liste, utilisez =back pour la terminer. Le NOMBRE, s'il est fourni, indique au formateur de combien d'espaces indenter. Certains formateurs ne sont pas assez riches pour respecter l'indication, alors que d'autres le sont trop pour la respecter, tant il est difficile, lorsqu'on travaille avec des fontes proportionnelles, d'aligner le texte simplement en comptant les espaces. (Toutefois, on présume en général qu'avec quatre espaces il y a assez de place pour une puce ou un numéro.)
    Le véritable type de la liste est indiqué par le SYMBOLE sur chaque élément. Voici une liste à puces :
 
Sélectionnez
=over 4

=item * 

Armure de mithril 

=item * 

Manteau elfique 

=back

Une liste numérotée :

 
Sélectionnez
=over 4 

=item 1. 

Premièrement, dites "ami". 

=item 2. 

Deuxièmement, entrez dans la Moria. 

=back

Une liste de définitions :

 
Sélectionnez
=over 4 

=item armure() 

Description de la fonction armure() 

=item chant() 

Description de la fonction chant()

Vous pouvez imbriquer des listes de types identiques ou différents, mais il faut appliquer certaines règles de base : n'utilisez pas =item en dehors d'un bloc =over/=back ; utilisez au moins un =item à l'intérieur d'un bloc =over/=back ; et peut-être le plus important, conservez une cohérence dans le type des items dans une liste donnée. Utilisez soit =item * avec chaque élément pour produire une liste à puces, soit =item 1., =item 2., et ainsi de suite pour produire une liste numérotée, soit utilisez =item truc, =item machin, et ainsi de suite pour produire une liste de définitions. Si vous commencez avec des puces ou des numéros, gardez-les, puisque les formateurs sont autorisés à utiliser le type du premier =item pour décider de comment formater la liste.

Comme pour tout ce qui concerne pod, le résultat n'est pas meilleur que le traducteur. Certains traducteurs accordent de l'attention aux nombres particuliers (ou aux lettres, ou aux chiffres romains) qui suivent l'=item et d'autres non. Le traducteur pod2html actuel, par exemple, est quelque peu cavalier : il vide entièrement la séquence d'indicateurs sans les regarder pour en déduire quelle séquence vous utilisez, puis il enveloppe la liste complète à l'intérieur de balises <ol> et </ol> pour que le navigateur puisse afficher une liste ordonnée en HTML. Ce ne doit pas être interprété comme une fonctionnalité, cela pourra éventuellement être corrigé.

=for TRADUCTEUR

=begin TRADUCTEUR

=end TRADUCTEUR

  • =for, =begin et =end vous permettent d'inclure des paragraphes spéciaux à passer inchangés aux formateurs, mais seulement à des formateurs particuliers. Les formateurs reconnaissant leur propre nom ou des alias pour leur nom, dans TRADUCTEUR font attention à ces directives ; tous les autres les ignorent complètement. La directive =for spécifie que le reste de ce paragraphe n'est prévu que pour ( for, en anglais) un traducteur particulier.
 
Sélectionnez
=for html 
<p> Voici un paragraphe en <small>HTML</small> <flash>brut</flash> </p>

Les directives appariées =begin et =end fonctionnent de manière identique à, =for mais, au lieu de n'accepter qu'un seul paragraphe, elles traitent tout le texte com

pris entre le =begin et le =end correspondant comme étant destiné à un traducteur particulier. Quelques exemples :

 
Sélectionnez
=begin html 

<br>Figure 1.<img src="figure.png"><br> 

=end html 

=begin text 

------------------
|   truc          |
|          machin |
------------------ 
^^^^ Figure 1. ^^^^ 

=end text
  • Les valeurs de TRADUCTEUR couramment acceptées par les formateurs comprennent roff, man, troff, nroff, tbl, eqn, latex, tex, html et text. Certains formateurs accepteront certains d'entre eux comme synonymes. Aucun traducteur n'accepte comment — il s'agit juste du mot habituel pour quelque chose devant être ignoré par tout le monde. N'importe quel mot non reconnu pourrait jouer le même rôle. En écrivant ce livre, nous avons souvent laissé des remarques pour nous-même sous la directive =for later (pour plus tard).
    Remarquez que =begin et =end s'emboîtent, mais seulement dans le sens où la paire la plus à l'extérieur fait que tout ce qui se trouve au milieu est traité comme n'étant pas du pod, même s'il y a là-dedans d'autres directives =mot. C'est-à-dire que dès qu'un traducteur voit =begin truc, soit il ignorera, soit il traitera tout jusqu'au =end truc correspondant.
26-1-c. Séquences pod

Le troisième type de paragraphe est simplement du texte au kilomètre. C'est-à-dire que si un paragraphe ne commence ni par un espace ni par un signe égal, il est pris comme un paragraphe à plat : du texte ordinaire tapé avec aussi peu de décorations que possible. Les sauts de ligne sont traités comme des espaces. C'est en grande partie au traducteur de le rendre agréable, car les programmeurs ont des choses plus importantes à faire. On présume que les traducteurs appliqueront certaines heuristiques courantes — voir la section Traducteurs et modules pod plus loin dans ce chapitre.

Vous pouvez toutefois faire certaines choses explicitement. À l'intérieur des paragraphes ordinaires ou des directives d'en-têtes ou d'items (mais pas dans les paragraphes « tels quels »), vous pouvez utiliser des séquences spéciales pour ajuster le formatage. Ces séquences commencent toujours par une seule lettre majuscule suivie par un signe inférieur et elles se prolongent jusqu'au signe supérieur correspondant (qui n'est pas forcément le suivant). Les séquences peuvent contenir d'autres séquences.

Voici les séquences définies par pod :

I<texte>

  • Texte en italique, utilisé pour mettre en relief, pour les titres de livres, les noms de bateaux et les références de pages de manuel comme « perlpod(1) ».

B<texte>

  • Texte en gras, utilisé presque exclusivement pour les options de la ligne de commande et parfois pour les noms de programmes.

C<texte>

  • Code littéral, probablement dans une fonte de largeur fixe, comme du Courrier. Ce n'est pas nécessaire pour les items dont le traducteur devrait être capable d'en déduire qu'il s'agit de code, mais vous devriez mettre cette séquence de toute façon.

S<texte>

  • Texte avec des espaces insécables. Entoure souvent d'autres séquences.

L<nom>

  • Une référence croisée (un lien) vers un nom :
  • L<nom>

    • Page de manuel.
  • L<nom/ident>

    • Article dans une page de manuel.
  • L<nom /"sec">

    • Section dans une autre page de manuel.
  • L<"sec">

    • Section dans cette page de manuel (les guillemets sont optionnels).
  • L</"sec">

    • Idem.
  • Les cinq séquences suivantes sont les mêmes que celles ci-dessus, mais la sortie sera seulement texte, en cachant l'information sur le lien, comme en HTML :
 
Sélectionnez
L<texte |nom> 
L<texte |nom/ident> 
L<texte |nom/"sec"> 
L<texte |"sec"> 
L<texte |/"sec">

Le texte ne peut pas contenir les caractères / et | et il ne devrait contenir < ou > que s'il s'agit d'une paire.

F<chemin>

  • Utilisé pour les noms de fichiers. Ceci donne traditionnellement la même présentation que I.

X<entrée>

  • Une entrée d'index quelconque. Comme toujours, c'est au traducteur de décider quoi faire. La spécification de pod n'impose rien à ce sujet.

E<seq_échappement>

  • Un caractère nommé, comme les séquences d'échappement en HTML :
  • E<lt>

    • Un < littéral (optionnel, sauf à l'intérieur d'autres séquences et lorsqu'il est précédé d'une lettre en majuscule).
  • E<gt>

    • Un > littéral (optionnel, sauf à l'intérieur d'autres séquences).
  • E<sol>

    • Un / littéral (nécessaire seulement dans L<>).
  • E<verbar>

    • Un | littéral (nécessaire seulement dans L<>).
  • E<NNN>

    • Le caractère numéro NNN, probablement en ISO-8859-1, mais peut-être en unicode. Cela ne devrait rien changer, dans l'abstrait...
  • E<entité>

    • Une entité HTML non numérique, comme E<Agrave>.

Z<>

  • Un caractère de taille nulle. C'est sympathique pour préfixer des séquences qui pourraient rendre quelque chose confus. Par exemple, si vous aviez une ligne en prose ordinaire que devait commencé par un signe égal, vous devriez l'écrire ainsi :
 
Sélectionnez
Z<>=vous voyez ?

ou pour quelque chose contenant un « From », pour que le logiciel de mail ne mette pas un > devant :

 
Sélectionnez
Z<>From veut dire "de la part de" en anglais...

La plupart du temps, vous n'aurez besoin que d'un seul ensemble de signes supérieur/inférieur pour délimiter l'une de ces séquences pod. Cependant, vous voudrez parfois mettre un < ou un > à l'intérieur d'une séquence. (C'est particulièrement courant lorsqu'on utilise une séquence C<> pour donner une fonte de largeur fixe à un extrait de code.) Comme toujours en Perl, il y a plus d'une manière de faire. Une méthode est de simplement représenter les signes inférieur/supérieur avec une séquence E :

 
Sélectionnez
C<$a E<lt>=E<gt> $b>

Ce qui donne « $a <=> $b ». Une méthode plus lisible et peut-être plus « à plat », est d'utiliser un autre ensem

ble de délimiteurs qui ne nécessite pas que les signes inférieur/supérieur soient protégés. Des doubles chevrons (C<< truc >>) peuvent être employés, pourvu qu'il y ait un espace suivant immédiatement le délimiteur ouvrant et précédent immédiatement le délimiteur fermant. Par exemple, ce qui suit fonctionnera :

 
Sélectionnez
C<< $a <=> $b >>

Vous pouvez utiliser autant de chevrons que vous désirez pour autant que vous en avez le même nombre des deux côtés et que vous vous êtes assuré qu'un espace suive immédiatement le dernier < du côté gauche et qu'un espace précède immédiatement le premier > du côté droit. Ainsi, ce qui suit fonctionnera également :

 
Sélectionnez
C<<< $a <=> $b >>> 
C<<<< $a <=> $b >>>>

Tous ces exemples finissent par donner $a <=> $b dans une fonte de largeur fixe. L'espace supplémentaire à l'intérieur de chaque extrémité disparaît, vous devriez donc laisser un espace à l'extérieur si vous en voulez un. De même, les deux caractères espaces supplémentaires à l'intérieur ne se chevauchent pas, donc, si la première chose citée est >>, ce n'est pas pris en tant que délimiteur fermant :

L'opérateur C<< >> >> de décalage vers la droite. Ce qui donne « L'opérateur >> de décalage vers la droite ». Remarquez que les séquences pod s'imbriquent bien. Cela signifie que vous pouvez

écrire « La I<Santa MarE<iacute>a> a pris le large depuis longtemps » pour produire « La Santa María a pris le large depuis longtemps » ou « B<touch> S<B<t> I<time> > I<fichier> » pour produire « touch -t time fichier », et vous attendre à ce que ça marche correctement.

26-2. Traducteurs et modules pod

Perl est livré avec plusieurs traducteurs de pod qui convertissent les documents en pod (ou le pod inclus dans d'autres types de documents) vers divers formats. Tous devraient travailler purement sur 8 bits.

pod2text

  • Convertit du pod en texte. Normalement, ce texte sera en ASCII sur 7 bits, mais il sera sur 8 bits s'il a reçu une entrée sur 8 bits, ou spécifiquement de l'ISO-8859-1 (ou de l'Unicode) si vous utilisez des séquences telles que LE<uacute>thien pour Lúthien ou EE<auml>rendil pour Eärendil.
    Si vous avez un fichier contenant du pod, la manière la plus facile (mais peut-être pas la plus jolie) de ne visualiser que le pod formaté serait :
 
Sélectionnez
% pod2text Fichier.pm | more
  • Mais, encore une fois, le pod est supposé être lisible par un humain sans formatage.

pod2man

  • Convertit du pod vers le format des pages de manuel d'Unix, qui convient pour être visualisé via nroff (1) ou pour créer des copies composées via troff (1). Par exemple :
 
Sélectionnez
% pod2man Fichier.pm | nroff -man | more
  • ou
 
Sélectionnez
% pod2man Fichier.pm | troff -man -Tps -t > page_temp.ps % ghostview page_temp.ps
  • et pour imprimer :
 
Sélectionnez
% lpr -Ppostscript page_temp.ps

pod2html

  • Convertit le pod en HTML pour aller avec votre navigateur préféré :
 
Sélectionnez
% pod2html Fichier.pm > page_temp.html % lynx page_temp.html % netscape -remote "openURL(file:`pwd`/page_temp.html)"
  • Le dernier exemple est une astuce de Netscape qui ne fonctionne que si vous avez déjà un navigateur de lancé quelque part pour dire à cette incarnation de charger la page. Sinon, appelez-le exactement comme vous l'avez fait pour lynx.

pod2latex

  • Convertit du pod en LATEX.

Des traducteurs supplémentaires pour d'autres formats sont disponibles sur CPAN. Les traducteurs font preuve de comportements par défaut différents selon le format de

sortie. Par exemple, si votre pod contient un paragraphe en prose disant :

 
Sélectionnez
Il s'agit ici d'une $variable

alors pod2html changera ça en :

 
Sélectionnez
Il s'agit ici d'une <STRONG>$variable</STRONG>

alors que pod2text le laissera sans décoration, puisque le dollar devrait suffire pour que cela soit lisible.

Vous devriez écrire votre pod en restant le plus proche possible du texte « à plat », en vous passant le plus possible de balises explicites. C'est à chaque traducteur de décider comment créer des signes de protection appariés, comment remplir et ajuster le texte, comment trouver une fonte plus petite pour les mots entièrement en majuscules, etc. Puisqu'ils ont été écrits pour traiter la documentation en Perl, la plupart des traducteurs(190) devraient également reconnaître des éléments sans décoration comme ceux-ci et les présenter de manière appropriée :

  • HANDLE_FICHIER
  • $scalaire
  • tableau
  • fonction()
  • page_man(3r)
  • quelquun@quelquepart.com
  • http://truc.com/

Perl apporte également plusieurs modules standard pour analyser et convertir du pod, comprenant Pod::Checker (et l'utilitaire associé podchecker) pour vérifier la syntaxe des documents pod, Pod::Find pour trouver des documents pod dans les arborescences de répertoires et Pod::Parser pour créer vos propres utilitaires pod.

Remarquez que les traducteurs de pod devraient seulement regarder les paragraphes commençant par une directive pod (cela facilite l'analyse), puisqu'en fait le compilateur sait rechercher les séquences d'échappement pod même au milieu d'un paragraphe. Cela signifie que le secret suivant sera ignoré à la fois par le compilateur et par les traducteurs.

 
Sélectionnez
$a=3; 
=secret 
warn "Ni du POD, ni du CODE !?" 
=cut back 
print "reçu $a\n";

Vous ne devriez probablement pas vous reposer sur le fait que le warn restera à jamais sorti du pod. Les traducteurs de pod ne sont pas tous sages à cet égard et le compilateur pourrait devenir moins sélectif un jour ou l'autre.

26-3. Écrire vos propres outils pod

Pod a été conçu tout d'abord et surtout pour être facile à écrire. Un corollaire de la simplicité de pod est qu'elle permet d'écrire soi-même des outils simples pour traiter du pod. Si vous recherchez des directives pod, vous n'avez qu'à positionner le séparateur d'enregistrements en entrée en mode paragraphe (peut-être avec l'option -00) et n'accorder de l'attention qu'à ce qui ressemble à du pod.

Voici par exemple un simple programme afficheur de plan en pod :

 
Sélectionnez
#!/usr/bin/perl -l00n 
# planpod -affiche le plan en pod 
next unless /^=head/; 
s/^=head(\d)\s+/ " x ($1 * 4 -4)/e; 
print $_, "\n";

Si vous le lancez sur le présent chapitre de ce livre, vous obtiendrez quelque chose comme cela :

Plain Old Documentation

 
Sélectionnez
    Pod en quelques mots
        Paragraphes « tels quels »
        Directives pod
        Séquences pod
    Traducteurs et modules pod
    Écrire vos propres outils pod
    Pièges du pod
    Documenter vos programmes perl

Cet afficheur de plan en pod n'a pas vraiment fait attention au fait que le bloc pod était valide ou non. Puisque ce qui est en pod et ce qui ne l'est pas peuvent s'entremêler dans le même fichier, cela n'a pas toujours de sens de lancer des outils génériques pour rechercher et analyser le fichier complet. Mais ce n'est pas un problème, étant donné qu'il est si facile d'écrire des outils pour le pod. Voici un outil qui est attentif aux différences entre le pod et ce qui n'est pas du pod, puis n'affiche que le pod :

 
Sélectionnez
#!/usr/bin/perl -00 
# catpod -n'affiche que ce qui est en pod 
while (<>) {
    if (!$enpod) { $enpod = /^=/}
    if ($enpod)  { $enpod = !/=cut/; print; } 
} continue {
    if (eof)     { close ARGV; $enpod = ''; } 
}

Vous pourriez utiliser ce programme sur un autre programme ou un module en Perl, puis mettre un pipe sur la sortie vers un autre outil. Par exemple, si vous possédez le programme wc(1)(191) pour compter le nombre de lignes, de mots et de caractères, vous pourriez l'approvisionner avec la sortie de catpod pour qu'il ne considère dans son comptage que le pod :

 
Sélectionnez
% catpod MonModule.pm | wc

Il existe beaucoup d'endroits où le pod vous permet d'écrire des outils rudimentaires de façon simplissime en utilisant directement du Perl brut. Maintenant que vous avez catpod à utiliser comme composant, voici un autre outil pour ne montrer que le code indenté :

 
Sélectionnez
#!/usr/bin/perl -n00 
# podlit -affiche les blocs littéraux indentés 
# à partir de pod en entrée 
print if /^\s/;

Que pourriez-vous faire avec ça ? Eh bien, vous pourriez premièrement vouloir vérifier le code dans le document avec perl -wc. Ou peut-être voudriez-vous une fonctionnalité de grep(1)(192) qui ne regarde que les exemples de code :

 
Sélectionnez
% catpod MonModule.pm | podlit | grep nom_fonc

Cette philosophie, selon laquelle les outils et les filtres sont des parties interchangeables (et testables unitairement), est une approche tout bonnement simple et puissante pour concevoir des composants logiciels réutilisables. C'est une forme de paresse de ne construire qu'une solution minimale pour accomplir le travail aujourd'hui — du moins, pour certains types de travaux.

Cependant, pour d'autres tâches, ceci peut même s'avérer contre-productif. Parfois, il y a plus de travail à écrire un outil à partir de rien et parfois moins. Pour ceux que nous vous avons montrés précédemment, les prouesses naturelles de Perl en ce qui concerne le traitement du texte sont tout indiquées pour appliquer « la force brute ». Mais tout ne fonctionne pas de cette façon. En jouant avec le pod, vous pourriez remarquer que bien que ses directives soient simples à analyser, ces séquences peuvent s'avérer plus risquées. Bien que des traducteurs, hum, pas très corrects, ne s'adaptent pas à ceci, les séquences peuvent s'imbriquer dans d'autres séquences et peuvent avoir des délimiteurs de taille variable.

Au lieu de vous mettre à coder vous-même ce code d'analyse, les paresseux chercheront une autre solution. Le module standard Pod::Parser remplit ce contrat. Il est tout particulièrement utile pour les tâches difficiles, comme celles qui nécessitent une réelle analyse des morceaux internes des paragraphes, une conversion vers d'autres formats de sortie, et ainsi de suite. Il est plus facile d'utiliser le module pour les cas compliqués, car la quantité de code à laquelle vous aboutirez est plus petite. C'est également mieux, car l'analyse épineuse est déjà réalisée pour vous. Il s'agit tout à fait du même principe que l'emploi de catpod dans une chaîne de pipes.

Le module Pod::Parser adopte une approche intéressante dans son travail. C'est un module orienté objet d'une facture différente de la plupart de ceux que vous avez vus dans ce livre. Son objectif premier n'est pas tant de fournir des objets à manipuler directement que d'apporter une classe de base à partir de laquelle d'autres classes peuvent être construites.

Vous créez votre propre classe et héritez de Pod::Parser. Puis vous déclarez des sous-programmes servant de méthodes de rappel (callbacks) que l'analyseur de votre classe parente invoquera. Il s'agit d'une façon de programmer différente des programmes procéduraux donnés précédemment. Dans un sens, il s'agit plus d'un style de programmation déclaratif, car, pour accomplir le travail, vous enregistrez simplement des fonctions et laissez d'autres entités les invoquer pour vous. La logique ennuyeuse du programme est gérée ailleurs. Vous ne faites que donner des morceaux « plug-and-play ».

Voici une réécriture du programme original catpod donné auparavant, mais, cette fois-ci,

elle utilise le module Pod::Parser pour créer votre propre sous-classe :

 
Sélectionnez
#!/usr/bin/perl 
# catpod2, classe et programme

package catpod_analyseur; 
use Pod::Parser; 
@ISA = qw(Pod::Parser); 
sub command {
    my ($analyseur, $commande, $paragraphe, $num_ligne) = @_;
    my $hf_sortie = $analyseur->output_handle();
    $paragraphe .= "\n" unless substr($paragraphe, -1) eq "\n";
    $paragraphe .= "\n" unless substr($paragraphe, -2) eq "\n\n";
    print $hf_sortie "=$commande $paragraphe"; 
}

sub verbatim {
    my ($analyseur, $paragraphe, $num_ligne) = @_;
    my $hf_sortie = $analyseur->output_handle();
    print $hf_sortie $paragraphe; 
}

sub textblock {
    my ($analyseur, $paragraphe, $num_ligne) = @_;
    my $hf_sortie = $analyseur->output_handle();
    print $hf_sortie $paragraphe; 
}

sub interior_sequence {
    my ($analyseur, $commande_seq, $argument_seq) = @_;
    return "$commande_seq<$argument_seq>"; 
}

if (!caller) {
    package main;
    my $analyseur = catpod_analyseur->new();
    unshift @ARGV, '-' unless @ARGV;
    for (@ARGV) { $analyseur->parse_from_file($_); } 
} 
1; 
__END__

=head1 NAME 

documentation décrivant le nouveau programme catpod, ici

Comme vous le voyez, c'est un peu plus long et plus compliqué. C'est également plus évolutif, car tout ce que vous avez à faire est de brancher vos propres méthodes lorsque vous voulez que votre sous-classe agisse différemment que sa classe de base.

Le dernier morceau à la fin, où il est écrit !caller, vérifie si le fichier est utilisé en tant que module ou en tant que programme. S'il est employé comme programme, il n'y aura alors pas d'appelant (caller en anglais). Il lancera donc son propre analyseur (en utilisant la méthode new dont il a hérité) sur les arguments de la ligne de commande. Si aucun nom de fichier n'a été passé, il prend l'entrée standard, tout comme le faisait la version précédente.

À la suite du code du module, il y a un marqueur __END__, une ligne vide sans espace, puis la propre documentation en pod du programme/module. Il s'agit d'un exemple d'un fichier qui est à la fois un programme et un module et sa propre documentation. Il est probablement plusieurs autres choses également.

26-4. Pièges du pod

Pod est assez direct à apprendre et à écrire, mais il est toujours possible de rater quelques petites choses :

  • Il est vraiment facile d'oublier le signe supérieur final.
  • Il est vraiment facile d'oublier la directive =back finale.
  • Il est facile de mettre accidentellement une ligne vide au milieu d'une longue directive =for comment. Songez à utiliser =begin/=end à la place.
  • Si vous faites une faute de frappe sur l'une des deux balises d'une paire =begin/=end, cela avalera tout le reste de votre fichier (en ce qui concerne le pod). Songez à utiliser =for à la place.
  • Les traducteurs de pod exigent que les paragraphes soient séparés par des lignes complètement vides ; c'est-à-dire, par au moins deux caractères de saut de ligne consécutifs (\n). Si vous avez une ligne avec des espaces ou des tabulations, elle ne sera pas prise comme une ligne blanche. Ceci peut causer le traitement de deux paragraphes ou plus comme s'il n'y en avait qu'un seul.
  • La signification d'un « lien » n'est pas définie par pod et c'est à chaque traducteur de décider quoi en faire. (Si vous commencez à penser que la plupart des décisions ont été reportées sur les traducteurs et pas dans pod, vous avez raison.) Les traducteurs ajouteront souvent des mots autour d'un lien L<>, ainsi, « L<truc(1)> » deviendra « la page de manuel truc(1) », par exemple. Vous ne devriez donc pas écrire des choses comme « la page de manuel L<truc> » si vous voulez que le document traduit soit raisonnablement lisible : cela aboutirait à « la page de manuel la page de manuel truc(1) ».

Si vous avez besoin d'un contrôle total sur le texte utilisé dans un lien, utilisez plutôt la forme L<montre ce texte|truc>.

Le programme standard podchecker vérifie la syntaxe pod pour trouver les erreurs et les avertissements. Par exemple, il recherche les séquences pod inconnues et les lignes blanches trompeuses contenant des espaces. Il est toujours recommandé de passer votre document à travers différents traducteurs de pod et de corriger les résultats. Certains des problèmes que vous trouverez peuvent être des idiosyncrasies des traducteurs particuliers, ce sur quoi vous voudrez ou non travailler.

Et, comme toujours, tout est sujet à modification selon les caprices d'un hacker quelconque.

26-5. Documenter vos programmes Perl

Nous espérons que vous documentez votre code, que vous soyez ou non un hacker quelconque. Si c'est le cas, vous pouvez souhaiter inclure les sections suivantes dans votre pod :

 
Sélectionnez
=head1 NOM

    Le nom de votre programme ou de votre module. 

=head1 SYNOPSIS 

    Une description d'une ligne sur ce que fait (soi-disant) votre programme ou votre module. 

=head1 DESCRIPTION 

    La masse de votre documentation. (Masse est le terme approprié dans ce contexte.) 

=head1 AUTEUR 

    Qui vous êtes. (Ou un pseudonyme, si vous avez honte de votre programme.) 

=head1 BOGUES 

    Ce que vous avez mal fait (et pourquoi ce n'était pas vraiment votre faute). 

=head1 VOIR AUSSI 

    Où les gens peuvent trouver des informations en rapport (pour qu'ils puissent travailler sur vos bogues). 

=head1 COPYRIGHT 

    Les instructions légales. Si vous voulez revendiquer un copyright
    explicite, vous devriez dire quelque chose comme : 
Copyright 2013, Randy Waterhouse. Tous droits réservés?
    Beaucoup de modules ajoutent également :
    Ce programme est un logiciel libre. Vous pouvez le copier ou le redistribuer sous les mêmes termes que Perl lui-même.

Un avertissement : si vous êtes sur le point de mettre votre pod à la fin du fichier et que vous utilisez les tokens __END__ ou __DATA__, assurez-vous de mettre une ligne vide avant la première directive pod :

 
Sélectionnez
__END__ 

=head1 NOM 

Moderne -Je suis vraiment un modèle d'un module essentiel moderne

Sans la ligne vide avant le =head1, les traducteurs de pod ignoreront le début de votre documentation (vaste, précise, cultivée).

27. Culture Perl

Ce livre fait partie de la culture Perl, nous ne pouvons donc pas espérer mettre ici tout ce que nous connaissons à propos de la culture Perl. Nous pouvons seulement attiser votre appétit avec un peu d'histoire et un peu d'art — certains diraient « de l'art mineur ». Pour une dose beaucoup plus grosse de culture Perl, voir www.perl.org et www.perl.com. (Larry garde des copies de tous ses délires (officiels) à www.wall.org/~larry.) Ou acoquinez-vous à d'autres programmeurs Perl. Nous ne pouvons pas vous dire quel genre de personnes ils seront — le seul trait de caractère que les programmeurs Perl ont en commun est qu'ils sont pathologiquement serviables.

27-1. L'histoire en pratique

Pour comprendre pourquoi Perl est ainsi défini (ou indéfini), il faut d'abord comprendre la raison même pour laquelle Perl existe. Sortons donc le grimoire poussiéreux...

En 1986, Larry était un programmeur système sur un projet de réseaux à grande échelle et à multiples niveaux de sécurité. Il s'occupait d'une installation composée de trois Vax et de trois Sun sur la Côte Ouest, connectés par une ligne série chiffrée à 1200 bauds à une configuration similaire sur la Côte Est. Comme son travail consistait surtout en du support (il n'était pas programmeur sur le projet, seulement le gourou système), il put exploiter ses trois vertus (paresse, impatience et orgueil) pour développer et améliorer toutes sortes d'outils intéressants — comme rn, patch et warp.(193)

Un jour, alors que Larry finissait de découper rn en petits morceaux, le laissant éparpillé sur le sol de son répertoire, le Grand Patron vint à lui et dit : « Larry, il nous faut un système de gestion de configurations et de contrôle pour les six Vax et les six Sun. Dans un mois. Exécution ! »

Larry, qui n'a jamais été un fainéant, se demanda quel était le meilleur moyen d'obtenir un système de gestion de configurations transcontinental, sans devoir partir de zéro, qui permettrait de visualiser les rapports d'incidents sur les deux côtes, avec validation et contrôle. La réponse tenait en un mot : B-news.(194)

Larry commença par installer les news sur ces machines et ajouta deux commandes de contrôle : une commande « append » pour enrichir un article existant et une commande « synchronize » pour que les numéros d'articles restent les mêmes sur les deux côtes. La gestion du contrôle passerait par RCS (Revision Control System) et les validations et les soumissions se feraient par les news et rn. Jusqu'ici, tout allait bien.

Puis, le Grand Patron lui demanda de fournir des rapports. Les news étaient maintenues dans des fichiers séparés sur une machine maître, avec un grand nombre de références croisées entre les fichiers. La première pensée de Larry fut : « Utilisons awk. » Malheureusement, le awk d'alors ne savait pas gérer l'ouverture et la fermeture de plusieurs fichiers en fonction des informations de ces fichiers. Larry ne voulait pas programmer un outil spécifique. C'est ainsi qu'un nouveau langage naquit.

À l'origine, il ne s'appelait pas Perl. Larry envisagea un certain nombre de noms avec ses collègues et son entourage (Dan Faigin, qui écrivit cette histoire et Mark Biggar, son beau-frère, qui l'assista énormément lors de la conception initiale). Larry considéra et rejeta en fait tous les mots de 3 ou 4 lettres du dictionnaire. L'un des premiers avait été « Gloria », inspiré de sa chère et tendre (et épouse). Il se dit rapidement que cela provoquerait trop de confusions d'ordre domestique.

Le nom devint alors « Pearl », qui muta en notre « Perl » actuel, en partie parce que Larry trouva une référence à un autre langage nommé « PEARL », mais surtout parce qu'il est trop paresseux pour taper cinq lettres d'affilée à chaque fois. Évidemment, afin que Perl puisse être utilisé comme l'un de ces mots de quatre lettres chers aux anglophones. (Notez cependant les vestiges du nom initial dans le développement de l'acronyme : « Practical Extraction And Report Language »).

Le Perl des origines avait beaucoup moins de fonctionnalités que celui d'aujourd'hui. La recherche de correspondances et les handles de fichiers étaient là, les scalaires aussi, de même que les formats, mais il ne comportait que très peu de fonctions, pas de tableaux associatifs et juste une implémentation restreinte des expressions régulières, empruntée à rn. Le manuel ne contenait que 15 pages. Mais Perl était plus rapide que sed et awk et commença à être utilisé pour d'autres applications du projet.

Mais Larry était demandé par ailleurs. Un autre Grand Patron arriva un jour et dit : « Larry, il faut assister la R&D » . Et Larry fut d'accord. Il prit Perl avec lui et découvrit qu'il se transformait en un bon outil d'administration système. Il emprunta à Henry Spencer son superbe paquetage d'expressions régulières et le charcuta en quelque chose auquel Henry préférerait ne pas penser à table. Puis Larry ajouta la plupart des fonctionnalités qui lui semblaient bonnes et quelques autres que d'autres personnes désiraient. Il le diffusa sur le réseau.(195) Le reste, comme on dit, appartient à l'histoire.(196)

L'histoire s'est déroulée comme ceci : Perl 1.0 sort le 18 décembre 1987 ; certains prennent toujours au sérieux l'anniversaire de Perl. Perl 2.0 suit en juin 1988 et Randal Schwartz crée la signature légendaire « Just Another Perl Hacker » (juste un autre bidouilleur Perl). En 1989, Tom Christiansen présente le premier tutoriel public au Usenix de Baltimore. Avec Perl 3.0 en octobre 1989, le langage sort et distribué pour la première fois dans les termes de la Licence Publique Générale de GNU.

En mars 1990, Larry écrit le premier poème en Perl (voir la section suivante). Puis lui et Randal écrivent la première édition de ce livre, le « Pink Camel » (le chameau rose ou fushia) ; il est publié en début d'année 1991. Perl 4.0 sort en même temps ; il comprend la Licence Artistique ainsi que la LPG.

L'inauguration du très attendu Perl 5 se déroule en octobre 1994. C'est une réécriture complète de Perl, comprenant des objets et des modules. L'avènement de Perl 5 fait même la couverture du journal The Economist. En 1995, CPAN est officiellement présenté à la communauté Perl. Jon Orwant commence à publier The Perl Journal en 1996. Après une longue gestation, la seconde édition de ce livre, le « Blue Camel » (le chameau bleu) paraît cet automne. La première conférence Perl d'O'Reilly (TPC) se déroule à San José, en Californie, durant l'été 1997. Des événements marquants arrivent maintenant presque quotidiennement, donc, pour la suite de l'histoire, regardez la Perl Timeline sur CPAST, le Comprehensive Perl Arcana Society Tapestry (N.d.T. La ligne du temps sur la tapisserie de la société des arcanes détaillés de Perl) sur history.perl.org.

27-2. Poésie en Perl

La contrefaçon ci-contre apparut sur Usenet le 1er avril 1990. Elle est présentée sans commentaires, essentiellement afin de montrer quel degré d'abjection peuvent atteindre les métaphores d'un langage de programmation typique. Larry est particulièrement soulagé que « Black Perl », écrit à l'origine pour Perl 3, ne compile plus sous Perl 5.

Le, euh, corpus de Larry a été heureusement supplanté par la Poétesse Perl régnante, Sharon Hopkins. Elle a écrit un certain nombre de poèmes Perl, ainsi qu'un article sur la poésie en Perl, qu'elle a présenté à la Conférence Technique Usenix de l'hiver 1992, intitulé « Camel and Needles: Computer Poetry Meets the Perl Programming Language » (Le chameau et les aiguilles : quand la poésie informatique rencontre le langage de programmation Perl). (L'article est disponible sous misc/poetry.ps sur CPAN.) Non contente d'être la plus prolifique des poètesses Perl, Sharon est également la plus largement publiée, le poème suivant étant sorti dans l'Economist et le Guardian :

 
Sélectionnez
#!/usr/bin/perl

APPEAL: 

listen (please, please); 

open yourself, wide;
    join (you, me), 
connect (us,together), 

tell me. 

do something if distressed;

    @dawn, dance;
    @evening, sing;
    read (books,$poems,stories) until peaceful;
    study if able;

    write me if-you-please; 

sort your feelings, reset goals, seek (friends, family, anyone);
        do*not*die (like this)
        if sin abounds; 

keys (hidden), open (locks, doors), tell secrets; 
do not, I-beg-you, close them, yet.

                        accept (yourself, changes),
                        bind (grief, despair); 

require truth, goodness if-you-will, each moment; 

select (always), length(of-days) 

# listen (a perl poem) 
# Sharon Hopkins 
# rev. June 19, 1995

Poésie en Perl
Article 970 of comp.lang.perl:
Path: jpl-devvax!jpl-dexxav!lwall
From: lwall@jpl-dexxav.JPL.NASA.GOV (Larry Wall)
Newsgroups: news.groups,rec.arts.poems,comp.lang.perl
Subject: CALL FOR DISCUSSION: comp.lang.perl.poems
Message-ID: <0401@jpl-devvax.JPL.NASA.GOV>
Date: 1 Apr 90 00:00:00 GMT
Reply-To: lwall@jpl-devvax.JPL.NASA.GOV (Larry Wall)
Organization: Jet Prepulsion Laboratory, Pasadena, CA
Lines: 61
Mon attention a été attirée par l'urgente nécessité qu'éprouvent les gens à exprimer simultanément leurs natures émotionnelle et technique. Plusieurs personnes m'ont envoyé des messages qui ne correspondent à aucun groupe de news. C'est peut-être parce que j'ai récemment posté simultanément dans comp.lang.perl et dans rec.arts.poems que des gens écrivent des poèmes en Perl, et qu'ils me demandent où les poster. En voici un échantillon :
D'un étudiant en faculté (dans ses dernières semaines), le haiku suivant :

 
Sélectionnez
study, write, study, 
do review (each word) if time. 
close book. sleep? what's that?

Et quelqu'un de Fort Lauerdale écrit :

 
Sélectionnez
sleep, close together, 
sort of sin each spring & wait; 
50% die

Une personne désirant rester anonyme a écrit l'exemple suivant de « Black Perl » (le poète de Pearl en aurait été choqué, sans aucun doute.)

 
Sélectionnez
BEFOREHAND: close door, each window & exit; wait until time.
     open spellbook, study, read (scan, select, tell us); 
write it, print the hex while each watches,
    reverse its length, write again;
    kill spiders, pop them, chop, split, kill them.
        unlink arms, shift, wait & listen (listening, wait), 
sort the flock (then, warn the "goats" & kill the "sheep");
    kill them, dump qualms, shift moralities,
    values aside, each one;
        die sheep! die to reverse the system
        you accept (reject, respect); 
next step,
    kill the next sacrifice, each sacrifice,
    wait, redo ritual until "all the spirits are pleased";
    do it ("as they say"). 
do it(*everyone***must***participate***in***forbidden**s*e*x*). 
return last victim; package body;
    exit crypt (time, times & "half a time") & close it,
    select (quickly) & warn your next victim; 
AFTERWORDS: tell nobody.
    wait, wait until time;
    wait until next year, next decade;
        sleep, sleep,
        die yourself, die at last

Je l'ai essayé et il compile effectivement en Perl. Il ne semble cependant pas faire grand-chose. Ce qui à mon avis est probablement heureux... Je propose donc la création de comp.lang.perl.poems pour servir de lieu d'accueil pour ces articles, afin que nous ne polluions pas les groupes de news sur perl ou les poèmes avec des choses qui ne seraient d'intérêt à aucun d'entre eux. Ou bien, nous pouvons créer rec.arts.poems.perl pour ceux qui se contentent de compiler, sans rien faire d'utile (il existe des précédents dans rec.arts.poems, après tout). Ensuite, créons également comp.lang.perl.poems pour ceux qui font réellement quelque chose, comme ce haiku personnel :

 
Sélectionnez
print STDOUT q Just another Perl hacker,
unless $spring

Larry Wall lwall@jpl-devvax.jpl.nasa.gov


précédentsommairesuivant
Qui fait maintenant partie de la distribution standard de Perl.
Le schéma général est que si le deuxième nombre de la version est pair, il s'agit d'une version maintenue ; s'il est impair, il s'agit d'une version de développement. L'extension .tar.gz, qui est parfois écrite .tgz, indique qu'il s'agit du format standard de l'Internet pour une archive tar GNUzippée, couramment appelée « tarball ».
Le bit setuid des permissions UNIX est le mode 04000, et le bit setgid est 02000 ; l'un comme l'autre peuvent être positionnés pour donner à l'utilisateur du programme certains des privilèges de son (ou de ses) propriétaire(s). D'autres systèmes d'exploitation peuvent conférer des privilèges spéciaux aux programmes par d'autres moyens, mais le principe reste le même.
Ou une fonction produisant une liste.
Un moyen officieux consiste à stocker une chaîne marquée comme clef d'un hachage et de récupérer à nouveau cette clef. Car les clefs ne sont pas des SV complets (nom interne des valeurs scalaires), elles ne portent pas sur elle la propriété du marquage. Ce comportement peut changer un jour ou l'autre, ne comptez donc pas dessus. Soyez prudent lorsque vous manipulez des clefs, de peur que vous n'enleviez accidentellement le marquage de vos données et que vous ne fassiez quelque chose de dangereux avec.
À moins que vous n'utilisiez intentionnellement des locales corrompues. Perl présuppose que les définitions de locales de votre système sont potentiellement compromises. Ainsi, lorsque le pragma use locale est actif, les motifs contenant une classe de caractères symbolique, comme \\w ou [[:alpha:]], produisent des résultats marqués.
Bien que vous puissiez également enlever le marquage d'un handle de répertoire, cette fonction ne marche que sur un handle de fichier. Car, étant donné un handle de répertoire, il n'existe pas de manière portable d'extraire son descripteur de fichier avec stat.
Et sur le Net, les seuls utilisateurs que vous pouvez en toute confiance considérer comme n'étant pas potentiellement hostiles sont plutôt ceux qui sont activement hostiles.
Si besoin et si c'est permis — si Perl détecte que le système de fichiers dans lequel réside le script a été monté avec l'option nosuid, cette dernière sera toujours respectée. Vous ne pouvez pas utiliser Perl pour contourner de cette façon la politique de sécurité de votre administrateur système.
Sur ces systèmes, Perl doit être compilé avec -DSETUID_SCRIPTS_ARE_SECURE_NOW. Le programme Configure qui compile Perl essaie de deviner cette situation, ce qui fait que vous ne devriez jamais le spécifier explicitement.
Oui, vous pouvez encore accomplir des opérations atomiques dans une zone dénucléarisée. Lorsque Démocrite a donné le nom « atome » aux parties indivisibles de la matière, il voulait littéralement dire quelque chose qui ne peut être divisé : a-(non) + tomos (divisible). Une opération atomique est une action qui ne peut être interrompue. (Essayez seulement d'interrompre un jour une bombe atomique.)
Sauf si vous êtes sur un système comme OpenBSD, qui affecte de nouveaux PID de manière aléatoire.
Une solution à ceci, qui ne fonctionne que sous certains systèmes d'exploitation, consiste à appeler sysopen en utilisant un OU avec le f lag O_NOFOLLOW. Ceci fait échouer la fonction si le composant final du chemin est un lien symbolique.
Excepté après coup, en faisant un stat sur les deux handles de fichiers et en comparant le deux premières valeurs renvoyées pour chacun (la paire périphérique/inode). Mais c'est trop tard, car le mal est déjà fait. Tout ce que vous pouvez faire est de constater les dégâts et d'abandonner (et peut-être envoyer furtivement un email à l'administrateur système).
Merci de ne pas rire. Nous avons vraiment vu des pages web faire cela. Sans Safe !
Cela dit, si vous générez une page web, il est possible d'émettre des balises HTML, comprenant du code JavaScript, qui pourrait faire quelque chose auquel le navigateur distant ne s'attendait pas.
N.d.T. Et puisqu'en français, nous avons la chance de faire la différence entre les genres, il signifie également « elle ». De plus, comme la langue de Molière nous a également fait cadeau d'une différenciation des pronoms selon leur fonction grammaticale, $_ peut finalement vouloir dire « il », « elle », « le », « la » ou « lui ». Pauvres Anglo-Saxons : ils n'ont pas plus d'une manière de le dire !
Dans ce paragraphe, plusieurs items énumérés d'affilée se réfèrent à l'exemple qui suit, puisque certains de nos exemples illustrent plus d'un idiome.
ou « elles », ou « les », ou « leur », ou « eux ».
N.d.T. En français dans le texte.
N.d.T. Les lvalues sont des valeurs (values, en anglais) que l'on peut mettre à gauche (left, en anglais) d'une affectation. Il y a donc ici un jeu de mots que l'on peut traduire littéralement par « puisque les valeurs que l'on peut mettre à gauche d'une affectation peuvent être mises à gauche d'une affectation. »
N.d.T. Le « Camel book » (littéralement le livre au chameau) est le surnom couramment employé par les fans de Perl (les Perl Mongers) pour désigner le livre que vous avez entre les mains ainsi que ses illustres ancêtres. Bien entendu, ceci est dû au dessin de la couverture (heu... c'est en fait un dromadaire, voir le Colophon). Selon les recherches approfondies des Perl Mongers, le chameau de cette troisième édition porte le doux nom d'Amélia, alors que celui de la précédente édition se prénommait Fido.
En fait, XML::Parser n'est qu'un enrobage pratique autour de l'analyseur XML expat de James Clark.
Techniquement parlant, Perl fait une correspondance avec le motif /^#\\s*line\\s+(\\d+)\\s*(?:\\s"([^"]+)")?\\s*$/, avec $1 fournissant le numéro de ligne pour la ligne suivante et $2 le nom de fichier optionnel spécifié entre les guillemets. (Un nom de fichier vide laisse __FILE__ inchangé.)
Toute conversation ne se doit pas d'être multiculturellement exacte. Perl essaie de vous donner au moins une manière de faire la Chose À Faire mais n'essaie pas de vous l'imposer strictement. À cet égard, Perl se rapproche plus de votre langue maternelle que de la langue de votre nourrice.
Certaines personnes en marge de la société lancent le script Configure de Perl comme une forme d'amusement bon marché. Certaines personnes sont même connues pour organiser des « courses de Configure » entre des systèmes concurrents et parient dessus de grandes sommes d'argent. Cette pratique est désormais hors-la-loi dans la majeure partie du monde civilisé.
Si vous concevez un traducteur de pod générique, et non pour du code Perl, vos critères peuvent différer.
Et si ce n'est pas le cas, allez chercher la version des Perl Power Tools dans le répertoire scripts de CPAN.
Et si vous ne possédez pas grep, voir la précédente note de bas de page.
C'est à peu près à ce moment que Larry revendiqua l'expression « feeping creaturism »
(N.d.T. Jeu de mots délibéré avec « creeping featurism », ajouter des fonctionnalités rampantes, signifiant que le système ou le programme en question est devenu une créature difforme à cause des bidouilles qu'on lui a successivement ajoutées). Ceci dans une tentative désespérée de justifier, sur la base d'une nécessité biologique, l'habitude d'ajouter « rien qu'une dernière fonctionnalité ». Après tout, si la vie est simplement trop compliquée, pourquoi pas les programmes ? Surtout les programmes comme rn qui devraient vraiment être traités comme des projets avancés d'Intelligence Artificielle afin qu'ils puissent lire les news pour vous. Évidemment, certains trouvent le programme patch déjà trop intelligent.
C'est-à-dire la seconde implémentation du logiciel de transport de Usenet.
Ce qui est encore plus extraordinaire, c'est qu'il continua à le diffuser alors qu'il était embauché chez Jet Propulsion Lab, puis NetLabs, puis Seagate. Maintenant, d'autres personnes font la plus grande partie du vrai boulot et Larry prétend travailler pour O'Reilly & Associates (une petite société qui publie des opuscules sur les ordinateurs et autres balivernes).
Et voici, pour ainsi dire, un addendum à l'histoire. Aux débuts de Perl, rn venait d'être mis en pièces avant une réécriture complète. Depuis qu'il a commencé à travailler sur Perl, Larry n'a plus touché à rn. Il reste en pièces détachées. Parfois, Larry menace de réécrire rn en Perl, mais jamais sérieusement.

Licence Creative Commons
Le contenu de cet article est rédigé par Larry Wall, Tom Christiansen et Jon Orwant et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Pas de Modification 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2018 Developpez.com.