Les expressions régulières

Article soumis par Averell le 9/03/03

Mmm... Pas facile de faire un cours sur les expressions régulières, pourtant il faut savoir que cette notion, qui est apparue dans mIRC après la version 5.82, est énormément utilisée par les webmestres de sites écrits en langage de serveur (perl, php, asp etc.) et aussi par certaines applications. Le javascript comprend aussi les expressions régulières ou standard (appelons-les "regexp", ce sera plus court).

Ce document, assez long à lire, s'articule de la manière suivante

  • Introduction: un exemple simple
  • A quoi ça sert
  • Fonctions qui implémentent les regexps dans mirc
  • Syntaxe d'une regexp
  • Un premier exemple concret: recherche d'une chaîne dans un texte
  • Introduction aux méta-caractères
  • Les ensembles entre crochets
  • Les parenthèses
  • Les accolades
  • Les signes spéciaux, hors quantificateurs
  • Les quantificateurs
  • Extraction de texte
  • La substitution de données
  • Liste non exhaustive des métacaractères importants à connaître

    Introduction: un exemple simple

    Mirc introduit depuis déjà longtemps des fonctions élémentaires de traitement de texte ($left, $right, fonctions relatives aux tokens, opérateur isin) mais dans bien des cas, il faut reconnaître que.. ce n'est pas toujours pratique.

    Prenons un exemple: mettons que vous voulez vérifier si une chaîne de caractères %email est bien une adresse e-mail. Un test élémentaire consiste à vérifier l'existence d'une arrobas:

    if (@ !isin %email ) { echo 1 -a %email n'est pas une email valide! | suite du traitement ... }


    Ce test ne peut pas vérifier si la chaîne contient deux arrobas, ou une arrobas et rien d'autre, ou même des caractères interdits (genre \, $, ^ etc.). Si l'on veut le vérifier, on est obligé de faire des tests supplémentaires; on peut utiliser $gettok, par exemple. Ceci devient finalement assez lourd.

    L'emploi d'une Regexp permet, dans ce cas précis, de vérifier en une seule ligne de test, qu'une adresse e-mail est syntaxiquement correcte. Sacré gain de temps et d'énergie, hein?

    ATTENTION /!\ il n'y a pas moyen de vérifier la validité d'une adresse e-mail, par l'emploi d'une expression régulière (c'est à dire, de savoir si elle existe réellement); tout au plus elle peut vous dire s'il est sûr qu'elle n'est pas valide.

    A quoi ça sert

    Une regexp se présente sous la forme d'un masque, comportant des caractères et des méta-caractères, dont le but est de vérifier la conformité d'un texte ou d'une partie d'un texte avec une expression donnée; l'expression étant définie dans le masque.

    Ce n'est pas tout: vous pouvez utiliser une expression régulière pour aller chercher des informations précises dans un texte, pourvu que vous connaissiez la structure de ce texte (par exemple: un raw de serveur irc) ou pour substituer une chaîne de caractères par une autre chaîne de caractères (par exemple: remplacer des virgules par des espaces, ou le contraire).

    Fonctions qui implémentent les regexps dans mirc

    Sous mirc, il existe trois fonctions implémentant les regexp:

  • $regex([name], texte, regexp)     But: Test de conformité
  • $regsub([name], texte, regexp, subtext, %var)     But: Substitution de texte
  • $regml([name], N)     But: Récupération d'extraits de texte


  • le [name] est facultatif, cependant il est vivement conseillé de renseigner ce champ au cas où il y aurait plusieurs regexps indépendantes à gérer. De plus, de ces trois fonctions, seule la 3e ne prend pas de regexp en paramètre. Vous saurez pourquoi plus loin.

    Syntaxe d'une regexp

    La regexp se présente sous la forme d'une chaîne de caractères, alphanumériques ou non, encadrée par deux "slashes" / et éventuellement suivie d'un ou de plusieurs caractères alphabétiques. Dans un premier temps, nous ignorons les caractères après le slash. Ils ont leur utilité, qui sera vue plus loin.

    Attention: ne pas mettre d'espacements inutiles dans une regexp, dans un but de lisibilité: ceux ci sont en effet compris comme des caractères normaux (le caractère espace).

    Un premier exemple concret: recherche d'une chaîne dans un texte

    1er exemple tout simple: vérifier que la phrase "Nicole apportez-moi mes pantoufles" comporte le mot "moi". (Pour la culture, cette phrase est issue du Bourgeois Gentilhomme, de Molière :)

    set %texte Nicole apportez-moi mes pantoufles
    set %re /moi/
    echo 1 -a $regex(%texte, %re)


    La fonction $regex retourne 1: cela signifie que le mot "moi" se trouve au moins une fois dans le texte. C'est-y pas beau cha madame? :)

    Bon. Si le texte est écrit façon "Cow-boy" maintenant:

    set %texte NiCoLe ApPoRtEz-MoI mEs PaNtOuFlEs


    la fonction $regex retourne alors 0. Eh oui, petit problème: cette fonction est case-sensitive. Heureusement, dans leur infinie mansuétude, les informaticiens ont prévu la possibilité qu'elle ne le soit pas: il suffit d'ajouter un "i" (i comme ignore case) après le 2e slash.

    set %re /moi/i
    echo 1 -a $regex(%texte, %re)


    et voilà le travail!

    D'une façon générale, on peut vérifier l'existence de n'importe quelle chaîne de caractères dans un texte, pourvu qu'on la connaisse, même si elle comporte des espaces. "Il suffit" de l'utiliser en tant que masque de regexp.

    J'ai mis "il suffit" entre guillemets, car certains caractères courants (tels le point "." les parenthèses "(" et ")" ou le dollar "$") ne sont pas des caractères normaux: ce sont des métacaractères. Si on veut introduire un point ou une parenthèse dans une regexp, il faudra le faire précéder d'un backslash "\". D'une manière générale, si vous pensez qu'un caractère risque d'être un méta-caractère, vous le faites précéder d'un backslash: cela ne mange pas de pain mais alourdit un peu l'expression. (Note: le backslash est évidemment un métacaractère)

    N.B. Toutes les lettres de l'alphabet, majuscules et minuscules, ainsi que les chiffres, sont des caractères normaux; donc pas de doute à avoir.

    Introduction aux méta-caractères

    Ceci est intéressant: on peut donc repérer l'existence d'une chaîne de caractères dans un texte; mais ça je savais déjà faire avec l'opérateur isin! Cependant, en reprenant l'exemple du début, puis-je y repérer l'existence d'une adresse e-mail, alors que je ne connais pas a priori les éléments qui composent cette adresse?

    La réponse est évidemment oui, et c'est là que réside toute la puissance des regexps. Pour cela, il y a les méta-caractères, qui ont une fonction bien précise. Une liste non exhaustive de ces caractères spéciaux est donnée plus bas. Beaucoup d'entre eux sont formés d'un caractère précédé un backslash

    Le méta-caractère le plus simple est le point ".": il signifie simplement "n'importe quel caractère". Par exemple, en remplaçant /moi/i par /mo./i, la fonction $regex est positive pour la phrase "Nicole apportez-moi mes pantoufles" mais aussi pour "Et mon cul c'est du poulet", et aussi pour "Geronimo, tu vas mourir!".

    D'autres métacaractères sont plus sélectifs: \d signifie un chiffre (de 0 à 9), \w signifie tout caractère de l'alphabet (majuscule ou minuscule) ou un chiffre ou l'underscore "_", \s signifie un espacement ou une séparation (tabulation ou saut à la ligne).

    Les ensembles entre crochets

    Si aucun des métacaractères prévus ne correspond à vos souhaits, vous pouvez vous-même constituer votre liste. Celle-ci se présente entre deux crochets, encadrant celle-ci. La liste peut aussi comporter des métacaractères (le signe moins "-" a une fonction spéciale):

    Exemple: [01234], que l'on peut simplifier en [0-4]: tous les chiffres de 0 à 4.

    Autre exemple: [a-zA-Z0-9_.-]: simplifiable en [\w.-], et qui signifie: toute lettre de l'alphabet, majuscule ou minuscule, ou un chiffre, ou un underscore, ou le point (à noter l'absence du backslash devant le point qui n'est pas nécessaire dans le cas des ensembles) ou un signe moins. Exemple intéressant car c'est précisément la syntaxe d'une adresse e-mail, au signe arrobas près.

    Le métacaractère ^ a une fonction spéciale: placé en premier, il signifie le complémentaire de l'ensemble défini (pour les non matheux, "tout sauf ces caractères").

    Exemple: [^;] : tout les caractères ascii sauf le point virgule.

    Les parenthèses

    Elles ont plusieurs fonctions:

    1) Regroupement (à voir dans le chapitre "quantificateurs")

    2) Alternatives, en conjonction avec la barre droite |

    Exemple:

    arti(ste|chaut) : permet dans ce cas précis de trouver le mot artiste ou artichaut dans un texte. L'imbrication de parenthèses est permise.

    3) Extraction de données (à voir dans le chapitre du même nom)

    Les accolades

    A voir dans le chapitre des quantificateurs, les accolades permettent de préciser le nombre minimum et maximum de fois que l'expression doit être rencontrée. Les accolades encadrent au plus deux nombres entiers séparés par une virgule représentant les bornes, et au moins un nombre. Si un des nombres est omis, il signifie que la borne peut valoir n'importe quoi.

    Exemple: 0{1,3} vaut pour 0, 00 et 000 mais pas pour 0000 ou pour rien (*). Par contre, 0{1,} signifie au moins un 0 et 0{,3} au plus trois 0 (ou rien, donc).

    Enfin 0{3}, qui signifie 0{3,3}, vaut très précisément pour 000.

    (*) Attention, 000 est une partie de 0000, donc 0{1,3} tout seul fonctionne aussi pour 0000, sauf si vous demandez explicitement que le texte entier soit conforme à la regexp; ceci est expliqué ci-dessous.

    Les signes spéciaux, hors quantificateurs

    Il y en a beaucoup et je ne les connais pas tous! Les deux que j'utilise le plus souvent sont le signe chapeau "^" placé en début de chaîne, qui signifie: le texte doit commencer par, et le signe dollar "$" en fin de chaîne signifiant: le texte doit finir par. Par défaut, la fonction $regex dit: le texte doit contenir. L'utilisation conjointe de ces deux signes signifie que le texte entier doit être conforme, et pas seulement une partie.

    Exemple: /^\d{3}$/ : vaut pour un nombre de trois chiffres exclusivement.

    Les quantificateurs

    Ils interviennent quand on ne sait pas précisément le nombre de caractères impliqués. Par exemple, un prénom ne comporte que des lettres, la première étant majuscule, et les autres étant en nombre quelconque. On peut utiliser les accolades, si on a une idée de la borne supérieure:

    /^[A-Z][a-z]{1,25}$/

    Ici j'ai fixé au moins une lettre minuscule après la première, et 25 lettres minuscules au plus.

    Mais il y a plus simple: utiliser le signe plus "+":

    /^[A-Z][a-z]+$/

    Le signe plus a la même signification que {1,}: il signifie "au moins une fois"

    A noter qu'on peut mettre un regroupement entre parenthèses devant un quantificateur. Celui-ci sera "vu" comme un simple (méta) caractère:

    /^0(123)+$/ : marche pour 0123, 0123123, 0123123123 mais par pour 0.

    Le quantificateur étoile "*" signifie "au moins zero fois, au plus n'importe quel nombre de fois", ce qui peut sembler assez vague! (équivalent: {,} ) mais cependant cela peut servir...

    Le signe point d'interrogation "?" signifie: "au plus une fois" {,1}

    Arrivé à ce stade, nous sommes à même de construire notre expression régulière vérifiant la syntaxe d'une adresse e-mail. Il y a toujours un grand nombre de possibilités face à un problème de ce genre; en voici une qui marche:

    /^[\w.-]+@[\w.-]+\.[a-z]{2,4}$/

    Elle fonctionne dans la plupart des cas (essayez avec la vôtre!), mais ne filtre pas certains non-sens, genre:

    machin.......truc@libertysurf.fr (je ne suis pas sûr que cela soit permis)

    ou

    truc.bidule@machin..com (deux point avant l'extension du domaine)

    ou

    123.21@34.aaa (sans commentaire)

    Extraction de texte

    Second domaine ou l'expression régulière est reine: celui de l'extraction de données pertinentes.

    Un exemple; voici une ligne type d'un formulaire entré sur ordinateur pour l'obtention d'une allocation de recherche universitaire en vue de préparer un doctorat:

    Nom: Dalton Prénom: Averell Lieu de résidence: le pénitencier de Nothing Gulch

    Je veux, à partir de cette phrase, extraire le nom et le prénom du candidat. Pour compliquer les choses, je ne sais pas a priori combien d'espaces précèdent les données du candidat, mais il y en a au moins un.

    Il suffit, dans l'expression régulière, de parenthéser les données qui m'intéressent, et je pourrai les récupérer avec la fonction $regml.

    Essayons un exemple:

    set %Formulaire Nom: Dalton Prénom: Averell Lieu de résidence: le pénitencier de Nothing Gulch
    set %Nom /Nom:\s+(\S+)/
    set %Prenom /Prénom:\s+(\S+)/


    set %lenom $regex(NOM, %Formulaire, %Nom)
    set %leprenom $regex(PRENOM, %Formulaire, %Prenom)

    echo 1 -a le prénom du candidat est $regml(PRENOM,1) et son nom est $regml(NOM,1)


    Le métacaractère \S signifie, à l'inverse de \s: "tout sauf un espace". En effet, le nom ou le prénom s'arrête au premier caractère espace rencontré! Si on avait utilisé un point à la place de \S, tout le reste du formulaire aurait été pris aussi, ce qui ne nous arrangeait pas.

    Nous voyons ici l'intérêt d'utiliser des noms d'expressions regulières (NOM et PRENOM). Par défaut, mirc utilise "l'expression courante", qui est écrasée à chaque nouvelle évaluation d'une autre expression.

    Remarque: Le chiffre 1 utilisé dans $regml signifie: prendre le contenu évalué par la première parenthèse. Ici il n'y a qu'une parenthèse de toute manière.

    On peut faire mieux:

    set %NomEtPrenom /Nom:\s+(\S+)\s+Prénom:\s+(\S+)/
    set %tout $regex(%Formulaire, %NomEtPrenom)
    echo 1 -a le candidat se nomme $regml(2) $regml(1)


    Avec une seule regexp, vous récupérez les deux informations! Magique :)

    /!\ Attention en ce qui concerne les parenthèses: l'alternative (xxx|yyy|zzz), placée obligatoirement entre parenthèses, compte aussi pour une donnée extractible.

    Encore un dernier exemple: je reviens à mon adresse e-mail. Je peux très facilement récupérer une adresse e-mail contenue dans un texte, où qu'elle soit:

    set %texte Je m'appelle Averell, mon email est webmaster@mircscriptsfrfm.com et je vous aime tous!
    set %re /([\w.-]+@[\w.-]+\.[a-z]{2,4})/
    if ( $regex(%texte, %re) == 1 ) { echo 1 -a l'adresse e-mail contenu dans la phrase est $regml(1) ! }
    else { echo 1 -a Il n'y a pas d'adresse e-mail dans la phrase... }


    La substitution de données

    Vous pouvez, dans un texte, remplacer très facilement une séquence de caractères qui "matche" une regexp par un autre texte: ceci se fait avec la fonction $regsub .

    Exemple: remplacer des espacements quelconques par des tirets "-"

    set %rien $regsub(%Formulaire, /\s+/g, $chr(45), %TexteModifie)


    Le résultat (c'est le contenu de la variable %TexteModifie)

    Nom:-Dalton-Prénom:-Averell-Lieu-de-résidence:-le-pénitencier-de-Nothing-Gulch


    Attention: j'ai rajouté un g (g comme global) après le 2e slash. En effet, par défaut l'évaluation de la regexp ne se fait qu'à la première occurrence dans le texte. Le /g force l'évaluation à toutes les occurrences rencontrées. On peut évidemment le combiner avec le i de ignore case: dans ce cas, mettre ces lettres après le slash (/gi).

    A la place de l'expression à substituer, on peut aussi mettre une fonction qui retourne l'expression; dans ce cas, rajouter un e après le slash de la regexp. Ceci est valable en Perl, sous mirc je ne suis pas sûr que ce soit obligatoire.

    Liste non exhaustive des métacaractères importants à connaître

    . Remplace tout caractère, sauf le retour à la ligne
    \d Chiffre de 0 à 9
    \D Tout sauf un chiffre
    \w Un caractère alphanumérique, ou underscore
    \W Tout sauf caractère alphanumérique, ou underscore
    \s Séparation (espacement, tabulation, saut à la ligne)
    \S Tout sauf une séparation
    ^ Commence par (à placer au début)
    $ Se termine par (à placer à la fin)
    + Se répète au moins une fois
    * Se répète au moins zéro fois
    ? Se répète zéro ou une fois
    Crochets [ et ] Définit un ensemble (précéder le contenu d'un ^ pour spécifier son contraire). Le signe moins entre 2 caractères de même genre (lettre ou chiffre) spécifie l'ensemble des caractères dont ils sont les bornes (bornes incluses)
    Barre | Définit une alternative, à placer entre parenthèses (Ex: (Papa|Maman|La bonne|Moi) )
    Accolades { et } Se répète N fois si N est le seul élément entre accolades, sinon, permet de fixer un nombre min et max de répétitions {min,max}
    Parenthèses ( et ) Effectue un regroupement en vue d'être exploité par un quantificateur, ou en vue d'être extrait en tant que donnée. Utilisé aussi pour définir une alternative.


    Réagir à cet article (1 commentaire)

    Titre de l'article Date d'édition Auteur Comm
    Différence entre un bot client et un bot de service 01/08/02 Averell 8
    Le link entre serveurs - comment ça marche 29/09/02 Averell 1
    Créer des fonctions de type $fct() 14/10/02 Vlad 2
    Les expressions régulières 9/03/03 Averell 1
    Avoir le Saint du jour - Snippet 15/11/04 Vestax 1
    Un script open-source 2/2/08 Averell 2


    Retour à la page des tutoriaux