dernière modification : 2023

Expressions régulières

Une expression régulière (regex) est un motif (pattern) destiné à identifier des chaînes de caractères. Donc, étant donné une regex, une chaine de caractères correspond (match) ou ne correspond pas à cette regex.

Concept issu de travaux théoriques en neuroscience (Stephen Kleen, 1956), les regex sont implémentées informatiquement pour la première fois par Ken Thompson dans le premier éditeur de texte d’Unix (ed), pour rechercher et substituer des portions de texte. Depuis, elles sont devenues une abstraction à part entière, apparessant un peu partout en informatique : de nombreuses commandes en ligne les comprennent, nombre de configurations logicielles les utilisent dans leur formulation, et la plupart des langages de programmation permettent au développeur de les exploiter.

Le formalisme concis des regex, bien que concourant à leur réputation ésotérique, offre des possibilités élégantes et puissantes de traitement de texte.

1. Aperçu

Une regex permet d’identifier finement un ensemble de chaînes de caractères. La regex est elle-même une chaîne de caractères. Par exemple, la regex defg représente toute chaîne de caractères contenant la chaîne "defg", comme par exemple :

abcdefg
defghi
abcdefghi
defg

Par contre, les chaînes ci-dessous ne s’idenfient pas à la regex defg :

def          # ne contient pas g
efg          # ne contient pas d
ddeeffgg     # ne contient pas def
gfed         # ne contient pas de

La commande grep

La commande Unix grep affiche les lignes d’un (ou plusieurs) fichier s’identifiant à la regex passée en argument. Par exemple :

$ grep net /etc/protocols

affiche toutes les lignes du fichier /etc/protocols s’identifiant à la regex net.

Des options peuvent modifier le comportement de grep. Par exemple -i signifie de ne pas différencier les minuscules des majuscules :

$ grep -i internet /etc/protocols

De même, -v demande à grep d’afficher les lignes ne correspondant pas à la regex.

Jocker, quantificateur, délimiteur

Quelques caractères jouent un rôle particulier, différent de leur propre représentation. Par exemple . ne représente pas le caractère point.

Historique

Il existe plusieurs types de regex (avec une compatibilité ascendante ) : les regex de base, les regex étendues et les regex de Perl, apparues dans cet ordre chronologique.

Les regex de base sont apparues à la naissance d’Unix. C’est le standard historiquement supporté par UNIX et ses commandes depuis l’origine.

Les regex étendues complètent les regex de base, en introduisant de nouveaux caractères spéciaux, pour plus de fonctionnalités. Les regex étendues sont un sur-ensemble des regex de base.

Même chose avec les regex de Perl, qui complètent les regex étendues. Perl est un langage de script créé spécialement pour traiter efficacement les chaînes de caractères, et dont le champ d’action s’est élargi au fil du temps, au point de devenir un langage généraliste (manipulant efficacement les regex).

2. Regex de base

C’est le socle de base.

Table 1. Caractères spéciaux d’une regex de base
car. type desc.

.

jocker

1 caractère quelconque

^

délimiteur

début de ligne

$

délimiteur

fin de ligne

*

quantificateur

caractère précédent répété 0 ou plusieurs fois

\

modificateur

litéralise le caractère suivant

Le joker .

Le caractère "." est un jocker : il représente un caractère quelconque. Les chaînes suivantes :

defg
dafg
dbfg
dcfg
ddfg
d.fg
abcd fghi

s’identifient à la regex d.fg, au contraire des chaînes ci-dessous :

dfg             # pas de caractère entre d et f
dxyfg           # trop de caractères en d et f
abcd  fghi      # trop de caractères en d et f

qui ne s’y identifient pas.

Le modificateur \

Le caractère "\" agit sur le caractère qui le suit dans la regex :

  • si ce caractère est particulier, alors il perd son super-pouvoir, et devient un caractère ordinaire,

  • si ce caractère est ordinaire, alors il le reste (le modificateur n’a donc pas d’effet dans ce cas).

Les délimiteurs ^ et $

Le caractère ^ en début de regex matérialise le début de la chaîne. Symétriquement, le caractère $ en fin de regex matérialise la fin de la chaîne. Ainsi :

  • ^a identifie toute chaîne commençant par "a",

  • b$ identifie toute chaîne finissant par "b".

Le quantificateur *

Le caractère "*" dans une regex signifie que le caractère qui le précède dans la regex est répété 0, 1 ou plusieurs fois. Ainsi :

  • a* signifie : 0, 1 ou plusieurs "a".

  • ab*c signifie : "a" suivi de 0, 1 ou plusieurs "b", suivi de "c".

regex vs glob

Les shells Unix utilisent des patterns (appelé glob) pour identifier des ensembles de noms de fichiers. glob et regex ont le même objectif, mais sont différents et incompatibles.

Les globs admettent seulement 2 jokers : "?" et "*". "?" représente un unique caractère quelconque et "*" représente une suite de caractères quelconques (éventuellement vide).

Ainsi :

  • le glob a??b est équivalent à la regex ^a..b$

  • le glob a*b est équivalent à la regex ^a.*b$

  • le glob *conf* est équivalent à la regex conf

3. Regex étendues

Elles introduisent de nouveaux caractères spéciaux pour plus de fonctionnalités.

Table 2. Elément d’une regex étendue
car. type desc.

|

alternative

soit les caractères précédents, soit les caractères suivants

( )

regroupement de caractères

+

quantificateur

caractère précédent répété au moins une fois

?

quantificateur

caractère précédent répété au plus une fois

{n}

quantificateur

caractère précédent répété n fois

{n,m}

quantificateur

caractère précédent répété entre n et m fois

{,n}

quantificateur

caractère précédent répété au plus n fois

{n,}

quantificateur

caractère précédent répété au moins n fois

[abcde]

alternative

1 caractère parmi ceux énumérés

[a-e]

alternative

1 caractère dans l’intervalle

[a-z0-9]

alternative

1 caractère dans les intervalles

[^a-z0-9]

alternative

1 caractère en dehors des intervalles

Pour utiliser les regex étendues avec grep, il faut spécifier l’option -E. Ou bien utiliser la commande egrep.

L’alternative |

  • a|b signifie : a ou b.

  • ab|cde signifie : ab ou cde.

Ainsi, les chaînes "viability", "undeavour", "stabilate" et "cadeau" s’identifient à la regex abil|dea.

Le groupement ( )

Les parenthèses regroupent plusieurs caractères. Ainsi :

  • (ab)* signifie : 0 ou plusieurs occurrences de ab

  • a(b|cd)e signifie : a, suivi de b ou cd, suivi de e.

Ainsi, les chaînes "stabilate" et "cadeau" s’identifient à la regex a(bil|de)a, mais pas "viability" ni "undeavour".

Le quantificateur +

Au moins 1 fois ce qui précède. Ainsi :

  • a+ signifie : 1 ou plusieurs "a"

  • ab+c signifie donc : "a", suivi de 1 ou plusieurs "b", suivi de "c"

a+ est donc un raccourci pour aa*.

Le quantificateur ?

Au plus 1 fois ce qui précède. Ainsi :

  • a? signifie : 0 ou 1 "a"

  • ab?c signifie donc : "a" suivi de 0 ou 1 "b", suivi de ""

Les quantificateurs { }

Au plus et au moins. Ainsi :

  • a{n} signifie "a" répété n fois

  • a{n,m} signifie "a" répété entre n et m fois

  • a{,n} signifie "a" répété au plus n fois

  • a{n,} signifie `

Sont donc équivalents :

  • a{0,} et a*

  • a{1,} et a+

  • a{,1} et a?

L’alternative [ ]

Elle permet de définir 1 caractère unique pris dans un ensemble donné.

[abcd] signifie : 1 caractère parmi "a", "b", "c" et "d"

[a-d] signifie : 1 caractère parmi "a", "b", "c" et "d"

[a-dkx-z] signifie : 1 caractère parmi "a", "b", "c", "d", "k", "x", "y" et "z"

[^abcd] signifie : 1 caractère différent de "a", "b", "c" et "d"

[^a-d] signifie : 1 caractère différent de "a", "b", "c" et "d"

[^a-dx-z] signifie : 1 caractère différent de "a", "b", "c", "d", "x", "y" et "z"

Captures

En réalité, les parenthèses groupent, mais aussi capturent la portion qu’elles délimitent. La capture est mémorisée, dans le but d’être utilisée plus tard. On récupère la capture via \1. Exemple :

  1. a|b identifie le caractère "a" ou le caractère "b"

  2. (a|b) capture le caractère qui s’est identifié (donc soit "a", soit "b")

  3. \1 sera donc soit "a", soit "b"

Exemple complet :

  • ^(a|b)\1$ signifie donc soit "aa", soit "bb", mais pas "ab" ni "ba".

Plusieurs captures sont possibles, et dans ce cas, on les récupère séquentiellement via \1, \2, \3, etc. Exemple :

  • (a|i)\1(r|s)\2 identifie toutes les chaines contenant l’un des motifs suivant :

aarr
aass
iirr
iiss

Par défaut, les quantificateurs sont gourmands, c’est-à-dire qu’ils identifient à la chaîne la plus longue possible. Par exemple, dans la chaine "abccccde", la portion capturée par la regex (c*) pourrait être :

** rien **
c
cc
ccc
cccc

mais les quantificateurs étant gourmands, la portion capturée est finalement "cccc".

4. Perl regex

Le langage Perl ajoute de nouvelles fonctionnalités aux regex étendues, dont quelques unes des plus simples sont listées ci-dessous.

Pour utiliser les regex de Perl avec grep, il faut spécifier l’option -P.

Les regex Perl offrent des délimiteurs supplémentaires :

\b

une fontière de mot

\B

une non-fontière de mot

Elles amènent aussi quelques alternatives :

\d

un chiffre ([0-9])

\D

un non-chiffre ([^0-9])

\s

un séparateur blanc ([ \t\n])

\S

un non-séparateur blanc ([^ \t\n])

\w

un caractère alphanumérique ([a-zA-Z0-9_])

\W

un caractère non alphanumérique ([^a-zA-Z0-9_])

Elles offrent également des quantificateurs non gourmands :

*?

0 ou plusieurs occurrences, la plus courte possible

+?

1 ou plusieurs occurrences, la plus courte possible

Par exemple, étant donnée la chaine "abcabc", alors la regex (a.*c) capture "abcabc" alors que la regex (a.*?c) capture "abc".