Les deux derniers articles nous ont quelque peu éloignés de notre but, il est temps que nous revenions à nos préocupations. Les expressions régulières sont fontamentales en Perl pourtant de nombreuses personnes éprouvent des difficutlés à penser en terme d'expressions régulières. Cet article décrit quelques stratégies à mettre en oeuvre pour éviter d'être dérouté par trop de signes cabalistiques.
La premiére règle consiste à tirer partie des séparateurs naturels. Nous utilisons couramment des blancs pour séparer les mots en écrivant, en parlant aussi d'ailleur. Ceci nous aide à mieux comprendre le discours. Faites donc de même en cherchant des signes constants qui vous aiderons à retrouver les articulations dans vos expressions régulières. Par exemple :
/^[-+]?\d+(\.\d+)?([eE][-+]?\d+)?$/est de l'hébreux jusqu'à ce que vous repèriez les groupes enclos dans () et [] pour découper cette expression en 4 fragments compréhensibles :
[+-]? \d+ (\.\d+)? ([eE][-+]?\d+)?Le premier est simple, c'est un signe "+" ou "-" optionnel. Le second est trivial, il s'agit d'un ou plusieurs chiffres. Le troisième signifie "un point décimal suivi d'un ou plusieurs chiffres", le point d'interrogation indiquant que ce groupe est optionnel. Le quatrième, également optionnel, est plus complexe : un 'E' majuscule ou minuscule, suivi d'un signe "+" ou "-" optionnel, suivi d'un ou plusieurs chiffres. Rassemblez toutes ces interprétations et vous reconnaîtrez l'expression de tout nombre valide en Perl, mais vous l'avez certainement déjà comprise.
Cette règle peut également s'appliquer à la construction d'expressions régulières. Supposons que nous ayons à reconnaître une date de la forme :
Fri Jan 28 13:12:02 PST 1994Dans cette ligne nous avons à faire à six blocs séparés par un blanc, mais il n'y a en fait que 2 types de chaînes fondamentales à reconnaître : des mots ("Fri", "Jan" et "PST") et des nombres ("28", "1994" et les heures, minutes et secondes). Nous pouvons donc simplement utiliser "
\w+
" pour les mots et
\d+
pour les nombres. L'expression peut donc se
réécrire :
# CETTE EXPRESSION EST FAUSSE... /^\w+ \w+ \d+ \d+:\d+:\d+ \w+ \d+$/Cette expression n'est pas tout à fait juste. Le jour du mois et l'heure du jour peuvent ne comporter qu'un seul chiffre, celui de la dizaine étant remplacé par un blanc. Il faut donc modifier un peu cette expression :
/^\w+ \w+\s+\d+s+\d+:\d+:\d+ \w+ \d+$/En général, je trouve "
\s+
" plus lisible que "
+
" (un blanc et un plus) même si elles ne sont pas
strictement équivalentes.La méthode que nous avons appliquée pour construire cet exemple nous conduit à notre seconde règle : commencez par des choses simples puis raffinez petit à petit. Par exemple, j'ai récemment eu la galère d'avoir à filtrer un fichier contenant des lignes du genre :
Pomeranz, Hal (pomeranz) x409Les espaces pouvaient être des blancs, des tabulations ou les deux. Fréquemment il y avait des blancs additionnels en fin de ligne. Certaines ne contenaient pas adresse E-Mail ou pas de numéro de téléphone, parfois elle ne contenait ni l'un ni l'autre.
La première approche aurait pu être :
/^\w+, \w+ \(\w+\) x\d+$/Vous voyez facilement les 4 blocs correspondants au nom, prénom, E-Mail et numéro de téléphone. Notez que nous avons du protéger les parenthèses par un antislash car elles ont une signification spéciale dans les expressions régulières. Nous pouvons maintenant nous attaquer aux cas particuliers.
L'E-Mail et le numéro de télephone sont optionnels
/^\w+, \w+( \(\w+\))?( x\d+)?$/Remarquez que nous avons inclu un espace avant l'adresse E-Mail et le numéro de téléphone dans chaque bloc optionnel. En théorie, la ligne pourrait se terminer simplement après le nom de famille sans autre champ ou blanc. Pour plus de rigueur, nous devrions également prendre en compte le cas des caractères blancs en fin de ligne et celui où les champs seraient séparés par plus d'un blanc.
/^\w+,\s+\w+(\s+\(\w+\))?(\s+x\d+)?\s*$/Le nom peut aussi être de la forme "Van Der Sluis" ou "Cody-lang", aussi souvenons nous de la première règle (toujours tirer avantage des caractères délimitant les chaînes). Nous pouvons donc considérer que le nom de famille s'étend jusqu'au dernier caractère avant la virgule :
/^.+,\s+\w+(\s+\(\w+\))?(\s+x\d+)?\s*$/Bien, l'expression ci-dessus filtre toutes les données que nous aurons à traiter parce que nous l'avons testée sur l'intégralité de ces données (vous avez bien sur fait ce test exhaustif n'est-ce pas ?). En fait ce dont j'ai réellement besoin c'est d'extraire les différents champs pour un traitement. Il faut donc maintenant que nous enclosions entre des parenthèses les champs particuliers que nous souhaitons récupérer en dehors de cette expression :
/^(.+),\s+(\w+)(\s+(\((\w+)\))?(\s+x(\d+))?\s*$/Comme Randal Schwarts le dit si justement, "Perl : une ligne bruitée avec un code correcteur et un soupson d'information".
La troisième règle est, ne jamais utiliser une expression compliquée quand une simple suffit. Par exemple, une adresse IP peut s'exprimer comme ceci :
/^([12]?\d?\d\.){3}[12]?\d?\d$/mais pourquoi se torturer l'esprit. Dans la plus part des cas, celle-ci suffit :
/^\d+\.\d+\.\d+\.\d+$/ou encore :
/^(\d+\.){3}\d+$/La première expression est peut-être plus lisible, mais votre agilité avec Perl peut varier. Dans bien des cas, la personne qui aura a maintenir votre code dans six mois (peut-être bien vous) vous remerciera.
Règle numéro 4, n'oublier jamais que la recherche de Perl est
grourmande. Les opérateurs '*
' et '+
'
consomment tous les caractères qu'ils rencontrent. Ceci peut être
intéressant quand vous avez à réaliser quelque chose de ce type :
$_ = "/usr/local/bin/perl"; ($dir, $prog) = ~/^(.*)\/(.*)$/;Le premier "
.*
" consommera tout jusqu'au '/
'
dont nous forçons la reconnaissance (application de la règle une)
avant que nous extrayons le nom du programme.Le comportement gourmand de Perl peut aussi être un problème, en particulier quand vous devez rencontrer une paire de séparateurs. Par exemple, supposons que nous devions retrouver le premier champs enclos entre des doubles guillemets dans la ligne suivante :
$_ = `pomeranz "Hal Pomeranz" "S Clara"';L'expression
$name = ~/"(.*)"/ # Fausse!instanciera
$name
avec
Hal Pomeranz" "S Clarace qui n'est pas ce que vous souhaitez. En fait il faut utliser
$name = ~/"([^"]+)"/ce qui signifie, repère un double guillemet suivi par n'importe quoi de différent d'un double guillement et se terminant par un second double guillement. Cette méthode "repère n'importe quoi jusqu'à mon séparateur" est trés utile dans votre panoplie du parfait petit programmeur Perl.
La cinquième règle est "prenez toujours la peine d'arrimer vos
expressions avec ^
et $
. Vous éviterez bien
des erreurs en les utilisant systématiquement, même quand ils ne
sont pas absolement nécessaires. C'est le cas dans la séquence suivante
très courante :
@files = grep(!/^\.\.?$/, readdir(DIR));qui donne la liste des fichiers du répertoire DIR exceptés "." (point) et ".." (pointpoint). Omettre
^
et $
supprimera tous les fichiers contenant un point. Omettre
$
supprimera tous les fichiers contenant un point dans
leur nom. Dans ces deux cas le résultat n'est pas celui escompté.Un autre cas où avez tout intérêt à arrimer votre expression se présente quand vous avez à vérifier le format d'une donnée.
/\d+/filtre des nombres entiers bien formés mais également "un9dur", ce qui n'est pas franchement un nombre entier. Pour s'assurer du type de la donnée, il faut utiliser :
/^\d+$/qui est une expression plus complexe, dans le genre de celle avec laquelle nous avons commencé cette étude.
Il est important que vous soyez à l'aise dans la manipulation des
expressions régulières en Perl. Pour cela pensez toujours à
découper vos expressions en petites entitées aisément
compréhensibles avant de les écrire ou d'essayer de les
comprendre. Procédez avec méthode, raffinez progressivement vos
expressions. Ne complexifiez pas inutilement vos expressions ou vous
aurez du mal à les modifier sans casser quelque chose dans votre
code. Sachez utiliser la gourmandise de la recherche de Perl, mais
conservez bien à l'esprit les piéges qu'elle réserve quelque
fois. Enfin n'hésitez pas à arrimer vos expressions par
^
et $
. Cela vous évitera bien des
déconvenues.
Traduction de ;login: Vol. 19 No. 2, April
1994.
Dernière édition: 29 décembre 1997 phb
Original 11/26/96ah
Back to the original
Retour à l'index