PERL: on ne nous dit pas tout ...
Sommaire
Motivation
Pièges de la syntaxe
Préfixes et types
Le plus déroutant est peut-être la façon (cf man perldata) dont Perl utilise les préfixes $ @ % en fonction des types de variables.
### Noms: $v @v %v sont trois variables de types distincts ### $v = "123"; #Variable scalaire (entier, réel, string, référence) @v = ( "a", "1", "b", "2" ); #Variable tableau %v = ( "a" => "1", "b" => "2" ); #Variable hachage
Accès aux variables
Choisir entre crochets, parenthèses ou accolades n'est pas innocent, puisque par exemple les index [] et {} ne concernent pas les mêmes variables.
$a = $v[1]; #"1" lu dans le tableau @v (accepté mais obsolète: @v[1]) $a = $v{"a"}; #"1" lu dans le hachage %v (accepté mais obsolète: %v{"a"}) $ra = \@v; # Référence de la variable 'array' @v $rh = \%v; # Référence de la variable 'hachage' %v $ra->[1]; # Accès à un élément du tableau référencé $rh->{"a"};# Accès à un élément du hachage
De plus, "" interprète les noms de variable dans les chaînes de caractères, alors que '' ne le fait pas.
print "moi@hebergeur.com\n"; #Erreur: @hebergeur est une variable vide... print 'toi@hebergeur.com\n'; #Erreur: @hebergeur tel quel, mais \n aussi. print "lui\@hebergeur.com\n";#Correct: @ non-interprété, \n interprété.
Contexte
Perl essaye de "deviner" ce que l'on veut faire. A cette fin, il prend en compte un "contexte" lors de l'évaluation des expressions.
En somme, le type de variable cible impose un "contexte" qui peut influer sur le résultat.
$vs = @va; #Contexte scalaire: taille du tableau @va @vb = @va; #Contexte array: copie du tableau @va @v = ( "a", "1", "b", "2" ); #Contexte tableau: $v[0]="a", $v[1]="1", ... %v = ( "a"=>"1", "b"=>"2" ); #Contexte hachage: $v{"a"}="1", $v{"b"}="2" %v = ( "a", "1", "b", "2" ); #Contexte hachage: idem!
Les fonctions et opérateurs utilisés sont également à choisir en fonction du type des données que l'on manipule (man perlop)
En particulier:
- Pour des nombres: == != < > <= >= <=>
- Pour des chaînes: eq ne lt gt le ge cmp
$s1="123"; $s2="123d"; if ( $s1 == $s2) { print "$s1 == $s2"; } #Compare des nombres: TRUE. if ( $s1 eq $s2) { print "$s1 eq $s2"; } #Compare des chaînes: FALSE.
Bind et variables par défaut
Un bon nombre de fonctions de Perl n’exigent pas d’argument: si elles n’en reçoivent pas, elles prennent la variable par défaut $_.
Pour en utiliser une autre, on recourt parfois aux opérateurs =~ et !~, dits de "bind".
### Variables par défaut et opérateurs de bind =~ !~ ### $s = "abcdef"; #Variable normale. $_ = "abcdef"; #$_ est la variable que PERL utilise par défaut. s/abc/ABC/; #Substitue "ABC" à "abc" dans $_ $s =~ s/abc/ABC; #Idem, mais dans la variable $s print; #Sans argument, affiche $_; if (m/abc/) { print; } #Affiche $_ si $_ contient "abc" if ($s !~ m/abc/) { print "KO\n"; } #Affiche KO si $s ne contient PAS "abc"
Flexibilité de la syntaxe
Pour faciliter la création des scripts, la syntaxe de PERL se veut très libre ("free form") en permettant de multiples façons d'exprimer un même test.
<si_vrai> if <condition>; #Ex: perl -e 'print "ok" if 1;' if (<condition>) { <si_vrai> } #Ex: perl -e 'if (1) {print "ok";}' <si_faux> unless <condition>; unless (<condition) { <si_faux> }
Expressions régulières
Les expressions régulières sont irremplaçables pour analyser du texte, et leur excellente intégration dans PERL est l'une des plus grandes forces du langage.
A quelques subtilités près, voici comment former une expression régulière (man perlre):
- * : répétition (0 fois ou plus)
- + : répétition (1 fois ou plus)
- {} : répétition {m,} pour au moins m fois, {,n} pour au plus n fois, {m,n} pour m à n fois
- ? : possibilité (1 fois au plus)
- [] : catégorie: [ab] pour a ou b, [^ab] pour ni "a ni b", [a-z] pour "une lettre minuscule"
- . : n'importe quel caractère
- ^ : début de chaîne
- $ : fin de chaîne
- \ : échappement (pour mettre un caractère littéral \( \) \[ \] \* \$ ...
- | : ou
- () : groupe :les chaînes reconnues sont mises dans les variables $1 $2 $3 etc, par ordre d'apparition de la parenthèse ouvrante.
- \Qxxxx\E: interprète xxxx tel quel: \Q désactive les caractères spéciaux des rexexp, et \E les rétablit (comme pour les chaînes entre deux ");
Les cas les plus courants concernent la reconnaissance (opérateur match m/expr/) et la substitution s/expr/remplacement/
#Reconnaissance grossière d'une adresse e-mail nom@hebergeur $sregex = '^([a-zA-Z0-9_.]+)@([a-zA-Z0-9_.]+)$'; #Remarque: ^...$ : début-fin #Reconnu: entre '', @domaine reste tel quel $chaine = 'utilisateur@domaine.extension'; if ( $chaine =~ m/$sregex/ ) { print "nom=$1; hebergeur=$2\n"; } #Non-reconnu: entre "", @domaine est interprété comme un tableau vide $chaine = "utilisateur2@domaine.extension"; if ( $chaine =~ m/$sregex/ ) { print "nom=$1; hebergeur=$2\n"; }
Syntaxe plus évoluée
Prototypes de fonctions
Les fonctions sont déclarées très simplement par sub:
- Les arguments sont transmis via le tableau @_.
- La valeur renvoyée est, par défaut, la dernière expression calculée.
sub f_double1 { my $d = 2*$_[0] ; return $d; } #Renvoie le double d'un nombre. sub f_double2 { my $d = 2*$_[0] ; } #Idem. sub f_add1 { my $x=$_[0]; my $y=$_[1]; $x+$y; } #Arguments lus dans @_: méthode 1. sub f_add2 { my $x=shift; my $y=shift; $x+$y; } #Arguments lus dans @_: méthode 2.
Par défaut, le nombre des arguments d'une fonction est libre. Pour spécifier un prototype, mettre un $ par argument attendu.
En voici quelques exemples.
sub fArgX; #Function avec nombre indéterminé d'args; sub fArg1($); #Fonction annonçant 1 argument formel exactement. sub fArg2($$); #Fonction annonçant 2 arguments formels exactement. fArgX("chose","machin"); #Prototypée: accepte tous arguments fArg1("x","y"); #Refusé: trop d'arguments pour $$ fArg2("x","y"); #Accepté: nombre d'args OK. sub fArgX($$) { print $_[0]+$_[1]; } #Accepté car proto indéterminé jusqu'ici. sub fArg1 { print $_[0]+$_[1]; } #Refusé: doit correspondre au prototype. sub fArg2($$) { print $_[0]+$_[1]; } #Accepté. fArgX("chose"); #Refusé car l'implémentation a fixé le prototype.
Structures complexes
Une référence n'est guère autre chose qu'un pointeur typé.
A l'instar du langage C, copier une référence ne copie pas l'objet pointé.
Là encore, le choix des délimiteurs [] ou {}, et celui des préfixes $, @ ou %, influe sur les objets créés ou consultés.
Voici la syntaxe (man perlref):
#Création: \variable $rs = \$vs; # référence un scalaire. $ra = \@va; # référence un tableau (array) $rh = \%vh; # référence un hachage (hash) $rf = \&f; # référence une fonction $rg = \*glob;# référence un symbole global (à approfondir) #Consultation: préfixe adapté au type, et {} autour de la référence. ${$rs}; # contenu de $vs, abrégeable en $$rs @{$ra}; # contenu de $va, abrégeable en @$ra %{$rh}; # contenu de $vh, abrégeable en %$rh &{$rf}(@args); # appel de la fonction f depuis sa référence $rf. #Consultation: autre syntaxe -> $ra->[0]; # Synonyme de @{$ra}[0] et de $$ra[0]; $rh->{"a"}; # Synonyme de %{$rh}[0] et de %$rh[0]; $rf->(@args); # Synonyme de &{$rf}(@args) #Création de références vers tableaux et hachages anonymes $ra = [ 1,2,3 ]; # référence au tableau (1,2,3) $rh = { k1 => v1, ... }; # référence au hachage ( k1 => v1, .... )
L'exemple qui suit utilise presque toutes ces syntaxes pour décrire les séances de cinéma dans une ville:
%programme = (); #Programme à remplir # -----Cinema "Rex" ----- $titre = "Avatar"; $raSeances = [ "20h30", "22h30" ]; %hRex = ( $titre => $raSeances ); # ----- Cinema "Louxor" ----- $raSeances1 = [ "19h30" ]; #Avatar $raSeances2 = [ "20h15", "22h30" ]; #RED $rhLouxor = { "Avatar" => $raSeances1, "RED" => $raSeances2 }; # ----- Séances dans toute la ville ----- $programme{"Rex"} = \%hRex; #Les () créaient un objet hash $programme{"Louxor"} = $rhLouxor; #Les {} créaient une référence hash # ----- Consultation des séances ----- for $cine ( keys %programme ) { print "cine=$cine\n"; $rhFilms = $programme{$cine}; for $film (keys %$rhFilms) { print " film=$film\n"; $raSeances = $rhFilms->{$film}; for $seance (@$raSeances) { print " seance=$seance\n"; } } }
Programmation objet
Le support dit objet de PERL (man perlobj) repose sur un bricolage peu élégant auquel on évitera soigneusement de penser par la suite:
- La syntaxe package::sous-package
- Une référence $r contient, en plus du type de l'objet, son 'référent' ref $r, i.e. par exemple le package ou le nom de classe dont l'objet référencé est censé faire partie.
- L'appel $r->fonction() exécute fonction() dans le package ref $r, avec l'argument $_[0]=$r.
- L'appel package->fonction() exécute package::fonction(), avec l'argument $_[0]=package.
- L'appel bless $reference,$nom décide que ref $référence renverra désormais $nom.
Voici comment cette «cuisine» fonctionne pour un exemple simple récolté ailleurs:
###### Package article ###### package article; # Nom du package: utilisé comme un nom de classe sub new { # Notre "constructeur" pourrait s'appeler n'importe comment. my($class) = shift; # La syntaxe article->new() met "article" en premier arg. my $rObjet = { # Créer d'abord une référence vers les données membres "description" => 0, "quantite" => 1 }; bless $rObjet, $class; # Ainsi 'ref $rObjet' renverra $class. #La fonction renvoie $rObjet } sub get_num { my $self = shift; # $objet->get_num() met $objet dans $_[0] return $self->{"quantite"}; } sub set_num { my $self = shift; # $objet->set_num() met $objet dans $_[0] my $qtt = shift; # Argument passé par l'utilisateur $self->{"quantite"} = $qtt; } ##### Utilisation de la 'classe' article ##### package main; $item = article->new(); #Référence de hash. Le référent est initialisé par new() print ref $item, "\n"; #affiche "article" $item->set_num(3); print $item->get_num(), "\n"; #Appelle get_num() dans 'ref $item', avec $_0=$item
Utilisation pratique
Cette section propose de brefs extraits de code réutilisables:
Un peu de rigueur
Deux lignes pour éviter bien des pertes de temps en débogage:
use strict; use warnings; $a = 1; #Refusé par 'strict': il faut déclarer $a avant my $b = 2; #Accepté: 'my' rend $b visible de tout le bloc courant.
Tester un type
Tester un type est rarement nécessaire. La fonction ref fera l'affaire (man perlref) pour dire vers quoi pointe une référence:
$s = "chaine"; $rs = \$s; # SCALAR $ra = []; # ARRAY $rh = {}; # HASH print ref $rs, "\n", ref $ra, "\n", ref $rh, "\n";
Pour une variable non-référence, le préfixe suffit en général, mais on peut utiliser encore ref en lui demandant vers quoi pointe la référence à cette variable:
$s = ""; @a = (); %h = {}; $rh = \%h; print ref \$s, "\n", ref \@a, "\n", ref \%h, "\n", ref \$rh, "\n";
Perl se refusant à distinguer chaînes et nombres, on calcule directemment avec, ou on teste sommairement si ce n'est pas nul, ou on appelle une fonction dédiée plus solide commne looks_like_number():
use Scalar::Util qw(looks_like_number); for my $s (qw(0 0.0 1.0 1e-9 nan AbC -inf -nan -nem INF naN)) { $v = $s+0; #Pour convertir une chaîne en nombre ($s != 0 || $s eq "0") or print "a) PAS un nombre?:", $s, "\n"; looks_like_number($s) or print "b) PAS un nombre?:", $s, "\n"; }
Le test a) utilise le fait que Perl compte tout nombre non-reconnu comme 0, mais il échoue pour 0.0;
Le test b) repose sur une fonction dédiée qui ne fait pas cette boulette;
Les deux tests comptent visiblement 'nan' et 'inf' comme des nombres, et semblent indifférents à la casse.
Tableaux
Les tableaux sont indexés à partir de 0; voir man perldata, section Subscripts et suivantes, pour beaucoup (trop) d’informations:
my @a = (); # Tableau vide push(@a, "1"); # Met un premier caractère my $taille=scalar(@a); # Les joies du contexte perl ... if (exists $a[1]) { print $a[1] ; } # my $premier = shift @a; # Décale les index et renvoie le premier élément ($x,$y) = @a[0,3] # Comme $x=$a[0] ; $y=$a[3] ; @sublist = @a[2..5] # Tableau ($a[2],$a[3],$a[4],$a[5])
Hachages
Un exemple minimal de XML::Parser, pour afficher des tags XML et leurs attributs. L'initialisation du parseur et le parcours des attributs d'un élément utilisent des hachages.
#!/bin/env perl use warnings; use strict; use XML::Parser; #Exemple minimal inspiré de http://docstore.mik.ua/orelly/xml/pxml/ch04_06.htm #Voir perldoc XML::Parser my $p1 = XML::Parser->new( #Voir perldoc XML::Parser pour tous les handlers possibles Handlers => { #référence vers un hachage de fonctions Start => \&handle_elem_start, End => \&handle_elem_end, }); #Analyse le fichier donné en argument, sinon stdin my $fichier = shift @ARGV; if ($fichier) { $p1->parsefile($fichier); } else { my $input = ""; while( <STDIN> ) { $input .= $_; } $p1->parse($input); } exit; #Implémentation des handlers annoncés à XML::Parser->new() sub handle_elem_start { my ($expat, $name, %atts) = @_; print $name,"\n"; for my $attname (keys %atts) { #Hachage nom=>valeur print " ",$attname," = ",$atts{$attname},";\n"; } } sub handle_elem_end { }
Fichiers
Fonctions de base: open, select, print, close
open fh,">","truc.txt"; #Création/ouverture print fh "Ligne 1 dans fh\n"; #Ecrit dans fh print "Ligne 1 dans STDOUT\n"; #Ecrit dans STDOUT par defaut select fh; #Change le filehandle par défaut print "Ligne 2 dans fh\n"; #Ecrit donc dans fh par défaut print STDOUT "Ligne 2 dans STDOUT\n"; #... et demande pour écrire STDOUT close fh; #Fermeture
Un exemple un peu plus évolué se trouve dans la section qui suit;
Arguments d’un programme
L’exemple qui suit lit un fichier nom/prénom puis le trie; Il reçoit en argument un nom de fichier d’entrée et un nom de fichier de sortie; puis il lit les lignes prenom <TAB> nom, avant de les récrire sous la forme nom <TAB> prenom en triant par nom;
#cf "Un peu de rigueur" use strict; use warnings; #cf "Variables prédéfinies" (man perlvar) my $nom_entree = $ARGV[0]; #Premier argument du script my $nom_sortie = $ARGV[1]; #Second argument du script print "Entree: $nom_entree\n"; print "Sortie: $nom_sortie\n"; #cf "Fichiers" open fh_entree,"<",$nom_entree || die "Impossible de lire $nom_entree\n"; my %hNomPrenom = (); #Hachage nom=>prenom, vide. while (<fh_entree>) { #cf "Variable par défaut" my $ligne = $_; #Ligne lue $ligne =~ s/[\n\r]*$//g; #Enlever saut et retour de ligne if ($ligne =~ m/(.*)\t(.*)/) { #Regex: nom, tabulation, prénom my $prenom = $1; #Premier groupe de parenthèses my $nom = $2; #Second groupe de parenthèses $hNomPrenom{$nom} = $prenom; #Associer nom/prenom } else { die "Ligne non-reconnue: $ligne\n"; } } close fh_entree; #Fini de lire: on peut fermer l'entrée my @aNoms = sort keys %hNomPrenom; #Trier les noms #Ouvrir/créer le fichier de sortie open fh_sortie,">",$nom_sortie || die "Impossible d'ouvrir $nom_sortie\n"; #Parcourir les noms dans l'ordre for my $nom (@aNoms) { my $prenom = $hNomPrenom{$nom}; #Prénom associé au nom my $chaine = $nom."\t".$prenom; #Ligne à écrire print fh_sortie $chaine, "\n"; #Ecriture dans le fichier print "STDOUT: $chaine\n"; #Vérification visuelle } close fh_sortie; #Fini! on peut fermer
Pour aller plus loin
Références locales
Documents disponibles sur la plupart des systèmes Linux:
- man perl : point d'entrée vers les autres docs locales;
- man perlcheat : référence concise de la syntaxe;
- perldoc XML::Parser : pour avoir la doc associée à un module
Inspirations
Documents de l’internet qui ont inspiré cet article:
- Perl in 2h30: l'essentiel de Perl pour ceux qui savent déjà programmer (et lire en anglais).
- Comparaison des opérateurs du C et du PERL.
- Discussion pour tester si une variable est un nombre
Références
Formations et ressources de référence:
- CPAN: modules PERL prêts à l'emploi.
- Perldoc.perl.org: la doc officielle, si vous avez du temps à perdre (ou un détail très pointu à creuser)
- Formation pédagogique en français
Dernière modification: 28 avril 2018