reseau social

Les bases des expressions régulières

Un article de ToutProgrammer.com.

Cet article propose une découverte des expressions régulières (ou rationnelles). Les expressions régulières emploient un langage puissant qui permet de retrouver des sous-chaînes dans des chaînes plus importantes en utilisant des motifs évolués.

Sommaire

[modifier] Avant-propos

Avant tout, il est important d'indiquer que le terme Expressions régulières est une mauvaise traduction de l'anglais Regular Expressions. On devrait traduire ceci par "Expressions rationnelles". Mais les mauvaises habitudes sont parfois les plus tenaces et lorsque l'on parle d'expressions régulières, on sait généralement de quoi on parle. Vous trouverez également le terme regexici ou là, version plus courte de regular expression.

Les exemples donnés ici ne s'appliqueront pas à un langage particulier car seuls les motifs seront donnés (sans instruction de langages comme Perl, PHP, C, Java, ...)

[modifier] Introduction

Les expressions régulières (ou regex) ont gagné leurs lettres de noblesse dans les années 70 avec l'utilitaire Unix grep. Cette utilitaire, toujours très utilisé, permet de rechercher les lignes de fichiers qui contiennent certains motifs. On retrouve une version très limitée des expressions régulières en utilisant les fameux jockers * ou ? lors du listage du contenu d'un répertoire du disque dur ou de son arborescence (avec dir, ls, ll, ...). On peut ainsi ne lister que certains fichiers ayant une certaine extension en ignorant ainsi tous les autres.

Mais les expressions régulières sont bien plus évoluées que cela puisque les motifs utilisés vont bien au dela du simple ? ou * car vous pouvez rechercher des chaînes de caractères particulières. Vous pouvez également, suivant les langages et les possibilités du moteur d'expressions régulières utilisé, décomposer une chaîne comme une adresse email pour en retrouver chaque élément (identifiant, hôte).

Les expressions régulières permettent d'optimiser et sécuriser un code source en permettant en une ligne de code de tester toutes les possibilités d'une saisie d'un utilisateur et lui interdire de rentrer des caractères non autorisés.

Il existe 2 normes pour les expressions régulières dont la syntaxe diffère très légèrement: posix et Perl. Les implémentations qui sont ensuite faites pour les différents langages vont intégrer l'une ou l'autre (voire les deux) ce qui implique de connaître un minimum celle que vous utiliserez.

[modifier] Un exemple

Avant d'aller plus loin, nous allons voir rapidement un exemple de motif permettant de vérifier si le format d'une adresse email est valide. Ceci ne vérifie en rien s'il l'adresse mail est valide puisque cela ne pourra se faire qu'à partir de fonctions qui vérifiront l'hôte.

Cet exemple ne prend pas en compte toutes les extensions des noms de domaine afin de ne pas le compliquer. Cela signifie que si l'utilisateur saisie un .fz à la place de .fr, le format de cette adresse sera tout de même validée.

Voici donc cet exemple (testé avec grep):

[a-zA-Z][-._a-zA-Z0-9]*@[a-zA-Z][-a-zA-Z0-9]*\.[a-zA-Z]{2,4}

Cette simple ligne pourra retrouver des adresses de courrier électronique de type:

  • jdupond@sitedetest.com,
  • j.dupond@site-de-test.com,
  • j_dupond@sitedetest.FR,
  • JDupond@SiteDeTest.Com,
  • ...

Comme vous le voyez, en très peu de caractères, nous sommes en mesure de tester un très grand nombre de cas.

[modifier] Les classes de caractères

La façon la plus simple de retrouver un chaîne de caractères est d'utiliser la chaîne elle même. Imaginons que nous souhaitions retrouver toutes les lignes qui contiennent la chaîne de, nous aurions comme motif ce qui suit:

de

Mais ceci ne permet pas de rechercher De, DEet dE. C'est là que les classes de caractères vont montrer leur puissance.

Une classe de caractères se reconnaît grâce à des crochets comme ceci []. Tous les caractères compris à l'intérieur de ces crochets vont représenter les caractères reconnaissables par le moteur d'expressions régulières. Si nous reprenons l'exemple de recherche des de voici ce que nous pourrions faire pour prendre en compte tous les cas:

[dD][eE]

Les moteurs d'expressions régulières sont capables de reconnaître des raccourcis pour les plages de caractères. En effet, si nous voulons compter le nombre de lettres dans un texte, voici le motif que nous pouvons utiliser:

[a-zA-Z]

Ceci indique que nous recherchons toutes les lettres qui vont de a à z et de A à Z. Si nous voulions inclure aussi les chiffres, nous aurions alors:

[a-zA-Z0-9]

En informatique, on utilise souvent l'hexadécimal. L'hexadécimal correspond à des nombres en base 16 (comme le décimal correspond à une base 10). Un nombre codé en hexadécimal sera formé des chiffres 0 à 9 puis des lettres A à F. Voici l'expression régulière que nous pourrions avoir pour tester les valeurs hexadécimales allant de 00 à FF (soit de 0 à 255 en décimal):

[0-9a-fA-F][0-9a-fA-F]

Si par exemple un utilisateur saisit une valeur du genre 9Z (en commentant une faute de frappe), l'expression régulière ne sera pas vérifiée ce qui indiquera qu'il ne s'agit pas d'un nombre hexadécimal.

Imaginons maintenant que nous souhaitions retrouver toutes les lignes qui ne contiennent pas de chiffre, nous devons utiliser la classe de caractères complémentée qui s'écrit en utilisant au début le signe ^ comme ceci:

[^0-9]

Si nous souhaitons reconnaître le signe ^, il faudra le placer n'importe ou tant qu'il n'est pas placé en tête.

Pour reconnaître le signe -, celui-ci doit être placé à la fin de la classe comme ceci:

[0-9-]

Ceci permettra de reconnaître le caractère - ou les chiffres 0 à 9.

[modifier] Les méta caractères

Les méta caractères sont des caractères dont la signification n'est pas celle que l'on pourrait penser à première vue. Nous avons déjà vu au moins un méta caractère puisque le ^ de [^a-z] indique pas de lettre comprise entre a et z.

Voici un tableau résumant les méta caractères ayant une signification particulières avec les expressions régulières:

Méta caractères Signification
[...] un des caractères indiqués entre les crochets comme nous avons pu le voir
[^...] aucun des caractères indiqué après le ^
[x-y] accepter les caractères allant de x à y inclus
[:alnum:] équivalent à a-zA-Z0-9 avec en plus les caractères spéciaux que l'on retrouve suivant les langues commes les éèùçà... (norme POSIX)
[:alpha:] équivalent à a-zA-Z avec en plus les caractères spéciaux que l'on retrouve suivant les langues commes les éèùçà... (norme POSIX)
[:digit:] équivalent à 0-9 (norme POSIX)
[:lower:] équivalent à a-z avec en plus les caractères spéciaux que l'on retrouve suivant les langues commes les éèùçà... (norme POSIX)
[:upper:] équivalent à A-Z avec en plus les caractères spéciaux que l'on retrouve suivant les langues commes les ÂÛÔ... (norme POSIX)
[:xdigit:] équivalent à 0-9a-fA-F (norme POSIX)
[:graph:] tout caractère graphique (norme POSIX)
[:print:] tout caractère affichable (norme POSIX)
[:punct:] tout caractère de ponctuation (norme POSIX)
[:blank:] espace, tabulation (norme POSIX)
[:space:] espace, tabulation, nouvelle ligne, retour chariot (norme POSIX)
[:cntrl:] tout caractère de controle (norme POSIX)
\b limite de mot (norme Perl)
\B non-limite de mot (norme Perl)
\d équivalent à [0-9] (norme Perl)
\D équivalent à [^0-9] (norme Perl)
\n nouvelle ligne (norme Perl)
\r retour chariot (norme Perl)
\t tabulation (norme Perl)
\s espace, tabulation (norme Perl)
\S non espace ou tabulation (norme Perl)
\w un mot (norme Perl)
\W pas un mot (norme Perl)
. n'importe quel caractère
^ en début de motif, indique que l'expression sera tester à partir du début de ligne
$ en fin de motif, indique que l'expression sera tester à partir de la fin de ligne
+ 1 ou plusieurs caractères comme ce qui précède le + (qui peut être une classe de caractères)
* 0 ou plusieurs caractères comme ce qui précède le * (qui peut être une classe de caractères)
? 0 ou 1 caractère comme ce qui précède le ? (qui peut être une classe de caractères)
{n} n caractères parmi le caractère qui précède
{x,} x fois caractères ou plus parmi le caractère qui précède
{,y} y fois caractères au plus parmi le caractère qui précède
{x,y} x à y caractères sans distinction parmi le caractère qui précède
(...) Sous-expression (voir \N ci-dessous)
\N Référence arrière ou N est un chiffre pouvant aller de 1 à 9, chaîne mise en évidence par la Nième sous-expression
| l'une ou l'autre des sous-expressions situées de part et d'autre du caractère |

A noter tout de même que suivant le moteur de regex, il peut être nécessaire d'ajouter un \ devant chaque accolade {}, parenthèse ou pipe | afin que le moteur ne recherche pas ces méta caractères.

Nous allons voir maintenant comment utiliser les méta-caractères les plus utilisés.

Les signes ?, * et + sont très souvent utilisés dans les expressions régulières puisque vous écrirez régulièrement des choses du genre:

[a-zA-Z]*
[a-zA-Z]+
[a-zA-Z]?

Le premier cas signifie qu'il faut 0 ou un nombre indéfini de caractères a à z (majuscules ou minuscules).

Dans le second cas, le + signifie qu'il faut au moins un caractères entre a à z (majuscules ou minuscules).

Enfin, le dernier cas indique qu'il faut 0 ou 1 caractères (et pas plus). S'il y a 1 caractère, il doit avoir pour valeur une lettre de l'alphabet majuscules ou minuscules).

Il peux être pratique d'indiquer précisément qu'il faut n caractères au maximum. Un exemple simple serait que l'utilisateur doive saisir son nom mais que celui-ci ne doit pas dépasser 16 caractères. Un nom de moins de 2 lettres n'est pas considéré comme valide. Le plus simple est alors d'écrire le motif suivant:

^[[:alpha:]' -]{2,16}$

Ceci permettra d'accepter des noms avec des accents ou des apostrophes, des traits d'union ou des espaces.

Imaginions maintenant que nous souhaitions rechercher toutes les lignes qui contiennent le mot connexion. Il arrive souvent que les personnes l'écrivent comme en anglais soit connection. Si nous souhaitons rechercher de telles lignes, nous pourrions écrire ceci:

conne(x|ct)ion

Comme nous le voyons, c'est le | qui a indiqué qu'il fallait soit x, soit ct. Nous aurions pu avoir d'autres possibilités séparées elles aussi par des |.

Il est parfois pratique avec les expressions régulières de rechercher des syllabes doublées comme ceci:

  • papa
  • baba
  • tata

Nous pourrions réaliser cela avec ceci:

(pa|ba|ta)\1

Le \1 est une référence arrière. Elle signifie "à cet endroit il faut avoir la même chaîne que la chaîne mise en évidence entre le premier jeu de parenthèses".

Pour essayer d'entre plus clair, voici un autre exemple. Nous avons une page HTML avec des balises HTML du genre <b>...</b>, <i>...</i> et ... (pour italique, gras et souligné). Pour rechercher ces balises, nous pourrions tester tous les cas pour ne pas avoir par exemple une balise <i> avec une balise symétrique </b>, mais ça fait beaucoup de possibilités. Le plus simple est de faire ceci:

<(i|b|u)>.*</\1>

Toutes les lignes sans symétrie seront ignorées. Une balise du genre:

<i>Mon texte en italique</b>

ne sera donc pas pris en compte.

[modifier] Déboguer une expression régulière

Le déboguage d'une expression régulière est un passage obligé lorsque l'on utilise ce puissant langage.

Pour déboguer une expression régulière, le plus simple est de ne pas voir trop grand en écrivant une expression qui fera 20 ou 30 lignes et sera ainsi totalement illisible. Le but d'une expression régulière est de simplifier le travail du développeur autant que possible. Lorsque le développeur ne se sent pas capable de reprendre facilement l'expression qu'il vient d'écrire pour corriger un bogue éventuel dans un mois, il est conseillé d'essayer de décomposer le travail effectué par cette expression régulière en plusieurs instructions.

Ensuite, il ne faut pas hésiter à ajouter de nouveaux éléments au motif au fur et à mesure de vos connaissances et de votre travail. Vous commencez par exemple votre expression avec 10 ou 20 caractères et lorsque cela fonctionne bien, vous ajoutez le reste du motif. En prenant l'exemple de l'adresse email, vous pourriez faire votre motif tout d'abord comme ceci:

[a-zA-Z][-._a-zA-Z0-9]

pour rechercher le nom de l'utilisateur. Ensuite, si cela fonctionne bien avec plusieurs possibilités de noms, vous pourriez ajouter le nécessaire pour rechercher l'hôte.

[a-zA-Z][-._a-zA-Z0-9]*@[a-zA-Z][-a-zA-Z0-9]*\.[a-zA-Z]{2,4}

Enfin, n'hésitez pas à bien commenter votre code en expliquant par exemple l'utilité de chaque morceau du motif. Même si ce n'est pas pour vous, cela pourra toujours resservir aux personnes qui devront comprendre vos développements.

[modifier] Les liens utiles

Vous trouverez ci-dessous quelques liens utiles sur les expressions régulières.

[modifier] Historique de l'article

Cet article, réalisé par Stéphane VANPOPERYNGHE, a été publié pour la première fois le 30 juin 2003 sur le site ToutProgrammer.com (1ème version).