Aller au contenu principal

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ères a, b, ou c.
    • [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)

pythonpython
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))
Chargement de l’éditeur...

#Animation: ordre et portée

Littéraux/Classes
a, [a-z], \d, \w
Concaténation
ab = "a" puis "b"
Alternatives
a|bc — la portée compte
Quantificateurs
*, +, ?, {n,m} (gourmands)
Ancrages
^, $, \b

#Remplacements

Les expressions régulières permettent de remplacer des parties de texte.

#Exemple (Python)

pythonpython
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
Chargement de l’éditeur...

#Parsing de logs HTTP

Les expressions régulières sont souvent utilisées pour parser des logs.

#Exemple (Python)

pythonpython
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)

Chargement de l’éditeur...
Stratégies anti‑backtracking

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 ».

Chargement de l’éditeur...

#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)

pythonpython
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
Chargement de l’éditeur...

#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

  1. L'adresse email doit commencer par des caractères alphanumériques ou des underscores.
  2. Elle doit contenir un @.
  3. Le domaine doit contenir des caractères alphanumériques, des points ou des tirets.
  4. Elle doit se terminer par un point suivi de 2 à 4 caractères alphabétiques.
Attention aux regex email

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

pythonpython
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]
Chargement de l’éditeur...

#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+)+$ sur aaaa...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

Quel motif est le plus sûr pour « chiffres seulement » ?
Quel motif est le plus sûr pour « chiffres seulement » ?
pythonpython
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.

Complexité
  • 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.