ANNEXE 1 · PRÉREQUIS

Expressions Régulières

Des automates à grep, en passant par sed :
ce que tout ingénieur devrait savoir avant de tokeniser le monde.

Pourquoi cette annexe ?

Le cours de Web Sémantique commence par une opération si triviale qu'on n'y pense même plus : découper du texte. Un .split() par-ci, un re.findall() par-là, et on obtient des mots. Mais derrière cette apparente simplicité se cache une théorie magnifique qui mêle logique, algèbre, automates et langages formels.

Cette annexe n'est pas un cours d'ingénierie linguistique. C'est un rappel — ou une découverte, selon d'où vous venez — de ce que sont les expressions régulières, comment elles fonctionnent vraiment (automates non déterministes → déterministes), et pourquoi des outils centenaires (dans le monde numérique) comme grep et sed sont encore les meilleurs amis du développeur.

📜 Petite chronologie :
1956 — Stephen Kleene invente les « événements réguliers » (ancêtre des regex)
1968 — Ken Thompson implémente grep en une nuit (littéralement)
1973sed fait son apparition dans Unix V7
1986 — Henry Spencer écrit la première bibliothèque regex portable
1992perl popularise les regex étendues (lookahead, backreferences…)
202x — Vous, ici, qui allez découper du texte comme des pros.
Près de 70 ans de théorie et d'outillage, condensés dans quelques pages.
01

Qu'est-ce qu'une expression régulière ?

Une expression régulière (ou regex) est une notation compacte pour décrire un ensemble de chaînes de caractères. Au lieu de lister « aa, ab, ba, bb », on écrit [ab]{2}. Au lieu de dire « une adresse email », on écrit \w+@\w+\.\w+.

Derrière cette notation, il y a trois opérations de base, définies par Kleene en 1956, et qui suffisent à tout construire :

OpérationNotationExempleReconnaît
Union (ou) | a|b a ou b
Concaténation (juxtaposition) ab a suivi de b
Étoile de Kleene * a* ε, a, aa, aaa

C'est tout. Trois opérations suffisent à décrire n'importe quel langage régulier. Tout le reste — +, ?, [a-z], \d, les groupes, les lookaheads — n'est que du sucre syntaxique. (Du sucre délicieux, certes, mais du sucre.)

Sucre syntaxique courant

NotationÉquivalentSens
+rr*Une ou plusieurs répétitions
?r|εOptionnel (zéro ou une)
{n,m}rr…r(r|ε)…Entre n et m répétitions
[abc]a|b|cUne classe de caractères
.(tout sauf \n)Caractère quelconque
\d[0-9]Chiffre
\w[a-zA-Z0-9_]Caractère de « mot »
« Certaines personnes, quand elles sont confrontées à un problème, se disent : "Tiens, je vais utiliser une expression régulière." Maintenant elles ont deux problèmes. » — Jamie Zawinski (1997)

Cette citation célèbre est un avertissement : les regex sont puissantes mais cryptiques. Un ^(?=.*[A-Z])(?=.*\d).{8,}$ est illisible pour un humain normal. Mais bien utilisées, elles restent l'outil le plus efficace pour manipuler du texte.

02

Des regex aux automates

Une expression régulière, c'est bien joli, mais comment un ordinateur fait-il pour décider si une chaîne correspond ? La réponse, c'est les automates finis.

L'idée est simple : on transforme la regex en une machine abstraite composée d'états et de transitions. On lui donne la chaîne caractère par caractère, et si à la fin on est dans un état « acceptant », la chaîne est reconnue.

Automate Non Déterministe (NFA)

Le plus facile à construire à partir d'une regex. Il a deux super-pouvoirs :

Exemple : la regex a(b|c)* donne le NFA suivant (en ASCII, parce que le vrai SVG coûte 50 lignes de code) :

État initial ──→ ((a)) ──→ ((ε)) ──→ ((ε)) ──→ ((ε)) ──→ ((accept)) │ │ │ │ ↓ ↓ │ ((b)) ((c)) │ │ │ └────────────────┴──────────┘ (retour via ε depuis (b) et (c))

La construction systématique d'un NFA à partir d'une regex s'appelle la construction de Thompson (Ken Thompson, 1968). Chaque opération de base correspond à un petit motif d'automate :

Concaténation

r₁r₂ → l'automate de r₁ suivi de celui de r₂.

Union

r₁|r₂ → deux branches parallèles avec ε-transitions.

Étoile

r* → une boucle (ε vers l'entrée de r, ε pour sortir).

Automate Déterministe (DFA)

Le NFA, c'est bien, mais il a un problème : pour savoir si une chaîne est reconnue, il faut explorer tous les chemins possibles en parallèle. C'est inefficace. La solution : transformer le NFA en DFA (Automate Fini Déterministe), où chaque état ne peut avoir qu'une seule transition par caractère.

La transformation s'appelle la construction par sous-ensembles (ou « powerset construction »). L'idée :

  1. On part de l'ε-fermeture de l'état initial du NFA (tous les états atteignables par des ε-transitions).
  2. Pour chaque caractère possible, on suit toutes les transitions depuis tous les états de cet ensemble, et on referme par ε.
  3. Chaque ensemble d'états du NFA devient un état du DFA.
  4. On répète jusqu'à n'avoir plus de nouveaux ensembles.

Résultat : un DFA avec potentiellement exponentiellement plus d'états que le NFA (mais en pratique, c'est très raisonnable).

🧠 NFA vs DFA — le match :
NFA : facile à construire, lent à exécuter (exploration parallèle / backtracking).
DFA : coûteux à construire (exponential dans le pire cas), mais O(n) pour reconnaître une chaîne de taille n.
Les moteurs de regex modernes (PCRE, Python re) utilisent un hybride : une simulation de NFA avec backtracking et mémoïsation, ce qui donne le meilleur des deux mondes — sauf quand le backtracking explose (malédiction du (a*)*b).

Minimisation

Une fois le DFA construit, on peut le minimiser en fusionnant les états équivalents (algorithme de Moore ou de Brzozowski). Le résultat est un automate minimal unique pour un langage donné. C'est beau, c'est optimal, et ça s'enseigne en cours de théorie des langages (vous avez suivi, n'est-ce pas ?).

« Un DFA minimisé, c'est comme un programme bien écrit : il ne fait que ce qu'il doit faire, sans un état de trop. » — Théorie des automates, résumée en une phrase
03

grep — Global Regular Expression Print

grep est l'outil Unix le plus légendaire. Inventé par Ken Thompson en 1974 (en une nuit, paraît-il, pour aider Ed à chercher du texte), il permet de filtrer les lignes d'un fichier qui correspondent à une regex.

Usage de base

bash# Chercher toutes les lignes contenant "error"
  grep 'error' log.txt

  # Compter le nombre de lignes avec "error"
  grep -c 'error' log.txt

  # Afficher le numéro de ligne
  grep -n 'error' log.txt

  # Inverser : lignes qui ne contiennent PAS "error"
  grep -v 'error' log.txt

  # Ignorer la casse
  grep -i 'error' log.txt

Expressions régulières étendues (-E)

Par défaut, grep utilise les regex de base (BRE), où +, ?, |, () doivent être échappés. Avec -E (ERE), on écrit naturellement :

bash# Lignes contenant "foo" ou "bar"
  grep -E 'foo|bar' data.txt

  # Adresses email (version simple)
  grep -E '\b\w+@\w+\.\w+\b' emails.txt

  # Lignes commençant par '#' (commentaires)
  grep -E '^#' config.ini

  # Numéros de téléphone français (10 chiffres, séparés ou non)
  grep -E '(0[1-9])([-. ]?[0-9]{2}){4}' annuaire.txt

Cas pratique — analyse de logs

Imaginez un fichier de log Apache avec des milliers de lignes. Vous voulez trouver les IP qui ont fait des requêtes POST en erreur 500 entre 14h et 15h :

bash# Un petit coup de grep bien senti
  grep -E '14:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9])' access.log | \
    grep -E 'POST' | \
    grep -E ' 500 ' | \
    grep -Eo '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | \
    sort | uniq -c | sort -rn

  # Résultat : liste des IP défaillantes, triées par fréquence
🐚 Le saviez-vous ? Le nom grep vient de la commande g/re/p de l'éditeur ed. La commande signifiait : « Globalement, cherche la Regex, et imprime (Print) les lignes. » Ken Thompson a tellement aimé ce pattern qu'il en a fait un outil dédié. Un autre informaticien aurait appelé ça grp. Mais Ken, c'est Ken.

Variantes modernes

CommandeUsage
grep -PPerl-compatible regex (PCRE) — lookaheads, backreferences
grep -rParcourt récursivement les dossiers
grep -lAffiche seulement les noms de fichiers qui matchent
rg (ripgrep)Version Rust, ultra-rapide, ignorer les .gitignore
ag (the_silver_searcher)Alternative rapide, très utilisée
04

sed — Stream EDitor

Si grep est le couteau suisse de la recherche, sed est celui de la transformation. sed lit un fichier ligne par ligne, applique des transformations, et écrit le résultat. Sans ouvrir le fichier dans un éditeur. Sans souris. Sans GUI. Rien que du terminal.

Substitution — le verbe s

La commande la plus utilisée de sed est s/ : substitution. Le pattern classique : s/regex/remplacement/drapeaux.

bash# Remplacer "foo" par "bar" sur chaque ligne
  sed 's/foo/bar/' fichier.txt

  # Remplacer globalement (pas juste la première occurence par ligne)
  sed 's/foo/bar/g' fichier.txt

  # Sauvegarder le résultat dans un nouveau fichier
  sed 's/foo/bar/g' fichier.txt > nouveau.txt

  # Modifier le fichier en place (-i = in-place)
  sed -i 's/foo/bar/g' fichier.txt

Références arrière (backreferences)

Avec \(…\) (BRE) ou (…) (ERE avec -E), on capture des parties de la ligne et on les réutilise dans le remplacement avec \1, \2, etc.

bash# Inverser "Nom, Prenom" → "Prenom Nom"
  sed -E 's/^(.+), (.+)$/\2 \1/' annuaire.txt

  # Standardiser des numéros de téléphone : 06.12.34.56.78 → 0612345678
  sed -E 's/0([1-9])[.-]?([0-9]{2})[.-]?([0-9]{2})[.-]?([0-9]{2})[.-]?([0-9]{2})/0\1\2\3\4\5/g' contacts.txt

  # Ajouter une balise HTML autour d'un mot
  sed -E 's/\b(urgent)\b/\1<\/strong>/gi' emails.txt

Plus que de la substitution

sed peut aussi supprimer des lignes, les insérer, les imprimer conditionnellement, et même écrire des scripts entiers :

bash# Supprimer les lignes vides
  sed '/^$/d' fichier.txt

  # Supprimer les lignes 10 à 20
  sed '10,20d' fichier.txt

  # Afficher seulement les lignes 5 à 8
  sed -n '5,8p' fichier.txt

  # Ajouter une ligne "<hr>" après chaque ligne de titre
  sed '/^# /a <hr>' article.md

Cas pratique — extraction de données structurées

Vous avez un fichier CSV mal formé, avec des guillemets qui trainent, et vous voulez le nettoyer pour le cours de Web Sémantique :

bash# Entrée : "Jean DUPONT", "jean@email.com",  "32", "Paris"
# Problème : guillemets, espaces en trop, valeurs mixées

# 1. Enlever les guillemets
sed -E 's/"([^"]*)"/\1/g' data.csv | \
  # 2. Nettoyer les espaces après les virgules
  sed -E 's/, +/,/g' | \
  # 3. Remplacer les points-virgules par des virgules (fichier européen)
  sed 's/;/,/g' | \
  # 4. Ne garder que les lignes avec une adresse email valide
  grep -E '^[^,]+,[^,]+@[^,]+\.\w+,'

  # Résultat : un CSV propre, prêt à être parsé par Python
« sed est le genre d'outil que vous utilisez une fois par mois, mais quand vous en avez besoin, rien d'autre ne fait le travail. » — Utilisateur Unix anonyme (probablement)

sed vs awk

awk est le grand frère de sed : il sait faire tout ce que fait sed, plus du traitement par colonnes, des variables, des conditions, et des boucles. La règle empirique :

05

Et dans le cours de Web Sémantique ?

Maintenant que vous êtes incollables sur les regex, voici exactement où et comment ces connaissances vous serviront dans les 5 cours :

Cours #1 — Tokenisation

re.split(), re.findall() pour découper le texte en mots. Filtrage de stopwords, extraction de motifs, cleaning de corpus. Nos amis \w+ et [^a-z].

Cours #1 — Index inversé

Nettoyage des documents avant indexation : regex pour normaliser la casse, enlever la ponctuation, gérer les contractions.

Cours #1 — Recherche booléenne

Le parseur de requêtes ET/OU utilise des regex pour tokeniser la requête utilisateur. Et notre AST peut s'inspirer du parseur d'expressions régulières.

Cours #2 — Triplets & RDF

Parseur Turtle : reconnaître les URI, les littéraux avec types et langues ("..."^^xsd:int, "..."@fr). Le parseur de Prolog utilise aussi des regex pour reconnaître les termes, variables, prédicats.

Cours #3 — OWL & Sparql

Parsing d'ontologies, validation de syntaxe, extraction d'annotations. Les IRIs, les préfixes, les chaînes encodées.

Cours #5 — Boutique Fuseki

Nettoyage des données CSV avant import dans Fuseki. Validation de formats (email, téléphone, prix). Filtrage des produits par regex dans le frontend.

Les outils dans le code du cours

python# Recherche.py — tokenisation (Cours #1)
  import re
  mots = re.findall(r'\w+', texte.lower())

  # Nettoyage : enlever la ponctuation, normaliser
  propre = re.sub(r'[^a-z0-9\s]', '', texte.lower())

  # Recherche booléenne : parser la requête ET/OU
  tokens = re.split(r'\s+(ET|OU)\s+', requete)

  # Mini parseur Turtle (Cours #2) — reconnaître un littéral typé
  motif_litteral = re.compile(
    r'("(?:[^"\\]|\\.)*")(?:\^\^(\w+:\w+)|@(\w+))?'
  )

Devoir de pré-rentrée

Avant d'attaquer le Cours #1, vous devriez être capable d'écrire sans documentation les regex suivantes :

ObjectifRegex attendue
Une URL HTTP/HTTPShttps?://\S+
Un nombre décimal (européen)-?\d+(,\d+)?
Un tag HTML<[^>]+>
Une date JJ/MM/AAAA\d{2}/\d{2}/\d{4}
Lignes vides^\s*$
🎯 Résumé pour le cours :
Les expressions régulières sont le B.A.-BA du traitement automatique du texte. Sans elles, .split() serait juste une méthode qui coupe aux espaces. Avec elles, vous pouvez découper le texte de n'importe quelle manière, extraire n'importe quelle information, et nettoyer n'importe quel corpus.

Le cours de Web Sémantique part du texte brut pour arriver aux ontologies OWL. Les regex sont le premier maillon de cette chaîne. Maîtrisez-les, et la première moitié du cours vous semblera naturelle. Ignorez-les, et vous passerez votre temps à debugger des patterns.