Note : expressions régulières

Sous-sections :

Les expressions régulières (ou "expressions rationnelles", en anglais "regular expression" ou "regexes", "regexp", etc.) permettent de manipuler (recherche et remplacement) du texte en utilisant des caractères spéciaux, qui valent pour une classe de cas et non littéralement. Perl en offre le meilleur support, mais des éditeurs de textes courants permettent également de les utiliser.

Les regexes ne sont cependant pas un langage : elles ne peuvent pas faire de boucle, ou agir de façon conditionnelle. Le pattern est générique mais statique. Elles sont surtout utiles dans des cas prévisibles et normalisés : paragraphe, ponctuation, encodage...

Il existe de nombreuses excellentes introductions aux expressions régulières et des documentations dans les logiciels qui les implémentent, dont une sélection est proposée ci-dessous ; on se limite ici à des introductions puis à des exemples avec trois environnements : Emacs (Windows / Linux), Textpad (Windows), et Perl. La syntaxe des expressions régulières peut varier très légèrement d'un éditeur ou d'un programme à l'autre, mais les fonctions restent les mêmes. Word n'est pas un éditeur de texte mais implémente un sous-ensemble des expressions régulières. Les exemples ci-dessous sont dans la syntaxe de Perl, ils sont traduits à la fin de cette partie dans les idiomes légèrement différents des autres outils présentés.


Liens et tutoriaux


Introduction en 5 paragraphes


Plus en détails

1. Recherche littérale - Les expressions régulières permettent de rechercher des caractères littéralement : les caractères sont désignés par le symbole correspondant, a recherche le caractère "a", etc. Des caractères "non visibles" comme le retour chariot, la tabulation, etc. peuvent être désignés littéralement par un caractère spécial : \n pour le retour chariot et \t pour la tabulation. L'anti-slash forme un seul symbole avec le caractère qui suit, qui ne vaut plus littéralement pour un "n" ou un "t". L'anti-slash lui-même n'est pas l'équivalent littéral du caractère "\". Pour désigner un anti-slash littéralement, il faut le faire précéder lui même d'un anti-slash : \\ recherche "\". Les "méta-caractères" de cette sorte sont généralement \, ., *, ?, +, ^, $, {, |, (, ), et }, dont on voit ci-dessous le sens spécial ; pour les utiliser littéralement il faut donc utiliser \\, \., \*, \?, \+, \^, \$, \{, \|, \(, et \). L'anti-slash permet d'interpréter un méta-caractère comme un caractère littéral, mais également de donner un sens spécial à des caractères littéraux, comme \n ou \t.

2. Classes d'éléments - Les expressions régulières permettent également de ne pas faire correspondre les élément un à un, mais de faire correspondre une liste de possibilités à un élément. a/ Une classe de caractères en alternative est désignée entre crochets : [aeiouy] recherche une occurrence d'une voyelle : elle trouvera deux éléments dans "soir". - Pour ne pas écrire de longue suite de caractères, on peut spécifier des plages avec le tiret : [0-9] recherche tout caractère numérique, [a-z] toute lettre en minuscule, [A-Za-z0-9] recherche tout caractère alphanumérique. Il faut noter que les plages de caractères sont basées sur l'ASCII, il faut donc ajouter les caractères qui n'en font pas partie, comme les caractères accentués : [a-zéèàùâêîôûäëïöüçA-ZÉÈÀÙÂÊÎÔÛÄËÏÖÜÇ0-9] pour être (presque) exhaustif. Mais, par chance, on peut alors utiliser le point . qui vaut pour tout élément sauf le retour chariot (\n). [aeiouy]. trouvera "az", "aa", "a!" mais pas "a" ni "ba". Pour mettre un tiret littéralement parmi les éléments en alternative, il faut le mettre en premier dans la liste : [-,;] recherche un tiret, une virgule ou un point virgule (pour "]" utiliser l'antislash : [-\]]). - Les méta-caractères perdent leur sens particulier dans une expression entre crochets. b/ Des symboles spéciaux permettent de désigner les classes les plus souvent utilisées, par exemple en Perl \w = word, tout caractère de mot (alphanumériques plus '_'), \d = digit ([0-9]), \s tout caractère blanc (= [ \t\n\r\f], soit espace, tabulation, saut de ligne, retour chariot, saut de page) ; lesquels peuvent être inversés \W = tout caractère non-mot, \D = tout non numérique, \S tout caractère autre que blanc, etc. (cf la page de manuel Perl : http://www.Perldoc.com/Perl5.8.0/pod/Perlre.html#Regular-Expressions) - Autres familles de classes, du type : [:alnum:] par exemple pour tout alphanumérique ([A-Za-z0-9]) c/ L'alternative entre des éléments de plusieurs caractères est exprimée par |, et s'applique aux éléments entre parenthèses : (oui|non) permet de rechercher "oui" ou "non". Avec les crochets, le contenu est évalué comme une liste de caractères, ainsi l'alternative | est inutile : [truc|tric] = [truci] - le caractère ^ en début de plage permet d'indiquer une assertion négative : [^a-z] recherche tout ce qui n'est pas un alphabétique minuscule.

3. Quantifieurs - Possibilité de préciser le nombre d'occurrences d'un élément (un caractère littéral, ou une classe d'éléments en alternative, ou une chaîne de caractères entre parenthèses). Les principaux quantificateurs sont ? (0 ou 1 fois), + (1 ou plusieurs fois), * (0 ou plusieurs fois). On peut également spécifier une plage d'occurrence : [aeiouy]{3,5} désigne toute suite de entre 3 et 5 voyelles. Si le second caractère n'est pas spécifié, il vaut pour l'infini : [0-9]{5,} recherche au moins 5 numériques de suite. On voit que ? = {0,1}, + = {1,} et * = {0,}. Une valeur seule entre accolades exige une valeur exacte : a{5} recherche exactement 5 "a" de suite. Les quantificateurs s'appliquent à la dernière unité syntaxique les précédents. non{5} trouvera "nonnnnn" mais non pas "nonnonnonnonnon" pour cela il faut écrire (non){5} Note : Les expressions rationnelles sont par défaut avides : dans "abbb", ab+ trouvera "abbb" et non "ab". Si on veut éviter ce comportement, on peut utiliser la négation : <[^>]+> par exemple pour ne pas prendre le début d'une balise et la fin de la dernière balise sur la même ligne. Avec Perl, on peut également préciser au quantificateur de chercher la séquence minimale en ajoutant "?" : +?, *? et {1,5}? cherche donc la première correspondance. D'autre part une expression rationnelle cherche l'expression dès que possible, et s'arrête dès que les conditions sont satisfaites même si une correspondance plus loin serait plus complète. Aussi y* trouve un motif vrai dès le premier caractère de "xyz", puisqu'il peut correspondre à une chaîne nulle.

4. Référence - Les parenthèses permettent de hiérarchiser les membres d'une alternative (cf. 2), et de regrouper une chaîne de caractères en un élément (cf. 3), mais servent également à mettre en mémoire le résultat. Ce qui permet par la suite de faire référence au contenu d'une expression entre parenthèses à l'aide de \1 (pour désigner le contenu d'une première parenthèse dans l'expression de recherche), \2 (pour désigner le contenu d'une seconde), etc. Ainsi ([^ ]+) \1 recherche un mot répété : un blanc, toute suite d'au moins un caractère qui n'est pas un blanc, un blanc, et la même suite de caractères. Les références de cette sorte sont également utiles pour remplacer : faire référence dans l'expression de remplacement au contenu trouvé dans l'expression de recherche : remplacer ([0-9]+)([a-zA-Z]+) par \1 \2 permet par exemple d'ajouter un espace entre une suite de numériques et une suite d'alphabétiques. Remplacer ([a-z])([;:\?!]) par \1 \2 ; permet d'ajouter un espace entre la fin d'un mot et les signes de ponctuation qui demandent cet espace. (anti-slash avant le ? pour lui faire rechercher le caractère "?", puisque c'est un caractère réservé).

5. Assertion - De nombreuses expressions permettent d'inclure des assertions, qui ne correspondent pas à un élément à rechercher mais indiquent par exemple que l'expression commence en début de ligne (^) ou en fin de ligne ($), etc. Ces assertions sont la partie la plus variable des expressions régulières entre les différents logiciels, il faut se reporter à la documentation correspondante.


Exemples d'expressions régulières

Les exemples sont notés m/expression de recherche/ (m = match) et s/expression de recherche/expression de remplacement/ (s = substitute), dans la syntaxe de Perl. Une traduction ci-dessous montre les expressions équivalentes dans les implémentations de Emacs et Texpad.

Supprimer des espaces

s/ +/ / Remplacer plusieurs espaces par un seul.

s/ +/ /g Le modificateur "g" (pour global) permet de faire tous les remplacements d'espaces multiples, et non seulement la première occurrence.

s/[ \t]+/ /g; Supprimer toute combinaison d'espaces et tabulations.

Ebarber des lignes (enlever les espaces en début et en fin de ligne)

s/ +$//gm; Supprimer un espace en fin de ligne. Le modificateur 'm' est nécessaire pour traiter la chaîne comme "multiligne", et pour que '$' corresponde à chaque fin de ligne et non à la fin de la chaîne entière.

s/[ \t]+$//gm Supprimer toute combinaison d'espace et tabulation en fin de ligne

s/(^[ \t]+)|([ \t]+$)//gm; Supprimer toute combinaison d'espace et tabulation en début ou en fin de ligne

Supprimer les sauts de ligne

s/\n{2,}/\n/g; Remplace les sauts de lignes multiples par un seul saut de ligne.

s/([ \t]*\n){2,}/\n/g Plus rigoureux : supprime les lignes vides ou ne contenant qu'une combinaison de blancs et de tabulations.

Ajouter un tag paragraphe

s/^(.+)$/<p>\1<\/p>/gm; pour ajouter un tag <p> ouvrant et fermant à chaque paragraphe. Ce qui est contenu entre chaque début et chaque fin de ligne est mis de côté avec "(.+)". On y fait référence dans l'expression de remplacement avec \1, en l'encadrant de la balise ouvrante et fermante. L'antislash avant "/p" est nécessaire à l'interpréteur pour ne pas confondre ce slash littéral avec le slash de fin de la syntaxe s///;

s/^([^<])(.+)$/<p>\1\2<\/p>/gm; si on veut éviter d'ajouter ce tag aux lignes commençant déjà par une balise (par exemple, on a des balises <div> ou <head> qu'il ne faut pas encadrer d'un <p>). On recherche une ligne qui ne commence pas par un chevron ouvrant : "[^<]". Le résultat est mis de côté (avec des parenthèses) pour être recollé dans le remplacement.

s/^([ \t]+)?([^< \t])(.+)$/\1<p>\2\3<\/p>\n/gm; Si les lignes ont été indentées, il faut prévoir d'éventuels blancs en début de ligne et ajouter "([ \t]+)?".

Renommer un tag

s/<(\/?)nom_du_tag(.*?)>/<\1nouveau_nom\2>/g; Pour renommer toutes les occurrences d'une balise, on veut sauvegarder les éventuels attributs et l'éventuel slash de fermeture. L'éventuel slash de fermeture ("\/?") est reporté avec \1 ; (.*?) permet de faire une recherche "non gourmande" : on prend tout ce qui précède le premier ">" rencontré. "<nom_du_tag(.*)>" seul ne marcherait pas s'il y a plusieurs balises sur la ligne : le ">" final pourra aller chercher la fin d'une autre balise si la ligne en contient.

Rechercher le contenu d'un tag

m/<([^\s]+)\s+[^>]*?rend=(["'])italics\2[^>]*?>((.*?|\s*?)*?)<\/\1>/g On recherche toute section encadrée par des balises où il y a un attribut "rend" avec la valeur "italics". On cherche une balise ouvrante ("<"), on isole et mémorise le nom du tag en cherchant les premiers caractères jusqu'à un espace ("([^\s]+)" (il peut y avoir même un saut de ligne entre le nom du tag et ses attributs), et on fait référence au même nom grâce à "\1" à la fin de l'expression (précédé du slash de fermeture : "\/"). On prévoit après le nom des blancs (\s+), éventuellement d'autres attributs ("[ >]*?") avant "rend=(["'])italics\2". On prévoit avec ['"] que la valeur de l'attribut peut être marquée entre apostrophes ou guillemets, mais la fermeture doit correspondre à l'ouverture ("\2"). Après l'attribut "rend" on prévoit encore d'autres éventuels attributs ([^>]*). Enfin on prévoit entre le tag d'ouverture et de fermeture toute combinaison, la moins longue possible ("*?"), de caractères et de sauts de ligne ("(.*?|\n*?)")

Note : si plusieurs balises de même nom sont enchaînées, par exemple <seg rend="italic"><seg></seg></seg>, l'expression sélectionnera <seg rend="italic"><seg></seg> et non <seg rend="italic"><seg></seg></seg>. On voit ici la limite des expressions régulières et la nécessité d'utiliser un analyseur syntaxique dès qu'on ne manipule plus le texte mais la logique XML.


Traduction des exemples dans les syntaxes de Perl et Textpad

Les éléments qui varient le plus entre logiciels sont régulièrement :

Correspondance des exemples proposés

EN COURS

Perl Textpad s/ +/ / Rechercher : + ; Remplacer s/ +/ /gid., utiliser la toucher "remplacer tout" s/[ \t]+/ /g;Rechercher : [ \t]+ ; Remplacer s/ +$//gRechercher : +$ ; Remplacer s/[ \t]+$//gRechercher : [ \t]+$ ; Remplacer s/(^[ \t]+)|([ \t]+$)//gm;Rechercher : \(^[ \t]+\)\|\([ \t]+$\) ; Remplacer s/\n{2,}/\n/g;Rechercher : ; Remplacer s/([ \t]*\n){2,}/\n/gRechercher : ; Remplacer s/^(.+)$/<p>\1<\/p>/gm;Rechercher : ^\(.+\)$ ; Remplacer<p>\1<\/p> s/^([^<])(.+)$/<p>\1\2<\/p>/gm;Rechercher : ^\([^<]\)\(.+\)$ ; Remplacer<p>\1<\/p> s/^([ \t]+)?([^> \t])(.+)$/\1<p>\2\3<\/p>\n/gm;Rechercher : ^\([ \t]+\)?\([^< \t]\)\(.+\)$ ne marche pas vraiment : conditionnel mal implémenté ; Remplacer s/<(\/?)nom_du_tag(.*?)>/<\1nouveau_nom\2>/g;Rechercher : <\(\/?\)ancien_tag\(.*?\)> pb également; Remplacer m/<([^\s]+)\s+[^>]*?rend=(["'])italics\2[^>]*?>((.*?|\s*?)*?)<\/\1>/gRechercher : ; Remplacer

Mettre en oeuvre les expressions régulières avec Textpad, Emacs et Perl

Sous-sections :


Textpad

Le logiciel propose une "boîte de dialogue" Chercher (menu Recherch->Chercher, touche F5) et une boîte de dialogue Remplacer (menu Recherch->Remplacer, touche F8 ou Ctrl-H) avec un champ "Rechercher" et un champ "Remplacer". Pour bénéficier des expressions régulières il faut cocher la case correspondante parmi les options.

Figure 1 : Recherche d'une regexe avec Textpad


Emacs

Figure 2 :Recherche d'une regexe avec Emacs. Ici, avec C-M-s (Ctrl-Alt-s), on parcourt toutes les balises.

Figure 3 : Remplacement d'une regexe avec Emacs (C-M-% (Ctrl-Alt-%)). Si le remplacement est correct, on peut demander "!" pour remplacer toutes les occurrences restantes.


Perl

Documentation pour l'utilisation des expressions rationnelles dans un programme Perl :

Principaux éléments :

Depuis la ligne de commande

Perl est une solution simple à mettre en oeuvre pour les regexes, puisqu'il n'y a pas besoin d'ouvrir un éditeur ni même le fichier. On peut effectuer directement un simple remplacement depuis la ligne de commande, sans écrire le programme dans un fichier :

Perl -e "s/(oui|yes|si)/non/g" -p -i.bak fichier.xml

L'interpréteur exécute le bout de code entre guillemets (c'est l'effet de "-e"). Il effectue le remplacement dans le fichier donné en argument (fichier.xml), et sauvegarde l'ancien fichier dans "fichier.xml.back".

Pour utiliser les regexes dans un script

Les principaux modificateurs

Les principales classes

\w			Match a "word" character [a-zA-Z_]
\W			Match a non-"word" character [^a-zA-Z_]
\s			Match a whitespace character [ \t\n\r\f]
\S			Match a non-whitespace character [^ \t\n\r\f]
\d			Match a digit character [0-9]
\D			Match a non-digit character [^0-9]
\t			tab
\n			newline
\r			return
\f			form feed