Aller au contenu

Une initiation aux expressions rationnelles

Table des matières

Un petit tutoriel très basique et explicatif pour se plonger dans le monde merveilleux des expressions régulières !

C’est quoi ?

Une expression rationnelle, c’est une suite de caractères qui représente un motif (ou pattern en anglais), ce motif, qui sert de modèle, nous permettra de trouver des portions de texte qui correspondront au modèle déterminé.

Pour faire un peu plus simple et généraliste:

C’est un modèle (une chaine de caractères) qui permet de retrouver dans un texte une suite de caractères qui correspond au modèle défini.

Alors oui, comme ça, on se rend pas forcément compte de l’utilité de ce système, mais au fur et à mesure de l’article, vous comprendrez que c’est vraiment très puissant.

Qui les utilise ?

On retrouve les regex dans beaucoup d’éditeurs de texte ou d’utilitaires principalement sous Unix et GNU/Linux (grep, egrep, pgrep, fgrep, vim, emacs, sed, awk) ainsi que dans les langages de programmation, idéalement avec Perl qui propose un support avancé des regex de manière native. Il existe aussi beaucoup de bibliothèques pour les autres langages qui ne les intègrent pas nativement, comme le C par exemple ou le Java, le PHP, etc… Enfin, on retrouve les regex dans les shells, comme Bash, Kch (Korn-shell), Csh (C-Shell) et bien d’autres.

Ça ressemble à quoi ?

Attention, c’est assez vomitif quand on ne connait pas, une expression rationnelle peut ressembler à ça:

^[a-z0-9_-]+@[a-z0-9._-]{2,}.[a-z]{2,4}$

J’ai précisé « peut », car une regexp peut prendre énormément de formes selon le motif que l’on souhaite construire.

A la fin de cet article, on sera en mesure de déchiffrer cette regexp !

Dans le vif du sujet !

Accrochez vous, c’est parti !

Pour construire notre motif nous disposons de caractères spéciaux et de structures réservés. Ils indiquent différentes choses selon où ils sont placés.

On y trouve:

Les ancres

Le caractère ^ marque le début d’une chaine.

Le caractère $ marque la fin d’une chaine.

Exemple: ^toto$ Ce motif ne reconnaitra que toto

Les classes de caractères

Les crochets [] indiquent une classe.

Le tiret - indique un intervalle dans une classe. En dehors d’une classe, au début d’une classe ou à la fin d’une classe, il est considéré comme un caractère normal.

Donc dans la regex suivante, le dernier tiret est considéré comme normal (dé-spécifié) car placé à la fin: [a-z0-9-] Le premier indique l’intervalle a – z et le deuxième l’intervalle 0 – 9.

Exemple:

[Zz]ilkos reconnaitra zilkos et Zilkos h[1-6] reconnaitra de h1 à h6.

Les classes complémentées

Le caractère ^ dans une classe indique un côté exclusif.

[^0-9] reconnait tout ce qui est différent d’un chiffre

[^1-3] reconnait tout, sauf les chiffres de 1 à 3.

^[^0-9] La chaine ne commence pas par un chiffre. Le premier ^ étant hors de la classe, il désigne donc le début de la chaine ! (cf les ancres).

Sachez aussi qu’il existe les parenthèses: ( )

Je n’en parlerai pas ici, c’est un peu trop compliqué pour ce qu’on va faire (sachez qu’on peut créer des variables en groupant des sous-chaines).

L’alternative

Via le symbole |. Indique simplement une alternative, on retrouve ce symbole souvent en programmation quand on aborde le OU.

^Zilk[o|a]s$ reconnaitra Zilkos et Zilkas (remarquez que j’ai indiqué le début et la fin de la chaine).

Les quantificateurs

Ils sont les suivants: ? * + {x} {x,} et {x,y}

  • ? indique 0 ou 1 occurrence.
  • * indique 0, 1 ou plusieurs occurrences.
  • + indique plusieurs occurrences.
  • {x} indique l’obligation d’apparaitre exactement x fois.
  • {x,} indique l’obligation d’apparaitre au moins x fois.
  • {x,y} indique l’obligation d’apparaitre au moins x fois mais pas plus de y fois.

Exemple:

p?: p peut apparaitre 0 ou 1 fois.

p{2}: p doit apparaitre exactement 2 fois.

p{2,}: p doit apparaitre au moins 2 fois.

p{2,6}: p doit apparaitre de 2 à 6 fois (pp, ppp, pppp, etc…).

Les classes abrégées

Les classes abrégées sont supportées uniquement par les regex PCRE. PCRE pour Perl Compatible Regular Expression, je vous avais dit que Perl était une référence en la matière.

Nous avons dans les classes abrégées une simplification de motifs courants comme:

  • d qui correspond à [0-9]
  • D qui correspond à [^0-9]
  • w qui correspond à [a-zA-Z0-9_]
  • W qui correspond à [^a-zA-Z0-9_]
  • t qui correspond à une tabulation
  • n qui correspond à un saut de ligne
  • r qui correspond à un retour chariot
  • s qui correspond à un espace
  • S qui correspond à N’EST PAS un espace
  • . qui correspond à « n’importe quel caractère »
Déchiffrons !

Au début de cet article, je vous avez donné la regexp suivante:

^[a-z0-9_+-]+@[a-z0-9._-]{2,}.[a-z]{2,4}$

Cette expression rationnelle sert à vérifier si une adresse email est valide ou non. Comme c’est à but explicatif et pas utilitaire elle est loin d’être parfaite, mais fonctionne quand même pas trop mal.

Avec les informations du dessus et en sachant à quoi elle sert, on peut aisément l’expliquer. Faisons le étape par étape.

La partie ^[a-z0-9_+-]+:

On indique le début de la chaine via un ^ ensuite on a un motif (une classe) et un + (quantificateur). L’action du quantificateur est sur le caractère ou le motif qui le précède (dans le cas présent la classe). Il faut donc lire un peu à l’envers, en commençant par le quantificateur. Ce qui donnerait:

La chaine commence (^) par une ou plusieurs (+ de fin) occurrences de lettre en minuscule, de chiffre, d’underscore (_) , de plus ou de tiret ([a-z0-9_+-] (souvenez vous qu’un tiret dans une classe, si placé au début ou à la fin est considéré comme un caractère normal et non pas un caractère d’alternative).

Voilà pour le premier bloc.

Après, on attend un arobase puis un motif et enfin un quantificateur. Voyons ça:

@[a-z0-9._-]{2,}

Le motif est simple, c’est le même que précédemment sauf qu’on a les points aussi. En effet le point dans une classe représente ce qu’il est, donc un point. Hors de la classe, le point est une classe abrégée si notre système supporte les regex PCRE qui représente «n’importe quel caractère». Là, on est dans une classe donc c’est un point (le caractère) et rien d’autre.

Le quantificateur, on lit dans le même sens qu’avec le +. Celui-ci nous dit: Le motif doit apparaitre au moins deux fois. Facile, c’est similaire au premier bloc.

Passons au troisième et dernier bloc: .[a-z]{2,4}$

A la suite de la chaine (bloc1 + bloc2) on attend un point et seulement un point vu qu’il est échappé grâce au caractère d’échappement . Puis un motif, puis un quantificateur puis une fin de chaine.

Allez courage on arrive presque au bout !

Le motif (la classe) est simple, des lettres. Le quantificateur est simple lui aussi, il souhaite que le motif apparaisse entre 2 et 4 fois.

Donc on lit dans le bon sens, et exprimé clairement ça donne: On souhaite que la chaine (bloc 1 + bloc2) se termine (donc le bloc 3) par minimum 2 et maximum 4 caractères minuscules uniquement.

Ayé ! Résumons !

On va étudier nos bouts de regexp, en français, sans code:

Bout 1:

La chaine commence par une ou plusieurs occurrences de caractères pouvant être composés de lettre en minuscule, de chiffre, d’underscore ou de tiret

Exemple: zilkos

Bout 2

Le bout 1 doit être suivi d’un arobase puis d’une chaine de caractères pouvant contenir des lettres minuscules, des chiffres, des points, des underscores et des tirets au nombre minimum de 2.

Exemple: @unixmail

Bout 3

 A la suite de cette chaine, on y attend un point puis une suite de caractère minuscule uniquement dont la longueur minimale est de 2 et la longueur maximale est de 4.

Exemple: .fr

Voyons les retours, après avoir scripté ça en Perl, j’ai trouvé un outil, un genre de validateur d’expressions rationnelles qui fonctionne pas trop mal (j’ai rajouté les causes des chaines invalidées pour que vous compreniez pourquoi ça ne match (correspond) pas.

Résultats des tests

Modèle testé :

^[a-z0-9_-]+@[a-z0-9._-]{2,}.[a-z]{2,4}$

Chaîne 1 : bonjour@greuh.trololo

Résultat: FAUX

Cause: La partie trololo fait bien plus de 2 caractères mais dépasse 4 caractères.

Chaîne 2: agrogrom@exemple.com

Résultat: *VRAI*

Chaîne 3: moi+spam@gmail.com

Résultat: VRAI

Chaîne 4: ma.maison.moi@exemple.info

Résultat: FAUX

Cause: Le premier bloc n’autorise pas les points.

Oui, cette expression rationnelle considère qu’on peut débuter une adresse email par un tiret et que la partie de gauche n’a pas à comporter de point, comme je l’ai dit, elle n’est pas parfaite et je ne voulais pas la complexifier d’avantage pour garder un côté pédagogique.

Utilité

Les expressions rationnelles sont très utiles en administration système ou en développement. Elle permettent de contrôler des saisies d’utilisateurs, de trier des longs fichiers de logs pour faire apparaitre ce qu’on veut, de trier des retours de fonctions Shell directement dans la console ou via des scripts. Bref, les utilités sont nombreuses et ça peut faire gagner vraiment beaucoup de temps, à condition d’être très rigoureux sur la vérification de l’expression rationnelle.

Les regex peuvent aussi être utiles pour configurer certains logiciels de sécurité. Prenons le cas de Fail2Ban, ce logiciel lit les logs de différents services, comme SSH, Apache, FTP, postfix, et consorts. Il recherche les erreurs d’authentification répétées aux services (dans un intervalle de temps spécifié) et ajoute une ligne dans iptables pour empêcher la personne de recommencer. Pour faire court, ça lutte contre les brute-force et vilain qui veulent squatter votre machine via des attaques par dictionnaire. C’est très efficace et pour lire les logs, il se base sur… des expressions rationnelles !

C’est vrai que c’est de la regex un peu touffue pour notre petit niveau, un exemple de qui scrute le service SASL:

(?i): warning: [-._w]+[<HOST>]: SASL (?:LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed(: [A-Za-z0-9+/ ]*)?$

Donc ça sert vraiment dans beaucoup de domaines. Même si l’apprentissage est un peu rude, ça vaut franchement le coup !

Mot de la fin

Comme vous pouvez le voir, c’est un domaine assez complexe bien que très logique. Une fois bien maitrisé, c’est franchement très très puissant, mais à aborder, ça reste épineux.

Personnellement c’était un calvaire d’écrire cet article, non pas parce que je n’avais pas envie, mais surtout parce que c’est assez complexe à expliquer. Je suis loin d’être un dieu en regex et donc essayer d’expliquer correctement les choses nécessite de prendre une certaine hauteur, si bien que j’ai demandé une relecture externe pour être sûr que ça ne soit pas une Pierre de Rosette avec vous en guise de Champollion des expressions rationnelles.

Je vous invite fortement à commenter si quelque chose n’est pas clair dans le but de vous aider et de faire en sorte que l’article soit plus clair. Vous pouvez également contribuer, comme à l’habitude.

Quelques liens pour terminer: