site de Fabien Torre


Notes de cours sur le langage Perl

Introduction à la programmation en Perl pour le traitement automatique des textes : syntaxe générale, gestion des fichiers, expressions régulières, tableaux associatifs, etc.

Dans ce cours, nous passons en revue les éléments syntaxiques du langage Perl en parallèle avec le langage de description vu en cours d'algorithmique (variables, types, structures de contrôle, procédures et fonctions, etc.). En particulier quand Perl propose plusieurs syntaxes à une même fin, nous choisissons systématiquement la plus rigoureuse, la moins ambigüe, bref, la plus proche du cours d'algorithmique.

Puis nous nous intéressons à l'utilisation de Perl pour le traitements des textes, structurés ou non (lecture/écriture de fichiers, opérations sur les chaînes de caractères, expressions régulières, production de code HTML ou du code LaTeX, etc.). Dans la partie travaux pratiques, nous nous intéressons en particulier aux applications en linguistique, sociologie, open data, visualisation de données, etc.

Le détail des fonctions évoquées ici peut être obtenu à l'aide de la commande perldoc -f.

Instructions, expressions, variables et types Perl

Programmes, blocs d'instructions et instructions

Un programme Perl débute par les lignes suivantes :

#!/usr/bin/perl

use strict;
use warnings;
use diagnostics;

use utf8;

binmode(STDOUT,":utf8");
binmode(STDERR,":utf8");

# puis des procédures et fonctions

# puis des appels aux procédures et fonctions

Commentaires sur ce premier code perl :

  • La première ligne précise où se trouve l'interpréteur perl qui va traiter la suite du programme (le chemin indiqué ici est valable sur les systèmes Linux, le fichier pourra être rendu exécutable et appelé dans un terminal).
  • La deuxième indication stipule que nous utilisons perl en mode strict, ce qui oblige par exemple à déclarer préalablement les variables utilisées.
  • Ensuite nous demandons à perl de signaler les éventuels warnings et de nous donner des diagnostics clairs en cas de problème.
  • Les deux lignes suivantes permettent de travailler en utf8 : dans le fichier perl et sur la sortie standard.
  • Notons également le caractère # qui permet d'introduire un commentaire que Perl ignorera.

En Perl, les blocs d'instructions sont délimités par des accolades  { représente un Début de bloc, } une Fin.

Chaque instruction est impérativement suivie d'un point-virgule (;).

Types des variables, variables, assignation

Il existe en Perl trois types de données  les types scalaires, les tableaux classiques et les tableaux associatifs.

Les types scalaires regroupent les types de base vus en cours d'algorithmique  entiers, réels, caractères et chaîne de caractères, etc.

À propos des booléens en perl : 0 ou '' ou () sont faux, le reste est vrai.

En Perl, toutes les variables commencent par un signe particulier, en fonction de leurs types 

  • $ pour les scalaires,
  • @ pour les tableaux classiques,
  • % pour les tableaux associatifs.

Plusieurs notions d'égalité sont à distinguer :

  • l'affectation, symbole =,
  • le test d'égalité entre nombres, symbole ==,
  • le test d'égalité entre chaînes de caractères avec l'opérateur eq.

Lors de la première utilisation d'une variable, nous utiliserons le mot-clef my en guise de déclaration de variable (mais pas de déclaration de type en Perl). Nous pouvons donc écrire par exemple 

my $w;
my $x = 2;
my $y = 3.14;
my $z = 'toto';

Opérateurs en Perl

Nous disposons en Perl des opérateurs arithmétiques classiques : +, -, *, / et de l'opérateur de concaténation entre chaînes de caractères noté par un point.

my $i = 10;
$i    = $i + 1;
$i    = $i + 9;
my $texte = 'La variable i vaut '.$i;
$texte    = $texte."\n";

Remarquons ici que Perl n'a pas de contrôle fort sur les types de variables  une même variable peut subir une opération arithmétique puis une opération propre aux chaînes de caractères sans provoquer d'erreur. Au final, la variable $texte contient donc La variable i vaut 20\n.

Il existe d'autres manières souvent pratiques d'exprimer ces opérations. Par exemple, les opérations ci-dessus sont strictement équivalentes à 

my $i = 10;
$i++;
$i += 9;
my $texte  = 'La variable i vaut '.$i;
$texte .= "\n";

Enfin, Perl propose les habituels

  • opérateurs booléens : &&, ||, !,
  • comparateurs entre entiers : ==, !=, >=, <=,, >, <,
  • comparateurs entre chaînes de caractères : eq, ne, lt, gt, le, ge.

Instruction de sortie et chaînes de caractères Perl

L'affichage est réalisé à l'aide l'instruction print. Cette instruction est particulièrement importante  c'est elle qui va produire le résultat final.

En Perl, les chaînes de caractères sont délimitées soit par des apostrophes, soit par des guillemets. Dans le premier cas, le contenu de la chaîne n'est pas interprété par Perl, dans le second une variable apparaissant dans la chaîne est remplacée par sa valeur.

my $t = 'coucou';
my $v = 5;
print '$v fois $t\n';
print "$v fois $t\n";

Dans cet exemple, le premier affichage produira $v fois $t\n, le second lui affichera 5 fois coucou avec un passage à la ligne ensuite (codé par \n).

Fonctions et procédures en Perl

Définition de fonctions Perl

sub nom_de_la_fonction (paramètres) {
  # des instructions ici
}

Les paramètres ne sont pas précisés par des noms de variables, mais par le symbole de leur type  $, @ ou %. Les valeurs sont transmises dans un tableau noté @_ dont nous prendrons soin d'immédiatement extraire et nommer les valeurs. Par exemple 

sub nom_de_la_fonction ($$) {

  my ($a,$b) = @_;

  # des instructions ici
}

Structures de contrôle en Perl

Modulo les conventions Perl, nous retrouvons les structures de contrôle habituelles. Le si alors sinon 

if (une condition ici) {
  # des instructions ici
} else {
  # des instructions ici
}

autres syntaxes :

# une instruction ici if (une condition ici);
# une instruction ici unless (une condition ici);

La boucle tant que :

while (une condition ici) {
  # des instructions ici
}

Et la boucle pour :

for (my $i=0 ; $i<10 ; $i++) {
  # des instructions ici
}

Un dernier type de boucle existe en Perl mais est dédié au parcours de tableaux.

Structures de données Perl : les tableaux classiques

Dans cette sestion, les tableaux dits classiques : pas de contrainte sur le contenu des cases, celles-ci sont numérotées à partir de 0, et les noms des variables de ce type débutent par @.

Manipulation des tableaux classiques Perl

Tout d'abord l'affectation de valeurs aux cases des tableaux.

# définition d'un tableau vide et remplissage des cases
my @t = ();
$t[0] = 3.14;
$t[1] = 'coucou';
$t[2] = 10;

# syntaxe équivalente
my @t = (3.14,'coucou',10);

# ajout d'un élément en fin
push(@t,2013);

À noter que la taille d'un tableau (c'est-à-dire son nombre de cases) est fournie par la fonction scalar.

# affichage de la taille d'un tableau
print scalar(@t);

Pour obtenir le contenu d'une case choisie au hasard, nous utilisons la fonction rand :

my @t = (3.14,'coucou',10);
print $t[int(rand(scalar(@t)))];

Nous pouvons ordonner les cases d'un tableau avec l'instruction sort.

Parcours de tableaux Perl : la boucle foreach

Enfin, la boucle foreach est dédiée au parcours de tableaux :

foreach my $val (@tab) {
  # des instructions ici portant sur $val
}

Opérations sur les chaînes de caractères et expressions régulières

Basiques sur les chaînes de caractères

Nous rappelons les comparateurs sur les chaînes de caractères :

  • eq,
  • ne,
  • lt,
  • gt,
  • le,
  • ge.

La précision use locale indique d'utiliser les paramètres régionaux par exemple pour comparer alphabétiquement deux mots, sans se tromper sur les mots accentués.

Ci-dessous les principales opérations sur les chaînes de caractères.

  • length : obtenir la longueur,
  • symbole . : concaténer deux chaînes de caractères,
  • index et rindex : rechercher la position d'une sous-chaîne,
  • substr : extraire et substituer,
  • chop : enlever le dernier caractère d'une chaîne,
  • chomp : enlever l'éventuel caractère de passage à la ligne en fin de chaîne.

Expressions régulières

Caractères spéciaux :

début ^
fin $
caractère quelconque .
un espace \s pas un espace \S
un chiffre \d pas un chiffre \D
une lettre \w pas une lettre \W
une ponctuation [:punct:]
une voyelle [aeiouy] pas une voyelle [^aeiouy]

Quantifieurs :

zéro ou une fois ?
zéro ou plusieurs fois *
une ou plusieurs fois +

Exemple :

  • commence par un S
  • suivi par une voyelle
  • puis par au moins une lettre
  • au moins un espace
  • éventuellement des chiffres pour finir
^{}S[aeiouy]\w+\s+\d*$

Opérateurs sur les expressions régulières

Opérateur de matching avec =~ // :

     if ($verbe =~ /^[aeiou]/) {
       ...
     }

Insensibilité à la casse avec le modifieur i

     if ($verbe =~ /^[aeiou]/i) {
       ...
     }

Matching et extraction, parenthèses et variables numérotées :

     $verbe =~ /(..)$/;
     $terminaison = $1;

Opérateur de substitution, substitution avec =~ s/// :

     $verbe =~ s/er$/ons/;

Toutes les substitutions avec le modifieur g :

     	$description =~ s/\. /.\n/g;

Opérateur de découpage, découpage avec split :

     my ($num,$nom,$texte) = split(/,/,$ligne);
     my @mots              = split(/\s+/,$ligne);

Gestion des flux et des fichiers

Les instructions perl relatives aux fichiers et aux flux :

  • open : ouvrir un fichier et l'associer à un flux,
  • close : fermer le flux et l'accès au fichier,
  • binmode : spécifier l'encodage du flux,
  • opérateur <FLUX> : récupérer une ligne dans le flux.

Écriture d'un fichier :

open(OUT,">sortie.html");
binmode(OUT,":utf8");
print OUT "<p>coucou !</p>\n";
print OUT "<p>ça va ?</p>\n";
close(OUT);

Lecture d'un fichier :

open(IN,"dico.txt");
binmode(IN,":utf8");
while (my $ligne = <IN>) {
  chop($ligne)
  # traitements de la $ligne
}
close(IN);

Structures de données Perl : les tableaux associatifs

Après les tableaux classiques, nous nous intéressons aux tableaux dits associatifs. Ceux-ci ont la particularité que les cases ne sont plus indicées par un numéro mais par un texte. Les noms des variables de ce type débutent par %.

Vocabulaire, syntaxe et fonctions des tableaux associatifs

Selon le langage de programmation ou le contexte, ce que nous appelons tableau associatif peut être dénommé dictionnaire, table de hachage ou encore en anglais hash. Dans tous les cas, cela désigne une structure de données dans laquelle nous accédons très rapidement au contenu d'une case à partir de son nom.

Les textes qui indicent les cases sont appelés clefs et les contenus des cases valeurs.

Les noms des tableaux associatifs commencent donc par le symbole % et les noms des cases sont encadrés par des accolades : { et }. Sur l'exemple suivant, nous définissons un tableau associatif à vide, puis nous associons noms de personnes et adresses mails :

    my %mail = ();
    
    $mail{'torre'} = 'torre@univ.fr';
    $mail{'toto'}  = 'toto.machin@free.fr';

    print 'Mail de toto = ',$mail{'toto'},"\n";

Une autre syntaxe utilisant les symboles => permet de créer le tableau associatif et en même temps de le remplir :

    my %mail = (
               'torre' => 'torre@univ.fr',
               'toto'  => 'toto.machin@free.fr'
    );
    
    print 'Mail de toto = ',$mail{'toto'},"\n";

Les fonctions utiles sur ces tableaux :

  • keys récupère les clefs d'un tableau associatif et les place dans un tableau classique,
  • values récupère de la même manière les valeurs du tableau associatif,
  • exists teste si une clef existe dans le tableau associatif,
  • delete efface une case,
  • scalar n'est pas utile directement sur ce type de tableaux pour connaître mais pour cela nous appellerons scalar sur le résultat de keys.

Voici quelques exemples de mises en œuvre de ces opérations :

    print scalar(keys %mails)," personnes dans notre carnet.\n";
    
    my @personnes = keys %mail;  # récupération des clefs du tableau %mail
    
    if (exists($mail{'titi'})) { # nous testons l'existence d'une case
        print "on connaît titi et on a son mail.\n";
    } else {
        print "titi est inconnu.\n";
    }

Boucles pour le parcours des tableaux associatifs

Pour parcourir un tableau associatif, nous pouvons utiliser de la boucle foreach sur les clefs (keys) du tableau :

foreach my $clef (keys %tab) {
  # des instructions ici utilisant $clef et $tab{$clef}
}

Alternativement, il est possible de conjuguer la boucle while avec la fonction each :

while (my ($clef,$val) = each %tab) {
  # des instructions ici utilisant $clef et $tab{$clef}
}

Sortie triée des éléments d'un tableau associatif

La situation la plus simple est celle où l'on souhaite afficher les éléments du tableau associatif par ordre alphabétique des clefs. Il suffit alors d'appliquer sort aux clefs :

    foreach my $clef (sort(keys %tab) {
       # des instructions ici utilisant $clef et $tab{$clef}
    }

Cas plus complexe : nous voulons ordonner selon les valeurs et non plus sur les clefs. Dans ce cas, il faut expliciter comment sort doit comparer deux valeurs $a et $b :

    foreach my $nom (sort { $mail{$a} cmp $mail{$b} } (keys %mail) {
       # des instructions ici utilisant $nom et $mail{$nom}
    }
      
    foreach my $nom (sort { $age{$a} <=> $age{$b} } (keys %age) {
       # des instructions ici utilisant $nom et $age{$nom}
    }

cmp et <=> correspondent respectivement à la comparaison entre chaînes de caractères et à la comparaison entre nombres. Dans le premier cas, nous parcourons donc les personnes classées par ordre alphabétique de leurs mails, dans le second par ordre croissant de leurs âges.

Pour plus de lisibilité, nous pouvons isoler la comparaison des deux éléments dans une fonction et, pourquoi pas, stocker dans un tableau classique les clefs ordonnées selon leurs valeurs associées.

    sub parage {
      $age{$a} <=> $age{$b};
    }

    my @personnes_classées = sort parage (keys %age);

Utilisations de tableaux associatifs en perl

Nous avons vu comment un tableau associatif pouvait stocker une information particulière sur des personnes (âge ou mail par exemple). Il est aussi possible d'y ranger plusieurs informations sur une même personne :

    my %personne = (
                    'pseudo'  => 'Toto',
                    'mail'    => 'toto.machin@free.fr',
                    'age'     => '21',
                    'adresse' => '42 rue du Perl',
                    'ville'   => 'Lille',
    );

Cependant, dans le cadre de nos applications au traitement automatique de la langue, nous allons détourner ces tableaux à d'autres fins.

Tester l'existence d'un mot dans un dictionnaire

Il s'agit par exemple de faire de la vérification orthographique. Dans ce cas, nous avons besoin d'un dictionnaire sous la forme d'une simple liste de mots. Si celle-ci est stockée dans un fichier, il suffit de la lire et de la charger dans un tableau associatif :

    my %dictionnaire = ();

    open(DICO,'dictionnaire.txt');
    binmode(DICO,":utf8");

    while (my $mot = <DICO>) {
        chop $mot;
        $dictionnaire{$mot} = 1;
    }
      
    close(DICO);

Ici, les valeurs du tableau n'ont pas d'importance (nous avons mis toutes les cases à 1) puisque nous avons juste besoin de tester l'existence des clefs (qui sont les mots du dictionnaire) :

      if (exists($dictionnaire{$mot})) {
	  print "« $mot » est dans le dictionnaire.\n";
      } else {
	  print "« $mot » est inconnu.\n";
      }

Compter les occurrences d'un mot

Cette fois nous voulons avoir un compteur associé à chaque mot. Nous utilisons un tableau associatif avec les mots en clefs et les compteurs comme valeurs. À la rencontre d'un mot, nous allons mettre à jour son compteur comme suit :

if (exists($comptage{$mot})) {
  $comptage{$mot} = $comptage{$mot} + 1;
} else {
  $comptage{$mot} = 1;
}

Autrement dit : si le mot est déjà connu nous ajoutons 1 à son compteur et sinon nous notons que le mot a été rencontré une première fois.

Autres utilisations possibles des tableaux associatifs

Citons par exemple :

  • compter des cooccurrences de mots,
  • trouver les anagrammes d'un mot ou les mots possibles à partir d'un tirage au Scrabble,
  • constituer un index qui à partir d'un mot donne les numéros des pages d'un livre dans lesquelles le mot apparaît.

Ces pistes nous obligent à augmenter encore nos structures de données car elles nécessitent des tableaux de tableaux. Cela implique de maîtriser les références en perl, c'est le sujet abordé à la section suivante.

Références en Perl

Retour sur les fonctions perl

Profils, passage, portée. En Perl les paramètres sont passés par valeur, c'est-à-dire que les procédures travaillent sur une copie des objets passés en paramètres.

Ce que reçoit une fonction est forcémment un tableau. Nous l'avons vu quand nous voulions simplement passer une unique valeur scalaire... Autre question : comment passer plusieurs tableaux ? Exemple : procédure qui reçoit deux tableaux et dit lequel est le plus grand. Exemple : expliciter la fusion des tableaux.

C'est pourquoi perl interdit d'avoir un profil @@... nous pourrions renoncer aux profils, mais cela ne change pas réellement le problème.

Idem pour les valeurs de retour des fonctions perl : comment retourner plusieurs tableaux ? Exemple : fonction qui partitionne une liste de lettres ?

Autre problématique : si les objets donnés à une procédure sont susceptibles d'être modifiés et si nous souhaitons que ces modifications soient visibles de l'extérieur de la procédure, le passage doit se faire par référence.

Solutions avec les références

notion de référence = une adresse = un entier = un scalaire

obtenir une référence selon l'objet

\$, \@ ou \%

ou à la création avec [] ou {}

déréférencer selon l'objet pointé

$$, @$ ou %$

-> pour les tableaux pointés

déréférencement des tableaux de tableaux, pourquoi les accolades...

Intérêts et applications des références

  • passage de plusieurs tableaux en paramètres
  • modification des paramètres dans une procédure
  • retour de plusieurs tableaux en résultats
  • structures de données complexes

Tableaux de tableaux en Perl

Construction d'un tableau de tableaux en perl

Repartons d'un tableau associatif habituel...

  my %dictionnaire = (

    ...
  
    'aegmnr' => 'manger';

    ...

  );

... qui devient une tableau associatif contenant des tableaux :

  my %dictionnaire = (

    ...

    'aegmnr' => [ 'gramen' , 'magner' , 'manger' ],

    ...

  );

Autre exemple d'un tableau associatif de tableaux :

  @contraintes = (

     [ 'e' , 2 ],
     [ 'x' , 5 ],

  );

Exemple d'un tableau de tableaux associatifs :

  my @dictionnaire = (

    ...

    { 'mot'=>'manger' , 'definition'=>'prendre un repas' }

   ...

  );

Tableaux de tableaux : accès et modification

Sur des exemples :

  my $lettre = $contraintes[1][0];

  my $mot = $dictionnaire{'aegmnr'}[2];

  my @mots = @{ $dictionnaire{'aegmnr'} }

  my %description = %{ $dictionnaire[12] };

  print $description[12]{'definition'};

Fabien Torre Valid HTML5! Valid CSS!
Accueil > Enseignement > Cours > Programmation > Perl
(contenu mis à jour )
site de Fabien Torre, université de Lille

Description

Survoler un lien de navigation pour lire sa description ici...


Une photo au hasard

Voyage à Hong-Kong.

Ile de Lantau.

(le 22 décembre 2006)

Poissons mis à sécher dans la rue.