Expressions régulières
Progression
#Expressions régulières
Les expressions régulières (regex) sont des suites de caractères qui forment un motif de recherche. Elles sont utilisées pour rechercher, extraire ou remplacer des portions de texte dans des chaînes.
#Ce que les regex peuvent (et ne peuvent pas)
Elles reconnaissent des langages réguliers: motifs à structure plate (répétitions, alternatives). Elles ne gèrent pas naturellement des structures récursives (parenthèses équilibrées générales, HTML bien formé). Pour ces cas, utilisez un parseur.
Objectifs d’apprentissage
- Concevoir des motifs robustes et lisibles (ancrages, classes, quantificateurs).
- Éviter le backtracking catastrophique et maîtriser la complexité.
- Utiliser des options sûres (mode multi‑ligne, flags) et valider des entrées prudemment.
#Syntaxe de base
- Littéraux :
a
,b
,1
,@
correspondent à eux-mêmes. - Classes de caractères :
[abc]
: un des caractèresa
,b
, ouc
.[a-z]
: une lettre minuscule.[0-9]
ou\d
: un chiffre.[^\d]
ou\D
: tout sauf un chiffre.
- Quantificateurs :
*
: 0 ou plus.+
: 1 ou plus.?
: 0 ou 1.{n}
: exactement n fois.{n,}
: n fois ou plus.{n,m}
: entre n et m fois.
#Groupes et capture
Les parenthèses ()
définissent des groupes et capturent le texte correspondant.
#Exemple (Python)
1import re2 3# Groupes capturants4text = "John Doe, 25 ans"5match = re.search(r"(\w+) (\w+), (\d+) ans", text)6if match:7 print("Prénom:", match.group(1))8 print("Nom:", match.group(2))9 print("Âge:", match.group(3))
#Animation: ordre et portée
#Remplacements
Les expressions régulières permettent de remplacer des parties de texte.
#Exemple (Python)
1import re2 3# Remplacement4text = "Il y a 3 pommes et 5 oranges."5new_text = re.sub(r"\d+", "X", text)6print(new_text) # Il y a X pommes et X oranges.7 8# Remplacement avec groupes9text = "Date: 2023-10-05"10new_text = re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\3/\2/\1", text)11print(new_text) # Date: 05/10/2023
#Parsing de logs HTTP
Les expressions régulières sont souvent utilisées pour parser des logs.
#Exemple (Python)
1import re2 3log = '127.0.0.1 - - [20/Oct/2023:13:55:36 +0000] "GET /index.html HTTP/1.1" 200 2326'4pattern = r'(\d+\.\d+\.\d+\.\d+) - - \[([^\]]+)\] "(\w+) ([^ ]+) HTTP/(\d\.\d)" (\d+) (\d+)'5 6match = re.match(pattern, log)7if match:8 ip, date, method, path, version, status, size = match.groups()9 print(f"IP: {ip}")10 print(f"Date: {date}")11 print(f"Method: {method}")12 print(f"Path: {path}")13 print(f"Version: {version}")14 print(f"Status: {status}")
#Performance et sûreté
- Backtracking catastrophique: certains moteurs (backtracking NFA) peuvent explorer exponentiellement. Évitez des quantificateurs gourmands imbriqués comme
(a+)+
sur des entrées proches mais non valides. - Ancrages: utilisez
^
et$
(ou\A
/\Z
) pour contraindre le motif et réduire l’exploration. - Options: flags multi‑ligne/insensible à la casse influencent les correspondances et les performances; soyez explicite.
- Variantes d’engins: DFA (sans backtracking) vs NFA backtracking (PCRE‑like). Le comportement diffère sur la complexité et les captures; gardez‑le en tête en changeant d’environnement.
#Playground: backtracking coûteux (démonstration courte)
Contraignez le motif (ancrages), préférez des quantificateurs spécifiques (^a+$
plutôt que (a+)+
), limitez la portée des alternatives (^(?:abc|abd)$
plutôt que des motifs vagues), et testez sur des entrées « cassées ».
#Rétro-références
Une rétro-référence permet de faire référence à un groupe capturant dans la même expression.
#Exemple (Python)
1import re2 3# Trouver des mots répétés4text = "C'est un un test."5match = re.search(r"\b(\w+)\s+\1\b", text)6if match:7 print("Mot répété trouvé:", match.group(1)) # un
#Exercice : Validation d'adresses email
Implémentez une expression régulière pour valider des adresses email simples. L'adresse doit contenir un @
et un .
dans le domaine.
#Instructions
- L'adresse email doit commencer par des caractères alphanumériques ou des underscores.
- Elle doit contenir un
@
. - Le domaine doit contenir des caractères alphanumériques, des points ou des tirets.
- Elle doit se terminer par un point suivi de 2 à 4 caractères alphabétiques.
Une validation « parfaite » d’email est complexe (RFC 5322). Utilisez une vérification raisonnable côté client, et faites autorité côté serveur (tentative d’envoi ou librairies robustes). L’exemple ci‑dessous couvre les cas simples.
#Exemple de code
1import re2 3def validate_email(email: str) -> bool:4 # Simplifiée: nom@domaine.tld, prend en charge sous‑domaines et TLD 2–245 pattern = r'^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,24}$'6 return re.match(pattern, email) is not None7 8# Exemples9emails = [10 "test@example.com",11 "invalid.email",12 "user@domain.co.uk",13 "john+news@sub.mail.example",14]
#Bonus: drapeaux et performances
- Drapeaux utiles:
re.IGNORECASE
(i
),re.MULTILINE
(m
),re.DOTALL
(s
). - Pré‑compiler:
regex = re.compile(pattern)
quand réutilisé dans une boucle.
#Pièges et défenses
- Backtracking catastrophique:
(a+)+$
suraaaa...b
→ temps exponentiel. - Ambiguïtés: motifs non ancrés +
.*
agressif. - Défenses: ancrer (
^...$
), préférer des motifs spécifiques, utiliser des groupes atomiques/possessifs si supportés, fixer des timeouts côté moteur.
#Mini‑quiz
1import re, time2 3pattern = re.compile(r"(foo|bar|baz)+")4texts = ["foobarbaz" * 1000 for _ in range(500)]5 6t0 = time.time()7count = sum(1 for t in texts if pattern.search(t))8print(count, "matches in", round(time.time()-t0, 3), "s")
#Moteurs pratiques vs théorie
Dans la pratique, les moteurs de regex incluent des extensions (ex: lookaheads, lookbehinds) non présentes dans la théorie des langages.
- La correspondance regex peut être coûteuse (jusqu'à O(2^n) dans le pire cas avec certaines extensions).
- Pour de meilleures performances, privilégier les expressions simples et les bibliothèques optimisées.