Nous poursuivons ici notre série d'articles sur la programmation en Perl. Après avoir passé en revue les principes de base du langage (scalaires, tableaux, structures de contrôle), nous allons aborder des notions qui constituent une partie de la puissance de Perl : les tables de hachage et les fichiers.
Les tables de hachage de Perl ne se retrouvent pas dans beaucoup d'autres langages ; pour les avoir souvent utilisées en Perl, il est dur de repasser à des langages qui n'en sont pas pourvus. Les fichiers, quant à eux, se retrouvent dans tous les langages, mais la manière très simple et très puissante de les manipuler font des fichiers une facilité de Perl.
Une table de hachage (hash table en anglais) est un type de donnée en Perl permettant d'associer une valeur à une clef. On peut dire d'un tableau (notion abordée la dernière fois) qu'il associe une valeur scalaire à un entier : à la position i (pour i entier), une certaine valeur scalaire est présente. Une table de hachage va nous permettre d'aller au-delà : on pourra faire correspondre une valeur scalaire (comme pour un tableau) à toute chaîne de caractères (plutôt qu'à un entier).
Je peux, par exemple, avoir envie de gérer en Perl un index téléphonique simple : chacun de mes amis a un numéro de téléphone, je veux pouvoir retrouver leur numéro à partir de leur prénom. Je vais donc associer le numéro au prénom :
"Paul" -> "01.23.45.67.89" "Virginie" -> "06.06.06.06.06" "Pierre" -> "heu ..."
Les prénoms seront les clefs, c'est-à-dire le "point d'entrée" dans la table de hachage (comme les indices numéraux le sont pour les tableaux). Les numéros de téléphone seront les valeurs associées à ces clefs. Il s'agit bien d'une association chaîne de caractères -> scalaire.
Vous l'avez sans doute compris, dans une table de hachage, une clef n'est présente qu'une seule fois et ne peut donc avoir qu'une seule valeur (comme l'élément d'un indice donné d'un tableau). Par contre, une valeur peut être associée à plusieurs clefs.
Une variable de type table de hachage se déclare de la sorte :
my %h;
On a alors une table de hachage vide (aucune clef). Il est possible de signaler explicitement que l'on déclare une table de hachage vide :
my %h = ();
Pour donner des valeurs initiales à notre table de hachage, on peut utiliser la syntaxe suivante :
my %h = ( "Paul" => "01.23.45.67.89", "Virginie" => "06.06.06.06.06", "Pierre" => "heu ..." );
Cette dernière table de hachage est déclarée et initialisée avec
les clefs Paul
, Virginie
et
Pierre
ayant respectivement pour valeurs
01.23.45.67.89
, 06.06.06.06.06
et heu ...
Dans une table de hachage %h
, on peut accéder à la
valeur d'une clef au moyen de la syntaxe suivante :
$h{
clef}
;
par exemple $h{Paul}
vaut 01.23.45.67.89
.
Si la clef comporte d'autres caractères que des lettres, des chiffres et
le souligné (underscore en anglais '_'), il faut la délimiter au moyen
de simples ou de doubles quotes : $h{"Marie-Pierre"}
ou $h{'Marie-Pierre'}
En fait, cette syntaxe force un contexte de chaîne de caractères entre
les accolades, ce qui fait qu'un mot simple (bareword
en anglais) sera converti silencieusement en chaîne de caractères
(même en positionnant l'option -w
).
De façon similaire aux tableaux avec l'arobase (@t
),
la totalité d'une table de hachage se représente au moyen du signe
pourcentage (%h
), alors qu'une valeur particulière est
désignée à l'aide d'un dollar
$h{
clef}
,
cette dernière expression étant bien une variable de type scalaire.
Voici quelques exemples de manipulation d'éléments de la table de hachage
%h
:
$h{Jacques} = "02.02.02.02.02"; print "Tél : $h{Jacques}\n"; $h{'Jean-Paul'} = "03.03.03.03.03"; if( $h{"Jean-Paul"} ne "Heu ..." ) { ... }
La clef utilisée pour cette syntaxe peut tout à fait être contenue dans une variable scalaire (qui sera évaluée en contexte de chaîne de caractères) :
my $k = "Jacques"; $h{$k} = "02.02.02.02.02";
Elle peut même être une expression plus complexe :
sub f { return "Jac"; } $h{f().'ques'} = "02.02.02.02.02";
Il existe trois fonctions permettant de parcourir une table de hachage.
Dans les exemples fournis, nous considérerons que la table %h
a été déclarée ainsi :
my %h = ( "Paul" => "01.23.45.67.89", "Virginie" => "06.06.06.06.06", "Pierre" => "heu ..." );
keys
: obtenir une liste des clefs
Cette fonction prend en paramètre une table de hachage et renvoie une liste comportant toutes les clefs de la table. L'ordre des clefs est quelconque, seule l'exhaustivité des clefs est garantie.
my @t = keys(%h);
Le tableau @t
peut par exemple valoir
la liste ("Virginie","Pierre","Paul")
.
Cette fonction va nous permettre de parcourir toute la table de hachage en effectuant une boucle sur la liste des clefs :
foreach my $k (keys(%h)) { print "Clef=$k Valeur=$h{$k}\n"; }
La variable de boucle $k
prendra pour valeurs
successives l'ensemble des clefs de la table, l'expression
$h{$k}
est la valeur associée à la clef
$k
.
Ce petit programme affichera donc tous les couples clef/valeur
de la table %h
.
values
: obtenir une liste des valeurs
De la même façon que keys
renvoie une liste
des clefs d'une table de hachage, la fonction values
fournit une liste des valeurs ; pour cette fonction non plus
l'ordre n'est pas garanti et seule l'exhaustivité l'est.
L'exemple suivant
foreach my $v (values(%h)) { print "Valeur=$v\n"; }
affichera tous les numéros de téléphone (c'est-à-dire les valeurs) de la table
%h
.
Il n'est bien sûr pas possible de retrouver la clef des valeurs que l'on manipule ainsi.
Il peut être intéressant de savoir que l'ordre des clefs renvoyées par
keys
et celui des valeurs par values
sera le même à condition de ne pas modifier la table de hachage
entre temps.
each
: itération sur les couples (clef,valeur)
Cette fonction renvoie un à un tous les couples (clef,valeur) d'une table de hachage. Elle a un comportement un peu spécial du fait qu'il faut l'appeler autant de fois qu'il y a de couples : c'est une fonction avec état, c'est-à-dire qu'elle ne renvoie pas toujours la même chose d'un appel à l'autre : en effet, elle renvoie le couple suivant ! De ce fait, je vous conseille de toujours l'utiliser dans la syntaxe qui suit :
while( my ($k,$v) = each(%h) ) { print "Clef=$k Valeur=$v\n"; }
Libre à vous de parcourir vos tables de hachage avec la fonction qui vous convient le mieux.
Sous ce terme barbare se cache une idée simple : si vous tentez de modifier un élément d'une table de hachage qui n'existe pas, il sera créé. S'il est utilisé dans un contexte numérique, il prendra pour valeur initiale zéro. S'il est utilisé dans un contexte de chaîne de caractères, il prendra pour valeur la chaîne vide (depuis Perl 5.6).
Par exemple, considérons une table de hachage qui ne comporte pas la clef
hello
; l'expression suivante
$h{hello} .= "après";
associe à la clef hello
la valeur chaîne vide
puis lui concatène la chaîne "après"
.
De la même façon, l'expression
$h{bye}++;
crée un élément de valeur 1.
Cette propriété d'autovivification est bien pratique dans le cas où l'on
ne connaît pas les clefs avant de devoir y accéder. Par exemple nous allons
pouvoir compter le nombre d'occurrences des mots dans un texte de manière
très simple. Supposons que les mots du texte soient déjà dans un tableau
(par exemple en utilisant la fonction qw
; elle
ne règle pas les problèmes des ponctuations, des majuscules et des lettres
accentuées, mais elle suffira à notre exemple).
Nous allons utiliser chaque mot comme une clef de la table
et nous allons ajouter 1 à la valeur de cette clef :
my @texte = qw( bonjour vous bonjour ); my %comptage = (); foreach my $mot ( @texte ) { $comptage{$mot}++; } while( my ($k,$v) = each(%comptage) ) { print "Le mot '$k' est présent $v fois\n"; }
Ce qui donne l'affichage suivant :
Le mot 'vous' est présent 1 fois Le mot 'bonjour' est présent 2 fois
À la lecture de ce qui précède, il peut sembler impossible de savoir si
un élément d'une table de hachage existe ou non. Rassurez-vous,
les auteurs de Perl ont tout prévu :-)
L'opérateur exists
renvoie vrai si l'élément de table
de hachage qu'on
lui donne en paramètre existe ; sinon il renvoie faux. Par exemple :
if( exists( $h{hello} ) ) { print "La clef 'hello' existe\n"; }
Il est important de noter qu'un test effectué au moyen de l'opérateur
defined
aurait été possible, mais dangereux.
En effet, l'expression defined($h{hello})
est fausse dans deux cas très différents : soit si l'élément n'existe pas,
soit si l'élément existe et vaut undef
;
elle sera vraie si l'élément existe et ne vaut pas undef
.
Il est donc impossible de distinguer le cas d'un élément absent
et celui d'un élément indéfini (valant undef
)
avec defined
.
Cette distinction entre absent et indéfini peut paraître artificielle dans ce cas (elle peut tout de même être importante dans certaines situations !), mais dans le cas de la suppression d'une clef, il en est tout autrement.
Pour supprimer une clef dans une table de hachage, il faut utiliser
l'opérateur delete
. L'instruction
delete( $h{hello} );
supprime la clef hello
de la table %h
si elle existe (si elle n'existe pas, elle ne fait rien).
De la même façon que exists
est la bonne méthode
pour tester l'existence d'un élément, delete
est la bonne méthode pour en supprimer un. Le débutant pourrait être
tenté d'écrire :
$h{hello} = undef; # attention!
Ce qui est fort différent car, dans ce cas, la clef hello
aura une valeur indéfinie, mais existera toujours ! On la
retrouvera, par exemple, dans les parcours effectués au moyen des
opérateurs keys
, values
ou
each
; ce qui n'est sans doute pas le but recherché.
Pour résumer, on peut dire que pour tester l'existence
d'une clef, il faut utiliser exists
et que pour en supprimer une, il faut utiliser delete
.
En marge de ces deux fonctions, voici une manière de savoir si une table de hachage est vide ou non (on qualifie de vide une table de hachage qui ne comporte aucune clef). Cette syntaxe utilise la table de hachage en contexte de chaîne de caractères, par exemple de cette façon :
if( %h eq 0 ) { print "%h est vide\n"; }
La valeur d'un hachage en contexte scalaire n'a pas d'autre utilisation
que celle-ci. En effet, scalar(%A)
renvoie une valeur
du type 4/8
qui indique le nombre de places
(buckets en anglais) utilisées par rapport au nombre total
disponibles dans le hachage.
Une table vide est une cas particulier, elle renverra 0.
On peut facilement passer d'une liste (ou tableau) à une table de hachage et inversement. Voyez, par exemple, le petit programme suivant :
my @t = ("Paul", "01.23.45.67.89", "Virginie", "06.06.06.06.06", "Pierre", "heu ..."); my %h = @t;
La première instruction crée un tableau @t
initialisé à une liste à 6 éléments. La seconde crée une table
de hachage %h
initialisée au moyen du précédent
tableau. Les valeurs du tableau sont prises deux à deux :
la première de chaque couple sera la clef dans la table de hachage,
la seconde la valeur.
Si le nombre d'éléments de la liste est impair, la dernière clef
créée aura undef
pour valeur.
Si une clef venait à être présente plusieurs fois dans la liste,
c'est la dernière valeur qui sera prise en compte dans la table
de hachage. On aurait aussi pu écrire :
my %h = ("Paul", "01.23.45.67.89", "Virginie", "06.06.06.06.06", "Pierre", "heu ...");
Il est à noter que cette syntaxe rappelle étrangement l'un des premiers
exemple de création de table de hachage qui utilisait =>
pour séparer clefs et valeurs. Cette similarité est en fait une quasi équivalence,
car l'opérateur =>
peut être utilisé à la place
de la virgule pour créer des listes ; il n'a été ajouté au langage Perl
que pour faciliter la lecture des affectations de tables de hachage,
car il force un contexte de chaîne à sa gauche, ce qui permet
justement d'écrire %a = ( toto => 'titi' );
La conversion dans l'autre sens est aussi possible.
L'évaluation d'une table de hachage dans un contexte de liste
renvoie une liste des clefs et des valeurs, se suivant respectivement deux à deux,
dans un ordre quelconque entre couples. La table de hachage %h
de l'exemple précédent peut être affectée à un tableau :
my @t2 = %h;
Le tableau $t2
sera initialisé, par exemple,
avec la liste suivante : ("Pierre", "heu ...", "Paul", "01.23.45.67.89",
"Virginie", "06.06.06.06.06")
; chaque clef précède sa valeur,
mais l'ordre des couples (clef,valeur) est quelconque (un peu comme pour la fonction
each
).
Une table de hachage se convertit en liste sans encombre dès qu'elle est en contexte de liste. Je vous laisse deviner ce que fait le code suivant :
foreach my $x (%h) { print "$x\n"; }
La fonction reverse
, qui nous a permis d'inverser
les listes, peut être employée pour inverser une table de hachage :
%h = reverse(%h);
Les valeurs deviennent les clefs et inversement. Si plusieurs valeurs identiques sont présentes le comportement est imprévisible car, certes, lors de la transformation de liste en table de hachage la dernière valeur compte, mais lors de la transformation de table de hachage en liste l'ordre est quelconque ...
L'association individu - numéro de téléphone est idéale pour illustrer cela :
my %h = ("Paul", "01.23.45.67.89", "Virginie", "06.06.06.06.06", "Pierre", "heu ..."); my %quidonc = reverse %h;
On pourra alors retrouver la personne à partir de son numéro de téléphone. Si, par contre, Paul et Virginie avaient eu le même numéro, on ne aurait pas pu prédire quel serait la personne renvoyée.
Voici quelques exemples d'utilisation des tables de hachage.
Le premier concerne la variable spéciale %ENV
qui contient les variables d'environnement du programme.
$ENV{PATH}
contient le path,
$ENV{HOME}
vaut le nom du répertoire personnel
de l'utilisateur qui exécute le programme, etc.
Deuxième exemple, les tables de hachage peuvent servir à constituer des tableaux à plusieurs dimensions ; on pourrait en effet imaginer avoir des clefs qui seraient la concaténation des coordonnées dans les n dimensions : dim1:dim2:dim3:...
my %h = (); foreach my $i (0..4) { foreach my $j (-3..10) { foreach my $k (130..148) { $h{"$i:$j:$k"} = Calcul($i,$j,$k); } } }
Nous verrons dans un prochain article qu'il est possible de bâtir de réels tableaux à plusieurs dimensions en utilisant des références.
L'exemple suivant concerne les ensembles ; nous allons utiliser les tables de hachage pour calculer l'union et l'intersection de deux ensembles.
# Voici mes deux ensembles # Je mets les éléments dans des tableaux my @ensA = (1, 3, 5, 6, 7, 8); my @ensB = (2, 3, 5, 7, 9); # Voici mon union et mon intersection, # les éléments des ensembles en seront les clefs my %union = (); my %inter = (); # Je mets tous les éléments de A dans l'union : foreach my $e (@ensA) { $union{$e} = 1; } # Pour tous les éléments de B : foreach my $e (@ensB) { # S'il est déjà dans l'union, c'est qu'il est # dans A : je le mets donc dans l'intersection : $inter{$e} = 1 if ( exists( $union{$e} ) ); # Je le mets dans l'union $union{$e} = 1; } # Tous les éléments présents dans A ou B # sont des clefs de la table union. # Tous les éléments présents dans A et B # sont des clefs de la table inter. # Je reconstitue des tableaux à partir # des tables de hachage (en les triant # pour l'affichage) my @union = sort( {$a<=>$b} keys(%union) ); my @inter = sort( {$a<=>$b} keys(%inter) ); print("@union\n"); # affiche : 1 2 3 5 6 7 8 9 print("@inter\n"); # affiche : 3 5 7
Pour le même problème, voici une solution n'utilisant qu'une seule table de hachage, je vous laisse le soin d'en apprécier le principe :
my @ensA = (1, 3, 5, 6, 7, 8); my @ensB = (2, 3, 5, 7, 9); my %hash = (); # Qu'une seule table ... foreach my $e (@ensA) { $hash{$e}++; } foreach my $e (@ensB) { $hash{$e}++; } my @union = sort( {$a<=>$b} keys(%hash) ); my @inter = sort( {$a<=>$b} ( grep { $hash{$_}==2 } keys(%hash) ) ); print("@union\n"); # affiche : 1 2 3 5 6 7 8 9 print("@inter\n"); # affiche : 3 5 7
La compréhension de cet exemple demande d'avoir assimilé plusieurs notions importantes vues jusqu'ici.
En Perl, il est possible de manipuler plusieurs éléments d'un tableau ou d'une table de hachage à la fois. Cela s'appelle une tranche (slice en anglais).
Une tranche de tableau est un sous-ensemble des éléments du tableau.
Imaginons par exemple un tableau @t
duquel
nous souhaiterions manipuler les éléments d'indice 4 et 10 ;
pour cela nous allons prendre la tranche correspondante de ce tableau :
@t[4,10]
est une liste à deux éléments
qui est équivalente à ($t[4],$t[10])
Quelques explications sur la syntaxe. Tout d'abord, l'expression commence
pas une arobase, car il s'agit d'une liste d'éléments ; le dollar est réservé
aux scalaires, par exemple $t[4]
est un scalaire.
Ensuite, comme d'habitude pour les tableaux, les crochets permettent de
spécifier les indices. Enfin, l'ensemble des indices est indiqué par une liste
d'entiers : @t[2,10,4,3] @t[3..5] @t[fonction()]
...
Une telle tranche est utilisable comme valeur (passage de paramètres, etc) et comme l-value (expression à gauche du signe égal d'affectation) :
@t[4,10] = (4321,"age");
cette instruction affecte 4321 à l'indice 4 du tableau @t
et la chaîne age
à l'indice 10. On aurait pu écrire
($t[4],$t[10]) = (4321,"age");
Un autre utilisation des tranches de tableau apparaît avec les fonctions
qui renvoient une liste. Par exemple la fonction stat
prend en paramètre un nom de fichier et renvoie toute sorte
d'informations sur le fichier : taille, dates, propriétaire etc.
Il est courant d'écrire :
($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat($filename);
La fonction renvoie une liste qui est affectée aux variables de la liste de gauche. Les tranches peuvent intervenir si seules quelques informations vous intéressent et que vous ne voulez pas déclarer de variables inutiles. Par exemple, si seules les dates de modification (indice 9) et de création (indice 10) vous intéressent, vous pouvez écrire :
($mtime,$ctime) = ( stat($filename) )[9,10];
L'appel à la fonction est placé entre parenthèses et on ne prend que les éléments d'indice 9 et 10 de sa valeur de retour. On a alors une liste à deux éléments, celle-ci est affectée à la liste à gauche du signe égal et donc ces deux éléments sont affectés aux deux variables.
De la même façon qu'il existe des tranches pour les tableaux et les listes,
il en existe pour les tables de hachage. La sélection s'effectue bien sûr
sur les clefs. Par exemple, si %h
est une table
de hachage, alors @h{'clef1','clef2'}
est une liste
équivalente à ($h{'clef1'},$h{'clef2'})
Il est ensuite possible d'utiliser cette liste comme bon vous semble
(affectation, passage en paramètre, etc).
Une utilisation (assez complexe) des tranches serait indiquée lorsque l'on veut construire automatiquement une liste de valeurs uniques à partir d'un tableau dont on n'est pas sûr que ses valeurs le soient :
# Un tableau avec des valeurs dupliquées : my @t = qw(hello toto hello vous); # Déclaration d'une table de hachage : my %h; # On prend la tranche de %h dont les clefs # sont les valeurs du tableau @t # et on leur associe la valeur undef @h{@t} = (); # Les clefs de %h sont donc constituées des # valeurs de @t, et on est sûr de ne les # retrouver qu'une seule fois : @t = keys %h;
Le tableau @t
comporte alors une fois et une seule
chacun de ses éléments.
Pour le moment nous avons écrit des programmes dont les interactions avec leur environnement étaient faibles. Nous allons voir dans cette partie comment manipuler des fichiers en Perl.
Perl dispose d'opérateurs prenant en paramètre un nom de fichier ;
ce nom de fichier doit être un scalaire (une variable ou une constante).
Leur valeur de retour est souvent booléenne et quelquefois numérique.
Les coutumiers du shell retrouveront de nombreuses options de la commande
test
.
-e
teste si son paramètre est un chemin valable
dans le système de fichier (répertoire, fichier, etc).
On pourrait l'utiliser ainsi :
if( -e "/usr/tmp/fichier" ) { print "Le fichier existe\n"; }
-f
teste si son paramètre est un fichier normal.
-d
teste si son paramètre est un répertoire.
-l
teste si son paramètre est un lien symbolique.
Ceci n'exclut pas que -f
ou -d
renvoie vrai.
-r
teste si le programme a le droit de lire
le fichier/répertoire/etc passé en paramètre.
-w
teste si le programme a le droit d'écrire.
-x
teste si le programme a le droit d'exécuter
le fichier ou d'accéder (ou axéder :-)) au répertoire.
-o
teste si le fichier appartient à l'utilisateur
qui exécute le programme.
-z
teste si le fichier est vide.
-s
teste si le fichier est non vide ; en fait
cet opérateur renvoie la taille du fichier.
-M
renvoie l'âge en jour du fichier (depuis le
début de l'exécution du programme).
Voici quelques exemple d'utilisation de ces opérateurs :
my $file = "/usr/doc/perl"; if( -f $file && -w $file ) { .... } my $taille = -s $file; my $age = -M $file;
glob
La fonction glob
prend en argument une expression
et renvoie une liste de noms de fichiers. Cette liste correspond à l'évaluation
de l'expression selon les wildcards du shell.
Par exemple, glob( '*.o' )
renvoie la liste des fichiers du répertoire courant ayant l'extension
.o
Notez bien qu'il ne s'agit pas d'une expression régulière (pour ceux qui
connaissent ; pour les autres, nous en parlerons plus tard), mais bien
d'une expression telle qu'on la donnerait à un shell :
ls [A-Z]*.h
liste tous les fichiers commençant
par une majuscule ayant l'extension .h
Il existe une syntaxe plus concise et au comportement identique à cette fonction : l'expression peut être mise entre chevrons. Les deux lignes suivantes effectuent la même opération :
@l = glob('/usr/include/*.h'); @l = </usr/include/*.h>;
Après l'exécution d'une de ces deux lignes, le tableau @l
contient la liste des noms absolus des fichiers d'include pour le C
du répertoire /usr/include
Voici un premier exemple de manipulation de noms de fichiers :
#!/usr/bin/perl -w use strict; foreach my $name ( <*> ) { print "$name\n" if( -l $name ); }
Il affiche les liens symboliques du répertoire courant.
Voici un second exemple :
#!/usr/bin/perl -w use strict; foreach my $name ( <.*>, <*> ) { next if( ! -d $name ); next if( ! -w $name ); print "$name : ". ( -s $name ) ."\n"; }
Ce programme affiche le nom et la taille des sous-répertoires
du répertoire courant sur lesquels j'ai les droits d'écriture
(y-compris ceux commençant par un point, donc .
et ..
).
Pour lire ou écrire dans un fichier, il est nécessaire de l'ouvrir
préalablement. La fonction effectuant cette opération en Perl se
nomme open
et sa syntaxe est la suivante :
open(
HANDLE,
expression )
Le paramètre HANDLE sera l'identifiant du fichier après ouverture (on pourrait parler de descripteur de fichier). C'est ce descripteur qui devra être fourni aux fonctions de lecture et d'écriture pour manipuler le fichier. Pour ne pas rentrer trop dans les détails, un descripteur de fichier se représente dans le code Perl par une simple chaîne de caractères (sans quotes pour le délimiter) ; la convention veut qu'on le mettre toujours en majuscules.
Le paramètre expression est un scalaire (chaîne de caractères) comportant le nom du fichier à ouvrir précédé de zéro, un ou deux caractères indiquant le mode d'ouverture :
Caractère(s) | Mode d'ouverture |
aucun | lecture |
< |
lecture |
> |
écriture (écrasement) |
>> |
écriture (ajout) |
+> |
lecture et écriture (écrasement) |
+< |
lecture et écriture (ajout) |
Le habitués du shell retrouveront certaines notations connues.
Par exemple open(FIC1,"<index.html")
ouvre le fichier
index.html
en lecture et open(FIC2,">index.html")
l'ouvre en écriture-écrasement (c'est-à-dire que le fichier sera vidé
avant que le curseur ne soit placé au début du fichier).
Cette fonction open
renvoie une valeur booléenne
vrai ou faux indiquant le bon déroulement ou non de l'opération.
Il est très important de tester les valeurs de retour des fonctions
manipulant les fichiers (cela est vrai quel que soit le langage),
car on ne peux jamais être sûr de rien. Voici deux exemples de tests
de la valeur de retour d'open
:
if( ! open(FIC,">>data.txt") ) { exit(1); }
Dans ce premier exemple, on tente d'ouvrir en ajout un fichier
data.txt
; en cas d'impossibilité,
on appelle la fonction exit
qui met fin au programme
(la valeur 1 signale une erreur ; la valeur 0 signalant la fin
normal du programme, les autres valeurs sont utilisées pour signaler
au shell appelant une erreur). Vous noterez que l'utilisateur qui
exécute le programme n'est pas informé de la cause de l'échec ;
le programme se termine, tout au plus sait-il qu'il y a eu un problème
avec l'ouverture de ce fichier, mais il n'en connaît pas la cause.
L'exemple suivant va nous permettre de lui afficher un joli message
d'erreur :
open(FIC2,"</tmp/$a") or die("open: $!");
Nous cherchons ici à ouvrir en lecture le fichier dont le nom serait
la concaténation de la chaîne /tmp/
et du
contenu de la variable $a
.
La fonction die
met fin au programme comme
exit
le ferait, mais affiche en plus le paramètre
qu'on lui passe. En l'occurrence, le paramètre fourni est la chaîne
de caractères comportant le nom de la fonction qui cause l'échec
(on aurait pu ajouter le nom du fichier) ainsi que la variable
$!
. En contexte de chaîne de caractères, cette
variable magique $!
contient le message errno de la dernière
erreur survenue, par exemple No such file or directory
ou Permission denied
etc. L'utilisateur
est donc informé de la cause de la fin du programme.
Les descripteurs de fichier sont d'une espèce étrange en Perl,
le débutant s'abstiendra de chercher à trop comprendre les mécanismes
sous-jacents. Ce que l'on pourrait dire, c'est qu'il n'est pas
nécessaire de déclarer un descripteur de fichier, la fonction
open
valant déclaration.
Une fois un fichier ouvert, il nous est possible d'écrire et/ou de lire dedans (selon le mode d'ouverture) et de le fermer.
La lecture des fichiers texte s'effectue typiquement au moyen de l'opérateur chevrons, cette lecture se faisant ligne par ligne.
$l = <FIC>;
Cette instruction lit la prochaine ligne disponible du fichier FIC. Vous noterez bien que l'opérateur chevrons (diamond operator en anglais) est ici en contexte scalaire. En contexte de liste, il renvoie la liste de toutes les lignes restant dans le fichier :
@t = <FIC>;
Cette instruction 'absorbe' toutes les lignes du fichier dans une
liste qui est placée dans le tableau @t
.
Pour itérer sur les lignes d'un fichier, il est courant de faire ainsi :
while( defined( $l = <FIC> ) ) { chomp $l; print "$. : $l\n"; }
La boucle while
donne pour valeur à la variable
$l
une à une toutes les lignes du fichier.
La fonction chomp
supprime le dernier caractère
s'il s'agit d'un retour à la ligne (voir premier article).
La variable spéciale $.
vaut le numéro de la
ligne courant du dernier fichier lu (ici FIC).
Si vous utilisez la variable spéciale omni-présente $_
,
la construction
while( defined( $_ = <FIC> ) )
peut même s'abréger en :
while( <FIC> )
Pour écrire dans un fichier, nous allons utiliser les fonctions
print
et printf
que nous avons déjà vues. Elles prennent en premier argument
le descripteur de fichier :
print( FIC "toto\n" ); printf( FIC "%03d", $i );
Il faut noter qu'il n'y a pas de virgule après le descripteur
de fichier (il ne faut pas en mettre !). Les parenthèses sont comme toujours
optionnelles autour des arguments, mais permettent de lever certaines
ambiguïtés. La fonction printf
fonctionne comme
printf
ou fprintf
du C
et ne sera donc pas détaillée ici
(voir man 3 printf
).
Pour fermer un descripteur de fichier (et donc vider les buffers
associés), il faut faire appel à la fonction close
:
close( FIC );
À noter que si vous réutilisez un descripteur de fichier dans un
open
sans faire de close
au préalable, Perl ne rouspétera pas et fermera consciencieusement
le premier fichier avant d'ouvrir le deuxième.
Il existe plusieurs fichiers ouverts automatiquement par Perl dès le lancement du programme :
STDIN
: l'entrée standard
(souvent le clavier).
STDOUT
: la sortie standard
(souvent le terminal). Par défaut print
et
printf
écrivent sur ce flux.
STDERR
: la sortie d'erreur standard
(souvent le terminal). Par défaut warn
écrit sur ce flux.
ARGV
: ce descripteur est un peu
spécial, mais souvent bien pratique. Les lignes lues sont celles
des fichiers de la ligne de commande (donc les arguments passés
au programme sont considérés comme des noms de fichier) ;
si le programme est lancé sans argument, l'entrée standard est lue.
NB: vous pouvez écrire soit <ARGV>
soit
<>
La variable spéciale $ARGV
contient le nom du fichier en cours de lecture.
Discutons un peu de la manipulation de fichiers binaires.
Les exemples de lecture de fichiers donnés jusqu'ici ne conviennent
pas à de tels fichiers mais plutôt à des fichiers contenant du texte.
Vous pouvez, pour cela, utiliser la fonction
getc
qui renvoie le prochain caractère disponible :
$c = getc(FIC);
Vous pouvez aussi faire usage
de la fonction read
qui lit un nombre déterminé
de caractères :
$tailleLue = read( FIC, $tampon, $tailleALire );
Les données seront placées dans la variable $tampon
À la fin du fichier, ou s'il y a un problème, le tampon n'est
pas complètement rempli. C'est pour cela que l'on récupère la
valeur de retour de read
.
Pour écrire des données non textuelles dans un fichier, vous pouvez
tout à fait utiliser les fonctions print
et
printf
car les chaînes de caractères de Perl
peuvent contenir le caractère de code ASCII zéro. On notera que la
fonction write
existe, mais n'est pas l'inverse
de read
.
S'il vous est nécessaire d'utiliser les fonctions d'entrée/sortie
bas niveau, voici leurs noms en Perl : sysopen
,
sysread
, syswrite
et
close
.
Voici le prolongement de l'exemple donné pour les tables de hachage. Nous n'allons plus considérer que les mots sont contenus dans un tableau, mais nous allons les extraire d'un fichier.
#!/usr/bin/perl -w use strict; open(FILE,"<$filename.txt") or die"open: $!"; my($line,@words,$word,%total); while( defined( $line = <FILE> ) ) { @words = split( /\W+/, $line ); foreach $word (@words) { $mot =~ tr/A-Z/a-z/; $total{$word}++; } } close(FILE); foreach $word (sort keys %total) { print "$word a été rencontré $total{$word} fois.\n"; }
On effectue une boucle while
sur les lignes
du fichier. Chaque ligne est alors découpée en mots par la fonction
split
(\W+
correspond
aux suites de caractères non-alphanumériques, nous verrons cela
lors d'un prochain article sur les expressions régulières).
Chaque mot est mis en minuscules au moyen de l'opérateur
tr
(que nous expliquerons avec les expressions
régulières).
open
Il est facile en Perl de lancer une commande shell et de récupérer sa sortie standard ou de fournir son entrée standard.
Pour lire la sortie standard d'un programme, il suffit d'utiliser la
syntaxe suivante :
open(HANDLE,"
commande|")
par exemple :
open(FIC1,"ls|") open(FIC2,"df -HT $device|")
Les lignes lues via le descripteur de fichier ainsi créé seront celles que la commande aurait affichées à l'écran si on l'avait lancée depuis un terminal.
La syntaxe
open(HANDLE,"|
commande")
permet de lancer une commande. Les lignes écrites dans le descripteur de
fichier constitueront son entrée standard, par exemple :
open(FIC3,"|gzip > $a.gz") open(FIC4,"|mail robert\@bidochon.org")
Le format DBM est un format de fichier de hachage (clef/valeur) standardisé. Il existe en dehors de Perl.
En Perl, il nous est possible de manipuler directement une table
de hachage en la liant avec un tel fichier :
les valeurs de la table de hachage et du fichier sont synchronisées.
Pour y accéder nous utiliserons les fonctions dbmopen
et dbmclose
Nous allons directement passer à un exemple. Il comporte deux phases : dans la première nous allons créer un fichier DBM comportant un couple clef/valeur, dans la seconde nous utiliserons ce fichier pour retrouver ce couple. Premier script :
my %h; dbmopen(%h,"data",0644) or die($!); $h{'prenom'} = 'Larry'; dbmclose(%h) or die($!);
Un ensemble de fichiers data*
est alors créé.
Ces fichiers sont ensuite exploitables de la sorte :
my %h; dbmopen(%h,"data",0644) or die($!); print "$h{'prenom'}\n"; dbmclose(%h) or die($!);
Lors de cet appel à dbmopen
la table
%h
retrouve la valeur qu'elle avait
lors du dbmclose
du premier script.
N'oubliez pas que la documentation de Perl est très bien faite
et est disponible sur votre ordinateur au moyen de la commande
perldoc
: perldoc perl
vous donne accès à la liste des thèmes consultables avec
perldoc
. Par exemple perldoc perldata
vous explique les structures de données en Perl.
Pour une fonction particulière, utilisez l'option -f
:
perldoc -f chomp
Vous avez maintenant en main beaucoup de notions importantes de Perl. À vous de les mettre en oeuvre pour vos propres problèmes. Il n'est pas forcement facile d'exprimer son besoin en termes directement exploitable dans un langage particulier, mais en ayant un peu d'expérience de Perl, vous trouverez vite votre manière de résoudre un problème. Vous allez alors commencer à faire connaissance avec Tim Towtdi (There is more than one way to do it). ;-)