En général la documentation du perl est bien faite et accessible à tous. Le but de ce document est de faire profiter les autres de mes erreurs dans l’utilisation de ce langage et de leur faire gagner du temps.
Il ne faut pas voir ce langage comme une exécution lourde et une cause perdue pour la maintenabilité. Dans 90% des cas l’emploi et/ou le choix du Perl sera fait pour des questions d’efficacité. Quand j’emploie efficacité, j’entends par ce terme : du temps en plus pour vous. Si l’on prend l’exemple d’une action ‘one shot’ qui comprend un grand nombre de tâches répétitives. Ne pas automatiser/scripter cette tâche est une hérésie.
En partant de rien, faire cette action peut prendre (on va dire) une demi-journée de code pour une seule et unique exécution de 5 minutes. Soyons fou imaginons la réalisation de cette action en shell :
Maintenant voyons la chose en Perl :
Si l’on part du principe qu’une demi-journée c’est 3h30 :
Donc le Perl, vous apportera une vie sexuelle et un cancer des poumons... ou de la gorge.
Je vais essayer de dévoiler tout au long de ce document l’esprit du langage. Et prouver que ce langage a les propriétés suivantes :
Ce qui est vraiment très intéressant avec ce langage, c’est que l’on a l’impression qu’il progresse au fur à mesure que l’on progresse dans son utilisation. Chaque niveau de maîtrise du PERL a sa pratique, ses possibilités et ses limites.
Le PERL est la fusion de différents mondes de la programmation :
On retrouve très souvent les influences d’autres langages dans le PERL.
Le PHP a été inventé, en 1994, par le Canadien Rasmus Lerdorf, en modifiant un peu apache et en intégrant la version 5.0 de PERL. Rasmus avait mis en place ce système pour savoir qui consultait son CV sur le net2). Il baptisa son bébé : Personal Home Page ⇒ PHP. Ce qui posa beaucoup de problèmes pour développer le langage et l’imposer3). En 1997, pour faire plus cool et en pompant un peu GNU, le nom est changé pour PHP: HyperText Processor (Définition récursive). Bref la grande question n’est pas de savoir : “Quel est le langage le meilleur entre le PERL et le PHP ?”. Car finalement le PHP dérive du PERL. D’autre part, au-delà des questions stupides : “Quel est le langage le plus cool ?”, il faut plutôt se demander : “Lequel est le plus abouti ?”
| Langage | Version 1.0 | support Objets | support vraiment Objets |
|---|---|---|---|
| PHP | 1994 | 2000 | 2005 |
| Perl | 1987 | 1994 | 1997 |
Je pense que l’on pourra vraiment parler de langage de programmation pour le PHP à partir de la version 5 voir 6. Le PERL a quelques années d’avance qui vont petit à petit se réduire.
Voir Perl PHP Soap - Comment combiner la puissance de deux languages ?
En me relisant, je me disais qu’un bon nombre de notions sont interdépendantes. Il serait possible de changer la présentation mais je pense qu’il est plus abordable de commencer par les premières questions que l’on se pose quand on est face à un nouveau langage :
Chercher l’information est primordial avec ce langage. D’autre part je ne pense pas que cela soit utile de réécrire une nième doc sur le Perl. Des bien plus intelligents et surtout meilleurs en orthographe que moi l’on fait auparavant.
Une petite note : les bouquins O’Reilly de Perl ne sont que la documentation fournie avec PERL en format papier.
Dans le man de perl on va trouver deux choses intéressantes :
Voici un bout du man :
La lecture des différents mans est très enrichissante.
On peut considérer que si on lit le man perlsyn on sait faire du perl. L’essentiel est dans ce man. C’est la première lecture pour un débutant et la référence pour un confirmé. Il faut être réaliste : on ne comprend pas tout à la première lecture. A chaque fois qu’on le relit, on y voit quelque chose de nouveau ou une manière de procéder que l’on n’était pas capable d’envisager avant.
C’est la bible des opérateurs, le Perl est riche en mots-clés et en opérateurs. C’est vraiment pratique de pouvoir choisir son opérateur en fonction de ce que l’on est en train de faire, de sa propre norme, de son humeur ou du trip en cours. Par exemple on peut écrire les lignes suivantes qui donneront toutes la même chose :
$failed && exit; $failed and exit; ! $failed || exit; not $failed or exit; ! $failed or exit; not $failed || exit;
C’est le grand jeu des PerlMongers4) : l’art d’écrire la même signification logique de manière différente. Ce qui est un plus dans le langage : on peut s’exprimer en PERL avec son propre style.
Il contient toutes les fonctions internes au langage comme split, join, etc... Le problème c’est qu’elles sont toutes les unes avec les autres dans le man. C’est pratique, cela permet de découvrir de nouvelles fonctions, même si l’on a un peu l’impression de lire un dictionnaire.
Un grand nombre de modules sont disponibles sur l’internet. Chaque module publié se doit d’avoir une documentation incluse dans le module. Lors de la lecture d’un script on peut tomber sur une ligne qui ressemble à cela :
use File::Find;
Ceci en gros pour dire qu’un module est utilisé avec le script. La plupart du temps les noms de modules sont explicites, mais pour plus d’information on peut avoir plus d’info avec la commande suivante :
perlkiller@mybox$ man File::Find
Perldoc est une commande qui permet à la base d’avoir accès à la doc de perl sur les machines qui ne disposent pas de man. A partir du moment où PERL est disponible, cette commande doit l’être aussi.
On retrouve les mêmes fonctionnalités que pour le man :
perlkiller@mybox$ perldoc perlsyn
C’est presque la même chose que pour la version du man à une grosse différence près : la version man du module est générée à l’installation (s’il y a un système d’installation). Pour sa part, perldoc va directement lire la documentation contenue dans le module.
perlkiller@mybox$ perldoc File::Find
Ceci est très pratique comparé à la version man. Si l’on veut avoir accès à la définition d’une fonction directement.
Dans les mans de perl il existe des FAQ inclus (perlfaq[1-9] ce qui a permis d’écrire les Cook book). Il peut être très lourd de chercher quelque chose dans ces 9 mans différents, mais heureusement, perldoc est là pour ça. Par exemple, si vous voulez savoir comment faire l’équivalent de la commande find mais en perl :
perlkiller@mybox$ perldoc -q find
On obtiendra alors une compilation des questions où le mot clef find est présent.
La documentation de Perl est ce qui fait sa force et sa faiblesse. On n’a pas besoin d’aller rechercher la documentation sur le net comme cela est le cas pour PHP, elle est (la plupart du temps) disponible sur la machine, elle même. D’autre part chaque module contient sa documentation cela en ralenti d’autant plus le chargement/l’interprétation du script qui l’utiliserait.
Le Perl est un langage plein de contradictions, c’est à la fois simple et difficile. Lorsque l’on s’exprime en Perl, il existe une certaine permissivité dans l’expression d’un algorithme et la possibilité de faire des sous-entendus. Cela entraîne inévitablement des contresens. Voici ce que cette section va vous apporter :
Il est possible de rendre l’interpréteur de perl plus restrictif au niveau de la syntaxe du langage grâce au module warning. Tout petit script devrait comporter au moins cette vérification.
Voici un petit script. Celui-ci ne fait qu’afficher la chaîne ‘“Hello”‘, il comporte aussi un test de comparaison sur une chaîne de caractères.
#!/usr/bin/perl $check = "do hello"; if ($check == 'do hello') { $var = "Hello"; print $var, "\n"; }
En soit, le code fonctionne et reste compréhensible :
bash-2.05b$ ~/perl/test_warn.pl Hello
Reprenons-le en détail :
1:#!/usr/bin/perl
2:
3:$check = "do hello";
4:if ($check == 'do hello') {
5: $var = "Hello";
6: print $var, "\n";
7:}
‘$check’ avec la chaîne ‘“do hello”‘,numérique avec une chaîne,‘$var’ avec la chaîne ‘“Hello”‘,‘$var’ suivit d’une nouvelle ligne,Maintenant nous allons augmenter le niveau de warnings. Pour ce faire il existe deux méthodes :
#!/usr/bin/perl -w#!/usr/bin/perl use warning;
L’utilisation du module permet d’influer sur les pragmas des warnings.
Revenons à notre petit script :
#!/usr/bin/perl -w $check = "do hello"; if ($check == 'do hello') { $var = "Hello"; print $var, "\n"; }
Examinons l’exécution de celui-ci :
bash-2.05b$ ./test_warn.pl Argument "do hello" isn't numeric in numeric eq (==) at ./test_warn.pl line 4. Argument "do hello" isn't numeric in numeric eq (==) at ./test_warn.pl line 4. Hello
La on peut observer que l’interpréteur Perl s’indigne de l’utilisation d’un opérateur ‘numérique’ dans un contexte ou un opérateur de ‘chaîne’ devrait être présent. Il faut toujours garder à l’esprit que le PERL est un langage assez verbeux qui permet de vous exprimer en argot et en littéraire. Sans utiliser les warnings, il est facile de se mélanger les pinceaux et de faire le contraire de ce que l’on voulait faire.
La grande question pour les experts : Pourquoi ce petit bout de code écrit deux fois le warnings ?
Voici l’opérateur qu’il aurait été de bon aloi d’utiliser :
#!/usr/bin/perl -w $check = "do hello"; if ($check eq 'do hello') { $var = "Hello"; print $var, "\n"; }
Beaucoup diront : “C’est bien gentil les warnings, c’est trop dur de les enlever !!!“. Ce genre de comportement stupide entraîne invariablement des exécutions foireuses. J’avoue qu’il n’est pas forcement aisé de trouver une solution pour éliminer un warning. Pour cela il existe un package pour éduquer le programmeur, c’est diagnostics.
Réutilisons notre petit script :
#!/usr/bin/perl -w use diagnostics; $check = "do hello"; if ($check == 'do hello') { $var = "Hello"; print $var, "\n"; }
Exécutons-le :
bash-2.05b$ ./test_warn.pl
Argument "do hello" isn't numeric in numeric eq (==) at ./test_warn.pl line 7 (#1)
(W numeric) The indicated string was fed as an argument to an operator
that expected a numeric value instead. If you're fortunate the message
will identify which operator was so unfortunate.
Hello
Pour chaque warnings vous aurez le droit à une explication plus complète pour pouvoir résoudre le problème.
La grande question pour les experts : Pourquoi ce petit bout de code affiche maintenant qu’une fois le warnings ?
Note : L’utilisation de diagnotics entraîne l’utilisation du package warning. Une fois le script développé et testé il faudrait retirer l’utilisation de celui-ci. A chaque exécution le fichier ‘diagnostics.pm’ est lu et chargé, ce qui ralentie le démarrage de l’exécution du script.
Les warnings permettent d’avoir un retour sur les erreurs syntaxiques potentielles et ne vérifient en aucun cas que vous savez ce que vous faites en manipulant des données. Le perl est aussi laxiste sur manipulation des structures de données que sur la syntaxe. C’est pourquoi il est possible de restreindre Perl pour stopper le programme (Parfois à l’interprétation) au moindre problème de cohérence de construction.
Pour donner un exemple, en perl il est possible d’utiliser/créer une variable sans la déclarer. L’utilisation de ‘strict’ interdit ce genre de pratique.
Reprenons encore notre petit script :
#!/usr/bin/perl -w use strict; $check = "do hello"; if ($check eq 'do hello') { $var = "Hello"; print $var, "\n"; }
bash-2.05b$ ./test_warn.pl Global symbol "$check" requires explicit package name at ./test_warn.pl line 7. Global symbol "$check" requires explicit package name at ./test_warn.pl line 8. Global symbol "$var" requires explicit package name at ./test_warn.pl line 9. Global symbol "$var" requires explicit package name at ./test_warn.pl line 10. Execution of ./test_warn.pl aborted due to compilation errors.
Visiblement le script refuse de s’exécuter comme précédemment. Rajoutons ‘diagnostics’ pour comprendre ces messages barbares.
#!/usr/bin/perl -w use strict; use diagnostics; $check = "do hello"; if ($check eq 'do hello') { $var = "Hello"; print $var, "\n"; }
bash-2.05b$ ./test_warn.pl
Global symbol "$check" requires explicit package name at ./test_warn.pl line 7.
Global symbol "$check" requires explicit package name at ./test_warn.pl line 8.
Global symbol "$var" requires explicit package name at ./test_warn.pl line 9.
Global symbol "$var" requires explicit package name at ./test_warn.pl line 10.
Execution of ./test_warn.pl aborted due to compilation errors (#1)
(F) You've said "use strict vars", which indicates that all variables
must either be lexically scoped (using "my"), declared beforehand using
"our", or explicitly qualified to say which package the global variable
is in (using "::").
Uncaught exception from user code:
Global symbol "$check" requires explicit package name at ./test_warn.pl line 7.
Global symbol "$check" requires explicit package name at ./test_warn.pl line 8.
Global symbol "$var" requires explicit package name at ./test_warn.pl line 9.
Global symbol "$var" requires explicit package name at ./test_warn.pl line 10.
Execution of ./test_warn.pl aborted due to compilation errors.
Après analyse des indications de diagnostics donc lecture d’une vraie phrase en anglais. On peut commencer à comprendre le problème. L’utilisation de ‘strict’ oblige (entre autres) utiliser des variables a porte lexicale. Nous reviendrons plus tard sur le sujet, pour simplifier on va dire qu’il faut ‘déclarer’ les variables avec le mot clef “my“.
#!/usr/bin/perl -w use strict; use diagnostics; my $check = "do hello"; if ($check eq 'do hello') { my $var = "Hello"; print $var, "\n"; }
bash-2.05b$ ./test_warn.pl Hello
L’ajout du mot clef “my” aux bons endroits a permit de supprimer tous les messages d’erreurs. Car pour chaque utilisation d’une variable nous avions le droit a une erreur.
On peut faire perdre a Perl toute sa souplesse ce qui permet d’ajouter de la rigueur. La contre partie est du passer du temps (au moins au début) à maîtriser les subtilités de la syntaxe et des constructions.
Quelles sont les moyens de manipuler des données les plus efficaces ? C’est une question à laquelle nous essaieront de répondre dans cette partie.
Le Perl est un langage faiblement type. Ce qui signifie, par opposition à un autre langage fortement typé comme C, qu’il n’est pas nécessaire de déclarer le type donnée avant de pouvoir l’utiliser. Dans un langage fortement typé cette opération est indispensable pour déterminer l’espace mémoire avant utilisation. A l’inverse dans un langage faiblement typé l’espace mémoire nécessaire est déterminé en cours d’utilisation. Ce type mécanisme est appelé variables scalaires car elles changent de taille dynamiquement.
En Perl il existe trois principaux types de variables :
L’objectif de cette section est plutôt de montrer des utilisations pratiques des variables.
Un des objectifs de la programmation est de s’abstraire des problèmes d’allocation/de gestion de la mémoire pour se concentrer sur les données elles-mêmes, leurs contenus, leurs sens.
Note: Les exemples seront écrit sans interpréteurs ou inclusion de package pour des questions de lisibilité.
my $var; $var = "coucou\n"; $var = 'coucou\n'; $var = 4;
Dans cet exemple on remarque que l’on peut affecter une chaîne ou un numérique à une variable de la même manière. De ce fait une variable peut contenir :
En PERL, on peut explicitement écrire qu’une variable ne contient rien du tout :
$var = undef;
Elle contient donc une valeur indéfinie.
Ce principe à pour impact de pouvoir savoir si une variable est définie ou pas.
Il y a une différence entre les simples quotes5) et les doubles quotes. Les simples quotes ne sont pas interprétées, dans le cas de l’utilisation d’un caractère de contrôle, dans la chaîne, comme ‘\n’ il est considéré comme deux caractères : ‘\’ et un ‘n’. Voici le tableau des Quote and Quote-like Operators du ‘man perlop‘, a méditer.
Customary Generic Meaning Interpolates
'' q{} Literal no
"" qq{} Literal yes
`` qx{} Command yes*
qw{} Word list no
// m{} Pattern match yes*
qr{} Pattern yes*
s{}{} Substitution yes*
tr{}{} Transliteration no (but see below)
<<EOF here-doc yes*
* unless the delimiter is ''.
Dans la plupart des cas il est préférable d’utiliser les doubles quotes pour éviter les erreurs d’interprétations. Il ne faut pas oublier les différentes possibilités, un jour elles vous seront utiles.
Les tableaux et les listes chaînées sont souvent dissociés, en Perl ce n’est pas le cas. Un tableau peut être considéré comme un tableau, une liste chaînée, ou une pile, en fonction des besoins ou du contexte.
Un des problèmes récurent pour le débutant est d’utiliser correctement un tableau. L’habitude d’autre langage le pousse utiliser les tableaux de la manière suivante :
my @tab; $tab[0] = "coucou"; $tab[1] = "blah"; $tab[2] = "blah"; $tab[3] = "cool"; my $i; for ($i = 0; $i <= $#tab; $i++) { print $tab[$i], ","; } print "\n"; foreach my $elm (@tab) { print $elm, ","; } print "\n";
bash-2.05b$ ./test_tab.pl coucou,blah,blah,cool, coucou,blah,blah,cool,
Dans ce code montre deux types de boucle pour afficher le tableau :
‘for’ on considère le tableau comme un tableau et l’on y accède par le biais d’un indice. Cette méthode est peu utilisée pour ce genre de pratique en Perl, car c’est la plus lente. Il est rare que l’accès par indice soit utilisé pour parcourir l’ensemble du tableau, par contre c’est la plus rapide pour accéder à un élément du tableau.‘foreach’ on considère le tableau comme une liste chaînée. C’est la plus rapide pour parcourir tous les éléments du tableau.Dans cet exemple :
foreach my $elm (@tab) { print $elm, ","; } print "\n";
On peut déclarer une variable ‘$elm‘, entre ‘foreach’ et la parenthèse ouvrante, qui représentera dans la boucle l’élément courant du tableau parcouru. Cette forme est la plus pratique pour les débutants ; c’est la moins implicite de toutes. A la moindre erreur rencontrée il faut se tourner vers cette forme. Car on peut tout à fait ne pas déclarer de variable pour manipuler l’élément courant. Dans la boucle l’élément courant sera représenté par la variable ‘$_‘. Cette variable est une variable implicite, une sorte de joker.
foreach (@tab) { print $_; print ","; } print "\n";
Et si c’est un joker cela veut dire que l’on peur se passer de l’écrire. Cette variable est implicitement passée en paramètre à toutes les fonctions internes du Perl si rien n’est précisé.
foreach (@tab) { print; print ","; } print "\n";
Ce mécanisme rends plus difficile la lecture de script à des non initiés. Son utilisation est vraiment une question d’habitude et une technique de feignasse (on préfère dans 90% des cas réfléchir que de taper trois caractères). C’est en grande partie ce pourquoi Perl est qualifié de Juste One Way language. Quoiqu’il en soit cela dépend surtout de qui le lit et de qui l’écrit.
Dans le même esprit pour pourrait afficher le tableau en le dépilant, grâce au mot clef shift...
while (@tab) { print shift @tab; print ","; } print "\n";
... le seul problème c’est que le tableau est vidé avec cette opération. shift enlève le premier élément du tableau et le renvoi.
Voici les équivalents de code si l’on veut afficher le tableau à l’envers :
my $i; for ($i = $#tab; $i >= 0; $i--) { print $tab[$i], ","; } print "\n"; foreach my $elm (reverse @tab) { print $elm, ","; } print "\n"; while (@tab) { print pop @tab; print ","; } print "\n";
bash-2.05b$ ./test_tab.pl cool,blah,blah,coucou, cool,blah,blah,coucou, cool,blah,blah,coucou,
Aussi appelées tableaux associatifs, les tables de hash sont pas très compliquées à utiliser. La principale difficulté d’emploi de ces structures est uniquement algorithmique. On n’a pas toujours l’idée de les utiliser. Pour ceux qui ne maîtrise pas ce type de mécanisme, il faut juste avoir à l’esprit qu’une table de hash contient des clefs uniques et ces clefs permettent d’obtenir des valeurs. Cette association clef ⇔ valeur peut simplifier la réalisation de script ou de programme.
La clef doit toujours être une chaîne de caractères, l’utilisation d’un chiffre comme valeur peut-être utilise mais cela sera toujours moins efficace qu’un tableau que l’on accède via un indice.
my %hash; $hash{weed} = "4kg"; $hash{'maroco'} = -1; $hash{"au soleil"} = $une_variable;
Le mot clef ‘keys’ permet d’obtenir la liste des clefs contenu par une table de hash, ainsi on peut parcourir la table de hash et afficher chacune des associations clef ⇔ valeur.
foreach my $key (keys %hash) { print $k, " => ", $hash{$k}, "\n"; }
On peut avoir besoin d’effacer une valeur de la table de hash
undef $hash{weed}; #ou $hash{weed} = undef;
Attention cette opération ne modifie que la valeur. La clef de cette valeur sera toujours présente dans la table de hash et donc la liste obtenue avec keys.
delete $hash{weed};
La clef n’apparaîtra plus dans dans la liste obtenue avec keys.
C’est dans cette section que l’on va vraiment pouvoir prendre l’ampleur de la richesse des mots clefs du langage. Cette éloquence est un désavantage en soi pour le PERL, elle rend difficiles la lecture d’un script aux novices. Nous allons essayer d’y voir plus clair.
La base et le minimum nécessaire dans un script c’est la structure conditionnelle. Pour explorer les différentes formes, nous allons décliner le même algorithme sous de multiples aspects.
Nous prendrons cet algorithme, un grand classique : avoir dans une variable la valeur la plus grande, d’un ensemble de variable. En gros on a deux variable ‘a’ et ‘b’ (‘$a’ et ‘$b’ en PERL) quelque soit la valeur de ‘a’ ou de ‘b‘, on veut avoir dans la variable ‘a’ la plus grande des valeurs.
Voici le canevas que nous utiliserons :
#!/usr/bin/perl -w use diagnostics; my $a = 5; my $b = 8; #algo print "Max : $a\n";
A chaque fois nous modifierons que la partie : ‘#algo‘;
Voici la forme la plus classique :
if ($a < $b) { $a = $b; }
Jusque ici pas de grande surprise, c’est une forme classique que l’on retrouve dans la plupart des langages. Après chaque instruction il y a un ‘;‘, comme en C, C++, Pascal, PHP... etc.
Toutefois il est possible d’écrire le BLOCK de la manière suivante :
if ($a < $b) { $a = $b }
Il est noter que le ‘;’ a disparu. Pourtant cela fonctionne quand même... Pourquoi ?
Le ‘;’ a pour seul et unique but de séparer les instructions les unes des autres. Or pour ce cas il n’existe qu’une seule instruction dans le BLOCK d’instructions, donc il n’est pas impératif d’utiliser un séparateur (le ‘;‘).
Voici ce que l’on peut trouver dans perlsyn :
if (EXPR) BLOCK if (EXPR) BLOCK else BLOCK if (EXPR) BLOCK elsif (EXPR) BLOCK ... else BLOCK
En utilisant cette forme : ‘if (EXPR) BLOCK‘. Il est obligatoire d’utiliser des ‘()’ et un BLOCK pour définir une structure conditionnelle sous cet aspect. Lorsque la documentation de PERL fait référence à un BLOCK cela se traduit par une utilisation explicite des ‘{}‘.
C’est pourquoi il n’est pas possible d’écrire cette forme conditionnelle sans les ‘{}‘, comme en C :
#Impossible en Perl if ($a < $b) $a = $b; #Ou encore impossible if ($a < $b) $a = $b;
Si l’on tient, quoiqu’il en, soit à utiliser cette forme sur une ligne, il est encouragé d’employer cette forme si le BLOCK ne contient qu’une seule instruction :
if ($a < $b) {$a = $b}
On aurait pu aussi écrire, en déclinant l’algèbre de Bool :
if (!$a > $b) {$a = $b}
Je pense que pour tous ceux qui ont eu un jour à lire du code ce problème d’omission de l’opérateur de négation. Perl dans sa richesse offre le choix des opérateurs de négation.
if (not $a > $b) {$a = $b}
Algorithmiquement on dire que ‘si ce n’est pas EXPR’ est équivalent à ‘a moins que EXPR’
Toujours dans la même logique de notre exemple, on aurait pu écrire :
unless ($a > $b) {$a = $b}
Le mot clef ‘unless’ se révèle très utiles dans bien des cas...
Il est courant d’avoir besoin de conditionner une instruction dans le but d’utiliser un aspect épurer des ‘()’ et des ‘{}‘, pour simplicité d’écriture. Cela signifie que l’on va inclure la condition dans l’instruction.
$a = $b if $a < $b; #Ou encore : $a = $b unless $a > $b;
Notez bien l’emplacement du ‘;‘.
On pourrait faire la démonstration suivante :
if (EXPR) BLOCK
if (EXPR) {STATEMENT;}
if (EXPR) {STATEMENT}
do {STATEMENT} if (EXPR);
STATEMENT if (EXPR);
STATEMENT if EXPR;
Si l’on maîtrise bien ces changements de formes on peut aisément comprendre que cette forme :
if ($a < $b) { $a = $b; $b = undef; }
fait exactement la même chose que celle-ci :
$a = $b and $b = undef unless ($a > $b);
Finalement c’est cette forme qui est la plus simple pour ce type d’algo... mais la moins drôle.
$a = ($a < $b) ? $a : $b;
Rien de bien exceptionnels dans les boucles, on y retrouve la plus part des boucles standard : while, for, until et meme foreach qui vient du monde du script.
C’est une grande question pour les programmeurs qui on fait beaucoup de C. Pour un parcours de tableau en perl le ‘foreach’ est entre 3 et 4 fois plus rapide que d’utiliser un ‘for‘. Cette différence est essentiellement du aux tableaux : Il est plus rapide d’accéder a tout les éléments un à un si le tableau est considéré comme une liste chaînée. Voici un petit script qui met en évidence la différence de temps d’exécution entre les différents types de boucles pour parcourir un tableau.
Consulter le script : For ou Foreach ? dans la section des cas d'écoles pour plus de détails techniques.
Attention ‘foreach’ disparaît dans PERL 6. Il faut bien comprendre que :
foreach my $elm (@tab) { $r += $elm; }
C’est la meme chose que ca :
for my $elm (@tab) { $r += $elm; }
Il faut dès maintenant abandonner ‘foreach (@)’ au profit de ‘for (@)’
Bien que ce document s’adresse à des débutants en Perl, ce langage n’est pas adapté aux novices de la programmation. Il faut avoir un minimum de rigueur personnel pour arriver à exprimer sa pensée dans un script. C’est un peu comme de s’exprimer dans une langue vivante : Il vaut mieux réfléchir avant de parler. Bien sur tout est une question de contexte, quoiqu’il en soit c’est toujours plus simple lorsque l’on a le vocabulaire adapté. Le but du reste de cette section n’est pas d’offrir une nième explication sur les variables ou autres, mais d’apporter un peu de vocabulaire pour s’exprimer en perl avec justesse.
Contrairement à d’autres langages le PERL possède dans sa grammaire des ambiguïtés. Selon les cas, on peut se retrouver dans l’obligation de préciser ce que l’on veut faire. Lors de l’utilisation d’une variable il faut tenir compte de deux facteurs :
Prenons le tableau suivant :
my @tab = qw(a b c d e f);
Traduisons la pensée en Perl.
j’affiche un truc, si j’ai quelque chose dans mon tableau.
print "un truc\n" if @tab;
j’affiche mon tableau
print @tab, "\n";
On constate, sur ces cas, la simplicité avec laquelle on peut s’exprimer en perl. Mais pourquoi est-ce que ça marche ? Sans doute parce que l’algorithme à traduire en Perl est basique. Pourtant si je sors des contextes d’utilisations courants d’un tableau, est-ce aussi facile ?
j’affiche le nombre d’éléments que contient mon tableau.
⇒ je passe par une étape intermédiaire : Je comptes le nombre d’éléments du tableau.
my $i = 0; foreach (@tab) {$i++}; print $i, "\n"; #ou plus tordu $i = 0 print map({$i++; $_ = ''} @tab), "$i\n";
⇒ j’utilise la notation du dernière indice du tableau plus un.
print $#tab + 1, "\n";
⇒ Pourquoi ca marche quand je fais ‘if @tab’ ? Regardons de plus près les définitions synoptiques de ‘if’ et de ‘print’ :
STATEMENT if EXPR; print LIST;
Dans le premier cas nous somme dans une expression (EXPR) donc dans un contexte scalaire, dans l’autre nous somme dans un contexte de liste (LIST). Un contexte de liste n’est qu’une suite de scalaires consécutifs. Si l’on s’arrange pour changer le contexte où l’on utilise le tableau on résout notre problème. Utilisons une expression mathématique pour changer le contexte.
print @tab + 0, "\n";
Tout ceci n’est pas très joli. Il existe un mot clef pour expliciter le contexte scalaire : ‘scalar’
print scalar @tab, "\n";
Il n’existe pas de mot clef pour forcer un contexte de listes, pour la simple et bonne raison que cela est inutile. L’imbrication de plusieurs contextes de listes peuvent être explicité par l’utilisation des ‘()’. Voici un cas où l’on a besoin de clarifier les contextes :
print "tab: ", join '|', @tab, "\n"; # ne donne pas le meme resultat print "tab: ", join('|', @tab), "\n";
Sous UNIX tout est fichier et ce qui ne l’est pas devrait l’être. Ce qui veut dire que tout objets, périphériques systèmes sont manipulables/accessibles comme un simple fichier. De ce fait les langages de programmation nés du monde UNIX ont besoin d’un système simple de manipulation des fichiers. En C (Sous Unix), ce sont des numéros, on est jamais sur d’avoir le même identifiant en ouvrant le même fichier.
Le PERL étant un langage plus linguiste, utilise des chaînes de caractères pour faire référence a un fichier. Donc les fonctions de manipulation de fichier de PERL, demande la plus part du temps comme argument un HANDLE ou en français un descripteur de fichier.
D’autre part en PERL, un HANDLE n’est pas seulement la référence au fichier mais aussi une représentation de son contenu.
open HANDLE_OUT, ">mon_fichier" or die "mon_fichier:$!"; #ou encore die "mon_fichier:$!" unless open(HANDLE_OUT, ">mon_fichier") ;
Bien sur une fois le traitement du file handle terminer il ne faut pas oublier de le fermer.
close HANDLE_OUT;
Il y plusieurs façon d’écrire dans un fichier. En voici une :
print HANDLE_OUT "Ma data qui tuxe";
Une erreur qui est courante dans l’utilisation de ces deux fonctions, c’est leur utilisation contradictoire. Ce qui me gêne le plus c’est cette virgule entre le file handle et le paramètre.
Pourtant tout s’explique lorsque l’on regarde les synopsis de ces deux fonctions :
print FILEHANDLE LIST print LIST open FILEHANDLE,EXPR
Comme print utilise un contexte de LIST, on ne peut pas utiliser de virgule pour séparer les paramètres.
Pour lire les données d’un file handle on peut utiliser la fonction dérivée du système : read. Ou alors utiliser les diamonds ou autrement dit : <> que l’on place autour du file handle. Voici le comportement par défaut des diamants :
Ce comportement est définie par la variable magique de PERL : $/ ou $INPUT_RECORD_SEPARATOR ou $RS. Par défaut cette variable contient :
Donc un traitement ligne par ligne. Si l’on veut traiter un file handle caractère par caractère ou sur un caractère particulier voici ce que cela donnerait :
#!/usr/bin/perl -w use strict; open(HANDLE, "echo coucou |"); $/ = 'u'; while (<HANDLE>) { print; print "\n"; } close HANDLE; open(HANDLE, "echo coucou |"); $/ = \1; while (<HANDLE>) { print; print "\n"; } close HANDLE;
Voila la sortie du programme :
cou cou c o u c o u
Il faut donc toujours avoir à l’esprit que les données du file handle sont toujours pre-maché par perl. Par exemple, si l’on a besoin d’utiliser la totalité du contenu d’un fichier sans avoir le besoin de le traiter ligne par ligne. Bref il ne faut surtout par faire ça :
open FILE, "mon_fichier"; my $data = ''; while (<FILE>) { $data .= $_; } close FILE;
Mais plutôt ce genre de chose :
open FILE, "mon_fichier"; my $RS_SAVE = $/; $/ = undef; $data = <FILE>; close FILE; #ou encore open FILE, "mon_fichier"; my $data; { local $/ = undef; $data = <FILE>; } close FILE;
On pourrait croire qu’il est plus efficace de charger l’intégralité d’un fichier pour le traiter directement en mémoire. Ceci est valable pour un petit fichier, pour les gros volumes de données il vaut mieux traiter le fichier sur le flux.
open FILE, "mon_fichier" or die; my @data = <FILE>; close FILE; for (@data) { print; }
Il ne faut pas oublier de détruire le contenu de data pour éviter d’encombrer inutilement la mémoire :
@data = (); #ou undef @data;
open FILE, "mon_fichier" or die; while (<FILE>) { print; }
Comme vous avez plus le constater la manipulation de fichiers peut se révéler assez lourde en fonction de la méthode que vous pouvez utiliser. Je pense surtout a la méthode open. Tout dépends de ce que vous voulez faire, par exemple une commande Unix.
Cette commande Unix est la plus basique : elle ouvre tout les fichiers qu’elle en arguments et en écrit le contenu sur la sortie standard.
#!/usr/bin/perl -w use strict; if (@ARGV) { for my $f (@ARGV) { if (open FILE, "$f") { while (<FILE>) { print; } } else { warn "$0: $f: $!\n"; } } } else { while (<STDIN>) { print; } }
#!/usr/bin/perl -w use strict; while (<>) { print; }
Il ne faut jamais oublier dans l’utilisation du PERL c’est que c’est melting pot de plusieurs façons de penser et de plusieurs philosophies. Voici comment traiterait la philosophie awk traiterait l’éternel problème des ^M ou \r dans un fichier avec perl :
perl -pi -e 's{\r$}{}' badfile.txt
Comme dans tout langage il est possible de faire des fonctions. Comme en C c’est à la discrétion du programmer de rendre plus ou moins permissif le passage de paramètres.
sub func { print "ma fonction a moi\n"; } func; func(); &func;
Ce qui est amusant c’est que l’on peut appeler une fonction plusieurs manières. Cela pourrait être futile or pas du tout. La présence du ‘&’ permet de préciser que le symbole qui suit est bien une fonction. Dans l’exemple précédant il n’y pas de problèmes car la fonction est déclarée avant sont utilisation.
func; func(); &func; sub func { print "ma fonction a moi\n"; }
Dans cette exemple, si les ‘warning’ ainsi que ‘strict’ ne sont pas activé la fonction ne sera exécuté que deux fois :
$ /tmp/test_func.pl ma fonction a moi ma fonction a moi
Quand rencontre la ligne ‘func;’ il ne peut pas déterminer si c’est ou non une fonction. Si vous voulez vous pouvez, grâce à ‘&‘, rendre la lecture de votre code plus accessible. Vous pouvez le rajouter devant l’appel de fonction si vous trouver que cela désambiguïse la compréhension du script.
Comme pour le reste en PERL, le passage de paramètres peut se faire de plusieurs manières. À chaque appel à une fonction, les variables passées en arguments sont copiées dans le tableau courant ou autrement dit en PERL ‘@_‘. Il suffit simplement de récupérer les éléments du tableau, dans la fonction.
sub func { print $_[2]; print "\n"; } &func('a', 'b', 'c', 'd');
$ ./test_func.pl c
Cette méthode n’est pas très efficace pour la lisibilité et la compréhension du code. D’autre part pour les débutant, il est imperatif de déclarer les variables dans une fonction.
A mon avis, c’est ce qui est de plus facile à manier pour les débutants et suffisamment structuré pour éviter les erreurs typographiques. On est obligé, de déclarer les variables pour les utiliser. On trouve l’utilisation de cette syntaxe dans le module DBI6).
func { my ($var1, $var2, $var3, $var4) = @_; print $var1 + $var2, "\n"; print $var3 * $var4, "\n"; }
Voici aussi un exemple dans le cas où l’on ne veut pas utiliser un argument passé en paramètre, par exemple pour éviter de le recopier encore une fois s’il occupe beaucoup de mémoire.
my ($var1, undef, undef, $var4) = @_;
On peut aussi récupérer un tableau en paramètre pour peu qu’il soit seul ou le dernier.
func { my ($var, @tab) = @_; print join(',', @tab), "\n" unless defined $var; } &func(undef, 'a', 'b', 'c', 'd');
$ ./test_func.pl a,b,c,d
Cette méthode peut paraître verbeuse mais cette utilisation est très courante surtout dans les constructions objets.
func { my $var1 = shift; my $var2 = shift; my $var3 = shift; my $var4 = shift; print $var1 + $var2, "\n"; print $var3 * $var4, "\n"; }
On peut s’interroger sur l’utilisation des parenthèses lors de l’appel d’une fonction en PERL. Certains pensent que de ne pas utiliser de parenthèses pour écrire une fonction est une manière de plus de rendre les scripts illisibles. En fait, il faut avoir a l’esprit que les parenthèses représentent un tableau anonyme, donc leur utilisation change le comportement du passage des paramètres aux fonctions. S’il n’y a pas de parenthèses la fonction prend automatiquement le tableau courant comme argument.
&func; #Est equivalent a &func(@_);
‘&func’ n’est pas du tout la meme chose que ‘&func()‘.
Avant de retourner une ou des valeurs dans une fonction on peut avoir besoin de savoir dans quel contexte la fonction a ete appelee. Grace a la fonction wantarray, ceci est possible.
| contexte | valeure |
|---|---|
| scalaire | false |
| list/array | true |
| void/indefini | undef |
return unless defined wantarray; # On ne va pas plus loin si la valeur de retour n'est pas utilise my @a = complex_calculation(); return wantarray ? @a : "@a"
Pourquoi ? Oui, pourquoi... Lorsque l’on débute en programmation le concept de références paraît toujours obscur. Je pense que c’est aussi une question de maturité, certains principes en programmation deviennent plus clairs avec le temps et la pratique. Les langages ont évolués avec le temps intégrant de nouveaux concepts. Ces concepts naissent d’une nécessité.
Les références sont à mon sens l’âge de raison7) pour les langages. Les précédentes générations de langages étaient les langages procéduraux. Ces derniers, au détour d’un algorithme, ont souvent besoin de manipuler des données à différents endroits du programme (fonction ou procédure). A chaque fois que l’on a besoin de cette donnée on va la copier. Voici un exemple en perl :
my @data = ('coucou', 3, 'blah', 'hihi', 4); #<= 1 ere copie sub func { my @d = @_; #<= 2 ere copie $d[1] += 2; return @d; #<= 3 ere copie } @data = func(@data);
Pour modifier un champ du tableau, nous avons été obligé de recopier 3 fois les données, ce qui pourrait être très problématique si ce tableau contenait un grand nombre d’éléments. Comment faire pour n’avoir le tableau qu’une seule fois en mémoire ? L’utilisation d’une variable globale. C’est sans doute pourquoi la plupart des langages autorisent la création de variable globale. Voici le même exemple mais en utilisant une méthode globale :
my @data = ('coucou', 3, 'blah', 'hihi', 4); #<= 1 ere copie sub func { $data[1] += 2; } func();
Malheureusement, dans ce cas-là nous faisons toujours une copie du tableau et nous ne pouvons plus utiliser la fonction pour un autre tableau.
Utiliser une variable, c’est utiliser de la mémoire. Or on manipule la mémoire avec des adresses mémoires (0xquelque chose) et les variables via leurs noms. D’ailleurs c’est le but des variables de simplifier l’utilisation/l’accès à la mémoire. En PERL on peut connaître l’adresse mémoire d’une variable en antislachant/backslashant une variable.
\$a
Voici un exemple qui affiche l’adresse d’une variable :
#!/usr/bin/perl -w use strict; my $a = "coucou"; print \$a, "\n";
Vous obtiendrez :
SCALAR(0x814f5c4)
Voici le même exemple avec un tableau :
#!/usr/bin/perl -w use strict; my @a = (1..1000); print \@a, "\n";
ARRAY(0x814f5dc)
En terme de volumétrie, il vaut mieux manipuler des références à des données que les données elles mêmes. Sinon on risque de les recopier.
On peut donc utiliser une variable (donc un scalaire) pour stocker une référence :
#!/usr/bin/perl -w use strict; my @a = (1..1000); my $ref = \@a; print $ref, "\n";
ARRAY(0x814f5dc)
Pour éviter de recopier les données (au moins a l’initialisation) il est possible de construire un tableau directement dans une référence. C’est ce que l’on appelle un tableau anonyme.
On peut créer un tableau anonyme :
my $array_ref = [];
Ou une table de hash anonyme :
my $hash_ref = {};
si on les affiche :
print "array: ", $array_ref, "\n"; print "hash: ", $hash_ref, "\n";
On obtiendra :
array: ARRAY(0x817aec0) hash: HASH(0x817aecc)
En reprenant l’exemple précèdent, nous obtenons :
my $data = ['coucou', 3, 'blah', 'hihi', 4]; #Construction anonyme pas de copie sub func { my $d = shift; ${$d}[1] += 2; } func($data);
Pour obtenir la reference d’une variable ce n’est pas bien compliqué il suffit de la précéder d’un backslash ‘\‘.
Pour chaque exemple il y aura aussi l’exemple avec la construction anonyme directe :
my $var = "coucou"; my $ref_var = \$var; #Construction anonyme my $ref_var = \"coucou";
my @tab = ("coucou", "hihi"); my $ref_tab = \@tab; #Construction anonyme my $ref_tab = ["coucou", "hihi"];
my %hash = ( text => "coucou", size => 6 ); my $ref_hash = \$hash; #Construction anonyme my $ref_hash = { text => "coucou", size => 6 }; ;
Pour le déréférencement c’est un peu plus compliqué. Il faut savoir que type de référence on veut utiliser :
| Type | Syntax |
|---|---|
| scalaire | ${$ref} |
| tableau | @{$ref} |
| hash | %{$ref} |
Les accolades sont volontairement rajoutées mais elles sont optionnelles, je pense que pour les débutants ou ceux qui ont un peu de mal c’est plus pratique. La syntaxe d’utilisation change complètement sans les accolades pour les tableaux et les tables de hash. Le fait d’avoir le choix de la syntaxe a un but. Pour le programmeur la syntaxe des références avec les accolades est une phase transitoire dans sa progression du langage. Une fois que les contextes d’utilisation sont bien maîtrisés on dérive naturellement (par flemme) vers la syntaxe sans accolade. Il ne faut pas oublier que la syntaxe avec accolades permet de résoudre des situations de contexte complexe.
Voici quelques exemples d’utilisation en reprenant les exemples du référencement :
print $var, "\n"; #avec la reference print ${$ref_var}, "\n"; #sans les accolades print $$ref_var, "\n";
if (@tab) { print $tab[0], "\n"; } #avec la reference if (@{$ref_array}) { print ${$ref_array}[0], "\n"; #sans les accolades print $ref_array->[0], "\n"; }
for my $k (keys %hash) { print "$k => ", $hash{$k}, "\n"; } #avec la reference for my $k (keys %{$ref_hash}) { print "$k => ", ${$ref_hash}{$k}, "\n"; #sans les accolades print "$k => ", $ref_hash->{$k}, "\n"; }
my $tab = ['a', 0, 'c']; print "Nombre d'elements dans le tableau: ", scalar @$tab, "\n"; #parcours for my $e (@$tab) { print "elm: " , $e, "\n"; } #ajout d'un element push @$tab, 2; #Affichage d'un element du tableau print "elm: ", $tab->[0], "\n"; print "elm: ", $$tab[0], "\n";
Cette structure est très pratique pour gérer plusieurs propriétés d’un élément. Prenons l’exemple d’une liste de fichiers.
my @files = (); # Path filename size chmod push @files, ['/tmp', 'test.txt', 4103, 0644]; push @files, ['/var/tmp', 'test.doc', 719823, 0600]; for my $f (@files) { print join('/', @{$f}[0,1]), " (", $f->[2], " bytes)\n"; }
On peut aussi faire la même chose directement avec une référence de tableau.
my $files = []; # Path filename size chmod push