PERL: on ne nous dit pas tout ...

Retour à la page Systèmes

Motivation

L'abondante documentation de PERL pêche par son manque de concision.
Cette page rassemble des informations que l'auteur aurait aimé découvrir en premier:
c'est un résumé des pièges et de la syntaxe pour des programmeurs C/C++ qui souhaiteraient découvrir PERL.
Pour plus de précision, voir les liens en fin de page, ou les pages man indiquées au fur et à mesure.

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
Remarque: tableaux et hachages ne peuvent contenir que des types scalaires.
La création de structures imbriquées se fait alors à l'aide de références. Certaines variables n'ont aucun préfixe: les descripteurs de fichiers sont les plus connues.

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
Pourquoi utiliser le préfixe $ pour lire dans des variables de type @ ou %?
Sans doute parce que le résultat est de type scalaire ...

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:

Références

Formations et ressources de référence:

Dernière modification: 28 avril 2018