[couverture de Linux Magazine 52]

Les variables standard de Perl

Article publié dans Linux Magazine 52, juillet/août 2003.

Copyright © 2003 - Philippe Bruhat.

[+ del.icio.us] [+ Developers Zone] [+ Bookmarks.fr] [Digg this] [+ My Yahoo!]

Chapeau de l'article

Perl a la réputation d'être un langage illisible (write-only, disent ses détracteurs anglophones) dont le code ressemble à du "bruit de modem" (ou line-noise), une suite incohérente de caractères. Ceci est dû à l'usage intensif de caractères non alphanumériques, en particulier pour les variables.

Cet article présente les principales de ces variables, et va vous apprendre à les connaître et à les utiliser.

Les variables de Perl

Dans le cas des variables de Perl, le sigil est un caractère non alphanumérique qui détermine le type d'une variable en précédant son nom. $x est un scalaire, @x un tableau, %x un hashage. Dans le cas des variables spéciales, le nom lui-même est souvent aussi un caractère non alphanumérique.

Les variables par défaut

Les variables qui suivent sont indispensables ! Il vous faudra maîtriser au moins celles-ci avant de devenir un véritable Perl Adept.

$_

$_ est la variable par défaut (ou la variable contenant la "valeur courante"). Elle fait partie des éléments les plus intéressants de Perl, en apportant à la fois concision et lisibilité à vos programmes.

Perl a été créé par un linguiste et utilise certains des mécanismes des langage naturels dont le pronom et l'ellipse. Justement, $_ fonctionne comme un pronom. Prenons par exemple cette conversation (fictive) entre deux utilisateurs de Perl :

Dans cette petite conversation, "Larry Wall" est mentionné une seule fois, mais est le sujet ou l'objet de cinq phrases au total. Les interlocuteurs n'ont pas besoin de rappeler à chaque fois que c'est de Larry qu'on parle. Il (Larry) est le sujet implicite de ces différentes phrases.

$_ fonctionne exactement de la même façon : c'est l'objet implicite sur lequel s'appliquent certaines fonctions ou opérations quand aucune variable n'est mentionnée.

De nombreuses fonctions unaires et certaines fonctions de liste utilisent $_. Plus précisément, toutes les fonctions qui utilisent $_ quand il n'est pas précisé d'argument (ou qu'un ou plusieurs d'entre eux sont absents) sont : -X, abs, chomp, chop, chr, cos, defined, eval, exp, glob, hex, int, lc, lcfirst, length, log, lstat, oct, ord, pos, print, quotemeta, readlink, ref, require, rmdir, sin, split, sqrt, stat, study, uc, ucfirst, unlink.

Dans la liste qui précède, -X représente l'ensemble des fonctions de test sur les fichiers (c'est-à-dire -r, -w -x, -o, -R, -W, -X, -O, -e, -z, -s, -f, -d, -l, -p, -S, -b, -c, -u, -g, -k, -T, -B, -M, -A et -C). La seule exception est -t, qui agit sur STDIN quand aucun argument n'est fourni.

Les opérations de correspondance de motif m//, s/// et tr/// (ou y///) s'appliquent à $_ quand elles sont utilisées sans l'opérateur =~.

On peut donc utiliser la "valeur courante" sans y faire référence. Mais cette construction ne serait pas si intéressante si $_ n'était pas mis à jour de façon tout aussi implicite par certaines constructions Perl, à savoir foreach et while.

Ainsi, $_ est l'itérateur par défaut de foreach quand aucune variable n'est fournie explicitement. Ceci se combine admirablement avec l'utilisation de foreach comme modificateur d'instruction (les modificateurs sont if, unless, while, until, foreach, placés après une instruction simple).

    # n'affiche que certains trucs
    /regex/ && print for @trucs;

    # autre formulation
    print grep { /regex/ } @trucs;

À comparer par exemple avec :

    # n'affiche que certains trucs (les mêmes qu'avant)
    foreach my $truc (@trucs) {
        print $truc if $truc =~ /regex/;
    }

Lorsque le résultat d'un <FH> est utilisé comme seul critère de test d'un while, il est placé dans $_. Ceci n'est valable que dans un while. De plus, while(<FH>) est "magique", et correspond en fait à une opération un peu plus complexe qu'un simple test.

    while (<FH>) {
        # la ligne courante est stockée dans $_
        ...
    }

est équivalent à :

    while ( defined( my $ligne = <FH> ) ) {
        # la ligne courante est stockée dans $ligne
        ...
    }

Note : Le defined n'est pas nécessaire ici, grâce à la magie de while() . En revanche, dans un test plus complexe que celui-ci (boucler tant qu'il y a quelque chose à lire), il est préférable d'utiliser une affectation avec un test de définition, sans quoi vous prendriez le risque d'être confrontés à un subtil bug.

Mais imaginons le script suivant, qui affiche les premières lignes d'un fichier, et servira à vous présenter ce fameux bug :

    open F, "< $fichier" or die "Impossible d'ouvrir $fichier: $!";
    while ( my $ligne = <F> and $. <= 10 ) {
        print $ligne;
    }

$. est le numéro de la ligne courante, comme vous allez le lire plus loin. Ce programme n'affiche donc que les 10 premières lignes du fichier.

Si jamais le fichier se termine par un 0 sans saut de ligne final (et fait moins de 10 lignes), la dernière ligne ne sera pas affichée. Pourquoi ?

Dans Linux Magazine 41, Sylvain vous a présenté les valeurs scalaires que Perl considère comme fausses. Pour mémoire, il s'agit de : "" (la chaîne vide), 0 (la valeur numérique 0), "0" (la chaîne contenant comme seul caractère 0 (code ASCII 48)) et undef.

Ainsi, la dernière ligne fera renvoyer une valeur fausse ("0") à l'affectation, ce qui va faire échouer le test et terminer (prématurément) la boucle.

Et pourtant, si vous écrivez while(<F>) ou while( my $ligne = <F> ), la dernière ligne ne sera pas ignorée. C'est parce que le compilateur Perl est capable de reconnaître ces cas simples, et va traduire le code qui sera exécuté en while( defined( my $ligne = <F> ) ).

Comme vous le verrez au cours de vos expérimentations avec Perl, Perl se met en quatre pour faire "ce à quoi vous pensez". C'est ce que dans la communauté Perl on appelle le principe DWIM (Do What I Mean).

Tout ce qui précède apparaît admirablement dans l'exemple qui suit :

    # imprime la liste des utilisateurs
    open F, "< /etc/passwd" or die "Impossible d'ouvrir /etc/passwd : $!";
    while (<F>) {
        next if /^\s*(?:#|$)/;    # ignore les commentaires et lignes blanches
        s/:.*//;
        print;
    }
    close F;

qui est tout de même plus lisible (moins de variables intermédiaires) et plus rapide à taper (moins de caractères) que :

    # imprime la liste des utilisateurs
    open F, "< /etc/passwd" or die "Impossible d'ouvrir /etc/passwd : $!";
    while (defined my $ligne = <F>) {
        next if $ligne =~ /^\s*(?:#|$)/;
        $ligne =~ s/:.*//;
        print $ligne;
    }
    close F;

Note : Les super-pouvoirs que vous avez acquis depuis votre exposition aux radiations de mon article précédent (Linux Magazine 50), vous permettent de réduire instantanément ce programme au one-liner suivant :

    $ perl -lpe 'next if/^\s*(?:#|$)/;s/:.*//' /etc/passwd

Pour terminer, $_ est l'itérateur implicite des fonctions grep et map, ce qui se combine parfaitement avec les cas précédents :

    # récupère la liste des sous-répertoires de $dir dans @subdirs
    opendir( DIR, $dir ) or die "Impossible d'ouvrir $dir: $!";
    my @files = readdir(DIR);

    # Note : @files contient des fichiers relatifs à $dir
    my @abs = map { "$dir/$_" } @files;
    my @subdirs = grep { -d } @abs;

    closedir(DIR);

On peut évidemment faire plus clair, et plus court :

    # récupère la liste des sous-répertoires de $dir dans @subdirs
    opendir( DIR, $dir ) or die "Impossible d'ouvrir $dir: $!";

    my @subdirs = grep { -d } map { "$dir/$_" } readdir(DIR);

    closedir(DIR);

@ARGV

@ARGV contient la liste des paramètres passés sur la ligne de commande au script en cours d'exécution. Le nom @ARGV tire ses origines du tableau argv utilisé en C, à ceci près que $ARGV[0] n'est pas le nom du script en cours d'exécution, mais le premier argument passé à votre script.

Dans la partie principale du programme (c'est-à-dire en dehors des définitions de sous-programmes par sub {}), pop et shift agissent sur @ARGV.

@ARGV s'utilise le plus souvent comme $_, c'est-à-dire de manière implicite. Ainsi, dans l'exemple de code qui suit, beaucoup de choses sont faites de façon implicite :

    #!/usr/bin/perl
    while (<>) {
        # traitement complexe sur $_
    }

En fait, @ARGV est principalement lié à open() . Mais, me direz-vous, on n'a même pas utilisé open() dans le code qui précède ? En effet, l'utilisation de open() fait justement partie des choses implicites dont je viens de parler...

Quand vous utilisez <> pour traiter ligne par ligne les fichiers passés en paramètre à votre script, Perl fait un open() implicite de chaque fichier listé dans @ARGV (et pousse le dévouement jusqu'à émettre un avertissement si l'un des fichiers n'existe pas).

L'exemple de code ci-dessus réalise donc un "traitement complexe" sur chaque ligne des fichiers listés dans @ARGV (c'est-à-dire sur chacun des fichiers passés en paramètre à votre script). Et ceci avec une construction somme toute assez simple.

De plus, si @ARGV est vide lors de la première itération de la boucle, Perl prétendra avoir ouvert le fichier - (tiret), c'est-à-dire l'entrée standard (voir également $ARGV, ci-après). On retrouve ainsi le fonctionnement en "filtre" qui est l'une des base de la philosophie Unix.

Vous pouvez parfaitement modifier le contenu de @ARGV avant de commencer une boucle utilisant <>. Ceci permet en particulier de traiter les options de ligne de commande (par exemple avec les modules Getopt:: qui vous ont été présentés par Jérôme Quelin dans Linux Magazine 49) qui précèdent les noms des fichiers à traiter.

Une autre possibilité est de construire dynamiquement la liste des fichiers à traiter à partir des options. Par exemple, si aucun fichier n'a été précisé, plutôt que de traiter STDIN, vous pourriez traiter tous les fichiers du répertoire courant :

    @ARGV = glob("*") unless @ARGV;

Ou même seulement les fichiers (et pas les répertoires) :

    @ARGV = grep { -f } @ARGV;

Bien sûr, si vous utilisez l'option -n ou -p dans la ligne shebang, n'oubliez pas de faire vos modifications de @ARGV dans un bloc BEGIN{} (car la boucle while(<>) est elle-même implicite).

Pour résumer, le contenu de @ARGV est ouvert automatiquement quand vous utilisez une boucle while(<>) dans le cadre d'un fonctionnement de type filtre. Bien sûr, ces fichiers sont ouverts avec le open() de Perl. Du coup, vous avez accès à toute la magie dont open() est capable. C'est-à-dire que si par exemple vous modifiez le contenu de @ARGV de façon à ce que les noms de fichiers soient en fait des chaînes de type "commande argument |", les lignes seront alors lues depuis un autre processus forké pour l'occasion.

Voici deux exemples de tels traitements pour rendre cela un peu plus clair :

    @ARGV = map { /^\.(gz|Z)$/ ? "gzip -dc $_ |" : $_ } @ARGV;

Si les fichiers passés en paramètre sont compressés, Perl les décompressera (avec gzip) pour pouvoir les traiter.

Encore plus fort, et sur le même principe, voici comment traiter des fichiers téléchargés directement sur le net :

    @ARGV = map { m!^\w+://! ? "lynx -dump $_ |" : $_ } @ARGV;

Ou, si voulez traiter les sources HTML :

    @ARGV = map { m!^\w+://! ? "lynx -source $_ |" : $_ } @ARGV;

La magie de open() a tout de même un inconvénient : vous ne pourrez pas traiter un fichier nommé - (tiret). Mais il y a plein d'astuces pour s'en tirer, comme par exemple l'appeler en tant que ./-.

@_

@_ contient les arguments passés au sous-programme en cours (sub). À l'intérieur d'une routine, pop et shift agissent sur @_.

C'est ainsi que l'on voit souvent des routines commencer comme ceci :

    sub rot13 {
        my $string = shift;
        $string =~ y/A-Za-Z/N-ZA-Mn-za-m/;
        return $string;
    }

Et pour les méthodes des modules objets (une référence à l'instance en cours de l'objet étant toujours passée en premier paramètre à la méthode):

    # un accesseur pour l'attribut color d'un objet
    sub color {
        my $self = shift;
        my $old  = $self->{color};
        $self->{color} = shift if @_;
        return $old;
    }

Voire :

    sub print_color { print shift->{color} }

Les variables d'entrée/sortie

Les variables qui suivent concernent les entrées/sorties de Perl, elle permettent de changer l'idée que Perl se fait de la notion de ligne, de modifier facilement l'affichage de champs en sortie, etc.

Entrée

Sortie

Les variables d'erreur

Bien programmer, c'est aussi se préparer à l'échec : que se passe-t-il si le fichier que vous voulez ouvrir n'existe pas ? Et si la chaîne que vous eval()uez contient une erreur de syntaxe ?

La gestion des erreurs en Perl est un petit peu compliquée par le fait qu'il existe plusieurs variables dédiées aux erreurs. Il y a au total quatre variables d'erreur en Perl : $@, $!, $^E et $?. Elles correspondent respectivement aux erreurs renvoyées par l'interprêteur Perl, la librairie C (errno), le système d'exploitation et un programme externe.

On peut expliquer cette abondance de deux manières :

En fait, les variables d'erreur les plus couramment utilisées sont $@ et $!.

Les variables système

Un certain nombre de variables permettent à vos scripts d'interagir avec le système (d'exploitation).

Autres variables

Le reste

Les variables en anglais

Si vous préférez utiliser des noms plus faciles à retenir (ou des noms similaires à ceux utilisés par awk) pour ces variables prédéfinies, vous pouvez toujours utiliser le module English. Celui-ci crée des alias en toutes lettres pour les variables de ponctuation.

Certaines variables ont même plusieurs noms, plus ou moins longs, le second venant en général du nom de la variable équivalente de awk.

Attention, l'utilisation du module English avait un effet négatif sur les performances, en particulier au niveau des expressions régulières. Cependant, depuis la version 5.8.0 de Perl, on peut utiliser English sans dégrader les performances. Dans ce cas, il faut taper :

    use English qw(-no_match_vars);

Pour les variables que nous avons vues aujourd'hui les noms longs sont :

    $_       $ARG
    @ARGV
    @_
    $/       $INPUT_RECORD_SEPARATOR    $RS
    $.       $INPUT_LINE_NUMBER         $NR
    $\       $OUTPUT_RECORD_SEPARATOR   $ORS
    $,       $OUTPUT_FIELD_SEPARATOR    $OFS
    $"       $LIST_SEPARATOR
    $|       $OUTPUT_AUTOFLUSH
    $@       $EVAL_ERROR
    $!       $OS_ERROR                  $ERRNO
    $?       $CHILD_ERROR
    $^E      $EXTENDED_OS_ERROR
    $0       $PROGRAM_NAME
    $$       $PROCESS_ID                $PID
    %ENV
    @INC
    $^O      $OSNAME
    $^X      $EXECUTABLE_NAME
    $^W      $WARNING
    %SIG

Les autres variables

Perl dispose de nombreuses autres variables prédéfinies, qui sont décrites dans la page de manuel perlvar(1).

Vous devez cependant savoir que tous les identificateurs Perl qui commencent par des chiffres ou des caractères de contrôle ne sont pas soumis aux effets de la déclaration package et sont toujours dans le package main.

Les noms suivants échappent également aux effets de package : ARGV, ARGVOUT, ENV, INC, SIG, STDERR, STDIN et STDOUT. Il s'agit de noms de variables ou de handle de fichiers spéciaux, qui vous ont pour la plupart été présentés dans cet article.

Enfin, j'ai complètement ignoré $`, $&, $' et autres $1, $+, @+, @-, qui sont spécifiques aux expressions rationnelles. Reportez vous à perlre(1) et perlretut(1) pour en connaître les arcanes.

Références

Le manuel de Perl est une mine d'informations. Cet article reprend, détaille, agrège et ajoute des exemples de code aux informations issues des pages suivantes :

Toujours en anglais, mais sur un mode plus détendu :

Mon prochain article sera d'ailleurs consacré à la documentation de Perl, afin de vous aider à vous retrouver dans la jungle de l'information disponible.

L'auteur

Philippe 'BooK' Bruhat, <book@mongueurs.net>.

Philippe Bruhat est vice-président de l'association les Mongueurs de Perl, membre du groupe Paris.pm et de l'équipe organisatrice de la conférence YAPC::Europe à Paris en juillet 2003 (http://yapc.mongueurs.net/). Il est consultant en sécurité et l'auteur des modules Log::Procmail, HTTP::Proxy et Regexp::Log, disponibles sur CPAN.

BooK tient à remercier Jean Forget, qui lui a fourni un exemple tout à fait éclairant sur l'étendue de la magie à l'œuvre dans while().

[IE7, par Dean Edwards] [Validation du HTML] [Validation du CSS]