Travailler avec des expressions rationnelles

84
8
fév.
2016
Technologie

Les expressions rationnelles sont un outil d’analyse de texte par un ordinateur. Elles permettent de décrire des enchaînements de caractères d’une complexité suffisamment grande pour être réellement utiles, mais suffisamment faible pour être implémentées efficacement. Elles sont d’une importance capitale pour le théoricien des langages comme pour l’UNIX power user.

Dans cette dépêche, nous :

  • décrivons brièvement la notion abstraite d’expression rationnelle et recensons les implémentations les plus courantes sur un système Unix ;
  • présentons quelques commandes permettant de rechercher des motifs décrits par une expression rationnelle dans un texte, réécrire des fichiers automatiquement ou transformer et analyser des fichiers structurés automatiquement en utilisant des expressions rationnelles ;
  • montrons comment améliorer votre productivité avec Emacs grâce aux expressions rationnelles.

Dans cette dépêche, nous allons nous pencher sur les expressions rationnelles (souvent nommées abusivement expressions régulières suite à une traduction littérale de regular expression). Elles permettent de représenter formellement un motif de recherche, par exemple : un caractère alphabétique majuscule suivi de quatre caractères minuscules, puis deux chiffres et un point à la fin. Les expressions rationnelles représentent un outil puissant pour qui sait les utiliser à bon escient mais nécessitent une phase d’apprentissage non négligeable. La diversité des moteurs et des syntaxes n’aide pas non plus à leur simplicité, et les confusions entre les différents outils peuvent parfois donner des résultats surprenants.

Sommaire

Expressions régulières
Source : original en VO XKCD, traduction en VF

Description abstraite et implémentations principales

Les expressions rationnelles sont souvent utilisées comme brique de l’analyse des textes, pour faire de l’analyse lexicale. Elles sont issues des théories mathématiques des langages formels.

Le concept ayant montré sa pertinence, il faut faire face à une richesse des implémentations : POSIX, puis chaque Unix à sa version, GNU, FreeBSD, puis Perl et Emacs, pour les plus répandues. Certaines apportent des extensions (sucre syntaxique +, répétitions, groupes, et back tracking).

Wikipédia fournit divers exemples illustratifs. En voici quelques exemples variés :

  • recherche de motif avec grep pour avoir un filtre pour sélectionner des lignes, pour identifier des fichiers, pour sélectionner des journaux système à une certaine date ou pour rechercher dans les pages de manuel, etc. ;
  • avec sed, transformation de journaux système en format Apache en format tabulaire, transformation de la sortie de Docker, ps, etc. ;
  • dans Emacs, mettre en valeur un motif dans du code pour une revue ou pour l’édition, extraire des listes d’un fichier avec re-search, etc.

Les expressions rationnelles POSIX basiques

Les expressions rationelles POSIX génèrent des machines à état fini déterministe. Elles ne sont ainsi pas capables de faire des retours en arrière.

La commande grep

Le premier usage des expressions rationnelles pour les utilisateurs de systèmes basés sur GNU/Linux ou Unix est en général la commande grep, qui permet de trouver toutes les lignes correspondant à une expression rationnelle. La syntaxe de la commande grep est simplement :

grep <options> <expression rationnelle> <liste de fichiers>

Pour les exemples ci‐dessous, nous ferons des recherches dans le fichier french d’une Debian stable (paquet wfrench qui amène le fichier /usr/share/dict/french, [informations de licence]), ce fichier contenant la liste des mots de la langue française à raison d’un mot par ligne.

Dans une expression rationnelle, la première règle est que chaque caractère se représente lui‐même, par exemple l’expression rationnelle « rationnelle » correspond à « toute ligne contenant un r, suivi d’un a, suivi d’un t, suivi d’un i, suivi d’un o, suivi d’un n, suivi d’un autre n, suivi d’un e, suivi d’un l, suivi d’un autre l, suivi d’un e, suivi d’un s » :
$ grep 'rationnelles' french<br/>
irrationnelles<br/>
opérationnelles<br/>
rationnelles

Chaque caractère ne représente pas vraiment lui‐même, il existe des exceptions avec des méta‐caractères qui décrivent autre chose qu’eux‐mêmes. Un des plus utilisés de ces méta‐caractères est le point, qui signifie « un caractère quelconque », par exemple l’expression rationnelle « rationnelle. » correspond à « toute ligne contenant un r, suivi d’un a, suivi d’un t, suivi d’un i, suivi d’un o, suivi d’un n, suivi d’un autre n, suivi d’un e, suivi d’un l, suivi d’un autre l, suivi d’un e, suivi d’un caractère quelconque » :
$ grep 'rationnelle.' french<br/>
irrationnelles<br/>
opérationnelles<br/>
irrationnellement<br/>
rationnelles

Le problème des méta‐caractères est qu’on peut vouloir chercher du texte les contenant. Par exemple, dans notre dictionnaire, il y a des abréviations se terminant par un point. Pour qu’un méta‐caractère ne soit pas interprété, il faut le précéder d’une contre‐oblique « \ », par exemple « \. » représente le caractère point. On peut alors s’amuser à chercher les abréviations d’au moins six caractères, en les décrivant comme « un caractère quelconque, suivi d’un autre caractère quelconque, suivi d’un troisième caractère quelconque, suivi d’un quatrième caractère quelconque, suivi d’un cinquième caractère quelconque, suivi d’un sixième caractère quelconque, suivi d’un point » :
$ grep '......\.' french<br/>
arrond.<br/>
c.‐à‐d.
On remarquera que le point lui‐même est un caractère quelconque.

Un autre méta‐caractère utile est le crochet, qui permet de décrire un caractère pouvant correspondre à plusieurs valeurs, par exemple une voyelle non accentuée peut être représentée par « [aeiouy] » (qu’on peut lire comme « n’importe quel caractère étant soit un a, soit un e, soit un i, soit un u, soit un y »). Par exemple, si vous voulez briller en société en citant des mots comportant six voyelles non accentuées à la suite :
$ grep '[aeiouy][aeiouy][aeiouy][aeiouy][aeiouy][aeiouy]' french<br/>
rougeoyaient<br/>
youyou<br/>
youyous

Deux méta‐caractères particuliers sont utiles entre crochets :

  • le tiret situé entre deux caractères permet de définir une liste de caractères qui se suivent, par exemple « [a-f] » définit « soit un a, soit un b, soit un c, soit un d, soit un e, soit un f » ;
  • l’accent circonflexe situé au début permet de définir une exclusion de caractères, par exemple « [\^aeiouy] » définit « un quelconque caractère qui ne soit ni un a, ni un e, ni un i, ni un o, ni un u, ni un y »). Ces deux méta‐caractères sont cumulables, par exemple « [\^a-z] » définit « un quelconque caractère qui ne soit pas une lettre minuscule non accentuée », ce qui peut nous permettre de trouver tous les mots qui ont à la suite deux caractères qui ne sont pas des lettres : $ grep '[^a-z][^a-z]' french 
c.‐à‐d.
ch.-l.

On peut économiser les copier‐coller lorsque l’on veut chercher plusieurs fois la même information, en utilisant le symbole « \{min,max\} » qui permet d'indiquer que l’on cherche la présence d’un caractère successivement entre min et max fois, par exemple si vous cherchez les mots contenant deux q séparés par 5 à 7 lettres [1] :
$ grep 'q[a-z]\{5,7\}q' french <br/>
quantique<br/>
quantiques<br/>
quelconque<br/>
quelconques<br/>
quiconque<br/>
quiproquo<br/>
quiproquos<br/>
squelettique<br/>
squelettiques

Il est possible avec certaines versions de grep de spécifier un seul chiffre entre accolades :

  • si l’on cherche exactement x occurrences, on indique : « \{x\} » ;
  • si l’on cherche de 0 à x occurrences, on indique : « \{,x\} » ;
  • si l’on cherche au moins x occurrences, on indique : « \{x,\} ». Ainsi, on pourrait donc abréger la recherche des mots contenant 6 voyelles non accentuées ainsi : $ grep '[aeiouy]\{6\}' french
rougeoyaient
youyou
youyous

Si l’on veut répéter plusieurs caractères au lieu d’un seul, il faut encadrer la recherche avec des « \( \) ». Par exemple, si vous bloquez dans une grille de mots croisés sur la définition « mot contenant sept fois à la suite une consonne suivie d’une voyelle » :
$ grep '\([^aeiouy][aeiouy]\)\{7\}' french <br/>
remilitarisation<br/>

Le contenu trouvé à partir d’une expression entre parenthèses est dit « capturé », cela signifie qu’il est gardé en mémoire et peut être réutilisé dans l’expression rationnelle. La contenu capturé est accessible en utilisant « \1 », « \2 », « \3 », etc. (en général, on ne peut pas dépasser \9). Le numéro de capture est défini en comptant le nombre de parenthèses ouvrantes précédant l’expression capturée. Cela permet par exemple de lister les mots contenant un palindrome de quatre lettres :
$ grep '\(.\)\(.\)\(.\)\(.\)\4\3\2\1' french <br/>
caressera<br/>
caresserai<br/>
caresseraient<br/>
caresserais<br/>
caresserait<br/>
caresseras<br/>
paressera<br/>
paresserai<br/>
paresseraient<br/>
paresserais<br/>
paresserait<br/>
paresseras<br/>
querellerez

On peut encore affiner les recherches en utilisant les ancres, qui permettent de situer où se situe une expression rationnelle dans la ligne :

  • le dollar, lorsqu’il est situé à la fin de l’expression rationnelle, représente la fin de la ligne ;
  • l’accent circonflexe, lorsqu’il est situé au début de l’expression rationnelle, représente le début de la ligne.

On peut cumuler les deux ancres dans la même expression, par exemple si l’on veut chercher les vrais palindromes de quatre lettres :
`{mathjax}  grep '^\(.\)\(.\)\2\1`' french <br/>
alla<br/>
elle<br/>
erre<br/>
esse

Pour en terminer avec les expressions rationnelles POSIX basiques, il ne reste plus qu’un méta‐caractère à présenter, qui est l’astérisque. Ce caractère est équivalent à « \{0,\} » :
`{mathjax}  grep '^d.*ouilles`' french <br/>
débrouilles<br/>
dépouilles<br/>
douilles

Utiliser dans vi

VimRegex détaille largement le sujet.

Extension des expressions rationnelles

Les extensions rationnelles basiques étant peu lisibles, la norme POSIX a évolué pour intégrer les expressions rationnelles étendues, aussi appelées « ERE ».

grep est mieux avec « -E »

Les versions récentes de grep permettent d’utiliser les expressions rationnelles étendues avec l’option -E. Si vous ajoutez l’option -E à grep, vous devez modifier votre expression rationnelle ainsi :

  • \{ et \} deviennent { et } ;
  • \( et \) deviennent ( et ) ;
  • tous les autres méta‐caractères (« . », « [ », « ] », « - », « ^ », « $ », « * », « \1 », etc.) sont inchangés.

Outre cette suppression des contre‐obliques superflus, les expressions rationnelles étendues apportent trois nouveaux méta‐caractères. Le premier est « ? » qui est un synonyme de « {0,1} », qui permet par exemple de chercher les palindromes de 4 ou 6 lettres avec une seule expression :
<br/>
`{mathjax}  grep -E '^(.)(.)((.)\4)?\2\1`' french <br/>
alla<br/>
elle<br/>
erre<br/>
esse<br/>
selles<br/>
serres

On dispose aussi de « + » qui est un synonyme de « {1,} » :
`{mathjax}  grep -E '^cré+e`' french <br/>
crée<br/>
créée<br/>

Enfin, le dernier méta‐caractère spécifique aux expressions rationnelles étendues est le « | » qui permet de séparer plusieurs options :
`{mathjax}  grep -E '^(gr|f|citr)ouille`' french <br/>
citrouille<br/>
fouille<br/>
grouille

Les classes de caractères

POSIX prévoit des classes de caractère, qui sont des notations spécifiques entre crochets. À noter que les classes de caractères sont aussi bien gérées par les expressions rationnelles basiques qu’étendues (il n’y a donc pas besoin d’utiliser l’option -E pour en bénéficier), mais il existe des implémentations d’expressions rationnelles basiques non compatibles POSIX qui ne les acceptent pas.

Les classes de caractères sont des mots ou abréviations en anglais désignant ce à quoi ils correspondent et encadrés par « [: » et « :] » :

  • [:digit:] : désigne un chiffre décimal (équivalent à [0-9]) ;
  • [:lower:] : désigne une lettre minuscule (équivalent à [a-z]) ;
  • [:upper:] : désigne une lettre majuscule (équivalent à [A-Z]) ;
  • [:alpha:] : désigne une lettre minuscule ou majuscule (équivalent à [A-Za-z]) ;
  • [:alnum:] : désigne une lettre minuscule ou majuscule ou un chiffre (équivalent à [A-Za-z0-9]) ;
  • [:xdigit:] : désigne un chiffre hexadécimal (équivalent à [0-9a-fA-F]) ;
  • [:space:] : désigne un caractère d’espacement (espace, tabulation, retour chariot, etc.) ;
  • [:blank:] : désigne un espace ou une tabulation horizontale (à ne pas confondre avec [:space:]) ;
  • [:punct:] : désigne à un crochet ou un caractère de la classe suivante : ['!"#$%&()*+,./:;<=>?@\^_{|}~-]` ;
  • [:cntrl:] : désigne un caractère de contrôle ;
  • [:print:] : désigne un caractère affichable (ainsi qu’une espace), cette classe est à peu près le contraire de [:cntrl:] ;
  • [:graph:] : désigne l’ensemble des caractères visibles, sauf les espaces, les caractères de contrôle, etc. (équivalent à [\x21-\x7E]).

Pour aller plus loin

Attention au GLOB

Dans les exemples précédents, il était important d’utiliser de simples apostrophes pour éviter l’interprétation de caractères spéciaux par le Shell.

Outils pour tester vos expressions rationnelles

Plusieurs outils s’offrent à vous pour tester et triturer dans tous les sens vos expressions rationnelles, comme par exemple le site Regex Pal, qui propose notamment de la coloration syntaxique et se veut « temps réel » dans les modifications, ou regex101 qui permet de tester des expressions rationnelles Python, JavaScript ou PCRE.

Ne pas toujours utiliser les expressions rationnelles

Les expressions rationnelles ne sont par exemple pas l’outil idéal pour analyser du XML ou du HTML.

Jouer avec les expressions rationnelles

Voir la dépêche Regexcrossword : un subtil mélange de sudoku et de mots croisés, à la sauce Regex, ainsi que la chasse au trésor du MIT en 2014, etc.

Un peu de théorie

Les automates finis

La base théorique des expressions rationnelles se trouve dans la théorie des langages. Elles permettent notamment de décrire les langages rationnels. Elles sont fortement liées aux automates finis.

Pour illustrer le parallèle nous allons utiliser les caractères et les quantificateurs de base :

  • a qui permet de reconnaître la lettre a ;
  • ? qui permet de définir un groupe optionnel ;
  • * qui permet de définir un groupe se répétant zéro fois ou plus ;
  • + qui permet de définir un groupe se répétant une fois ou plus.

Littérature

[1] Avec ça vous allez vraiment briller en société, il faudra juste trouver un moyen d’intégrer ça dans la conversation.

  • # Deux outils complementaires pour les devs

    Posté par  . Évalué à 3.

    Très bon post!
    Pour ceux qui font du ruby il existe rubular pour tester leurs regex, et pour JavaScript j'utilise également scriptular.

    Il est à noter également que si en général les admins savent les utiliser, les devs y pensent beaucoup moins, souvent par manque de maîtrise et de formation

  • # s/(k)(a)(y)(a)(k)/\5\4\3\2\1/

    Posté par  . Évalué à 4.

    Hoho j'étais persuadé que le mot anagramme était masculin. Néanmoins, tu l'utilises pour décrire des palindromes.

    • [^] # Re: s/(k)(a)(y)(a)(k)/\5\4\3\2\1/

      Posté par  (site web personnel) . Évalué à 3. Dernière modification le 08 février 2016 à 10:18.

      Corrigé, merci (et en plus ça avait été signalé dans la tribune de rédaction de la dépêche /o\).

      • [^] # Re: s/(k)(a)(y)(a)(k)/\5\4\3\2\1/

        Posté par  . Évalué à 2.

        Dans le chapitre : Attention au GLOB

        il était important d'utiliser de simples guillemets pour éviter

        Il me semble qu’on parle de ' simple quote, mais les guillemets vont forcément par deux, un peu comme les lunettes.

        • [^] # Re: s/(k)(a)(y)(a)(k)/\5\4\3\2\1/

          Posté par  (site web personnel) . Évalué à 5.

          Pourquoi faire simple… Les guillemets selon Wikipédia :

          • français doubles « … » ;
          • français simples ‹ … › ;
          • anglais simples ‘…’ ;
          • anglais doubles “…” ;
          • allemands »…« ;
          • allemands „…“ ;
          • droits doubles "…" ;
          • droits simples '…'.

          Sinon j'ai mis apostrophe (aussi appelé guillemet-apostrophe) dans la dépêche.

        • [^] # Re: s/(k)(a)(y)(a)(k)/\5\4\3\2\1/

          Posté par  . Évalué à 10.

          Mauvais exemple, une lunette est un objet qui existe bel et bien : lunette d'astronomie, lunette d'approche (ou longue-vue)…

  • # Travailler avec des expressions rationnelles

    Posté par  . Évalué à 6.

    C'est jurer comme un charretier car les expressions rationnelles de sed sont différentes de celles de perl, etc.
    \+ ??

  • # Ne pas toujours utiliser les expressions rationnelles

    Posté par  (site web personnel, Mastodon) . Évalué à 5.

    Les expressions rationnelles ne sont par exemple pas l'outil idéal pour analyser du XML ou de l'HTML.

    Cf. http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454 qui résume plutôt bien la situation.

  • # Outils de coloration?

    Posté par  . Évalué à 7.

    img

    Quel est l'outil qui t'as permis d'avoir la coloration? Ou est-ce fait à la main?

  • # Ça manque de classes...

    Posté par  . Évalué à 4. Dernière modification le 08 février 2016 à 11:43.

    Un reproche que je ferais aux langages gérant les expressions rationnelles, c'est de ne pas avoir cherché à ajouter de nouvelles classes de caractères, au fur et à mesure des besoins.
    Une qui serait fort utile, éviterait de nombreuses erreurs et arrachages de cheveux aux dév, c'est une classe [:email:] !
    Que tous les formulaires du Web sachent enfin gérer, une bonne fois pour toutes, les adresses mails correctement !

    Mais j'imaginerais bien aussi des classes [:ipv4:], [:ipv6:] et [:ip:] (capable de détecter une IP, qu'elle soit v4 ou v6)…

    • [^] # Re: Ça manque de classes...

      Posté par  (site web personnel) . Évalué à 10.

      Sauf qu'il s'agit de « classes de caractères », pas de vérification de grammaire suivant une RFC.

      [:ipv4:] correspondrait juste à [0-9.] (mais ça va reconnaître aussi bien 333.444.555.666 que juste 777 ou 0.4).

      Parce que sinon il suffirait d'avoir [:mot-français-correct:] ou [:commentaire-pertinent-sur-linuxfrorg:] ou [:page-html5:].

      • [^] # Re: Ça manque de classes...

        Posté par  . Évalué à -1.

        [:ipv4:] correspondrait juste à [0-9.]

        Non, ce serait plutôt ([0-9]{1,3}\.){3}[0-9], si le {3} fonctionne (peut pas tester d'ici). Par contre pour IP6, ça risque d'être un chouïa plus galère, la notation permettant des raccourcis.

        Pour ce qui est de l'idée d'ajouter ce type de regex au(x différents) "standard(s)", ça ne ferait que rendre les choses plus complexe, en ajoutant de la fragmentation inutile.
        Et puis, ceux qui utilisent souvent les regex doivent s'être fait des listes pour les plus courantes/complexes qu'ils utilisent, non?

        • [^] # Re: Ça manque de classes...

          Posté par  (site web personnel) . Évalué à 2.

          Ta regexp est trop laxiste.
          Celle-ci devrait mieux fonctionner : "((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"

          • [^] # Re: Ça manque de classes...

            Posté par  . Évalué à 2.

            Ta regexp est trop laxiste.

            Pas autant que la tienne, mais il manque une précision sur le dernier octet.
            ifconfig | grep -E '([0-9]{1,3}\.){3}[0-9]{1,3}'
               inet adr:192.168.1.111 Bcast:192.168.1.255 Masque:255.255.255.0
               inet adr:127.0.0.1 Masque:255.0.0.0

            Et ça ne suffira pas pour valider une adresse…
            echo 256.400.555.999 | grep -E '([0-9]{1,3}\.){3}[0-9]{1,3}'
               256.400.555.999

            • [^] # Re: Ça manque de classes...

              Posté par  (site web personnel, Mastodon) . Évalué à 3.

              Et ça ne suffira pas pour valider une adresse…

              tth@ds23:~/POV/Parking$ ping 012.024.000.026
              PING 012.024.000.026 (10.20.0.22) 56(84) bytes of data.
              tth@ds23:~/POV/Parking$ echo 012.024.000.026 |  grep -E '([0-9]{1,3}\.){3}[0-9]{1,3}'
              012.024.000.026
              tth@ds23:~/POV/Parking$ echo 012.024.000.028 |  grep -E '([0-9]{1,3}\.){3}[0-9]{1,3}'
              012.024.000.028
              

              Mais bon, qui, à part les plus ultimes des dinos, utilise encore l'octal ?

          • [^] # Re: Ça manque de classes...

            Posté par  . Évalué à 2.

            Exact, oublié le fait que [0-9]{1,3} peut monter jusqu'à 999, ainsi que la typo que j'aie oublié le {1,3} sur le dernier octet.
            De ton côté tu as oublié le \ devant le point :)

            Sinon, après un test (j'avais testé le comportement avec un 010, vu que je n'ai jamais vu de 0 "inutile" devant un morceau d'IPv4), il semble que ping convertisse les octets depuis l'hexa ou l'octal quand les valeurs sont pré-fixées par 0x ou 0. Du coup, 0xa.10.10.1 semble être une IPv4 valide? Quelqu'un sait?

            • [^] # Re: Ça manque de classes...

              Posté par  . Évalué à 3.

              une ipv4 c'est
              [0-2]?[0-9]{1,2}[.][0-2]?[0-9]{1,2}[.][0-2]?[0-9]{1,2}[.][0-2]?[0-9]{1,2}

              ce qui est assez chiant à lire.
              my $blocIP="[0-2]?[0-9]{1,2}";
              my $reIP=join('[.]', ($blocIP)x4 );

              on peut aussi utiliser . à la place de [.] mais j'ai tendance à privilégier les solutions sans \ c'est toujours le bordel dans les imbrications :)

              Il ne faut pas décorner les boeufs avant d'avoir semé le vent

              • [^] # Re: Ça manque de classes...

                Posté par  (site web personnel) . Évalué à 3.

                Non : 266.277.288.299 n’est pas une IPv4 valide.

                Pour tout ce qui contient des règles de sens autre que le texte brut, les regexp deviennent vite relou :-).

                • [^] # Re: Ça manque de classes...

                  Posté par  . Évalué à 4.

                  Ben le problème avec les regexp, c'est qu'une fois qu'on les maitrise on en abuse.
                  Il faut simplement savoir quand les utiliser et quand ne pas les utiliser.

                  En l'occurence, pour vérifier si un nombre est inférieur à 255, il existe des meilleurs méthodes :)

                  • [^] # Re: Ça manque de classes...

                    Posté par  . Évalué à 3.

                    le problème avec les regexp, c'est qu'une fois qu'on les maitrise on en abuse

                    Je suis d'accord. Voici tout de même, pour ceux que ça intéresserait, une expression rationnelle qui valide correctement une IPv4 :
                    \b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(.|$)){4}\b

                    • [^] # Re: Ça manque de classes...

                      Posté par  . Évalué à 2.

                      Si j'en crois wikipedia, même cette regex ne valide pas correctement toutes les formes d'IPv4:

                      For example, the quad-dotted IP address 192.0.2.235 represents the 32-bit decimal number 3221226219, which in hexadecimal format is 0xC00002EB. This may also be expressed in dotted hex format as 0xC0.0x00.0x02.0xEB, or with octal byte values as 0300.0000.0002.0353.

                      Malheureusement il n'y a pas de pointeur vers une source plus fiable à côté de ce paragraphe, et j'ai la flemme d'en chercher un moi-même. En tout cas, le fonctionnement de ping sur ma Debian semble confirmer cette affirmation.
                      M'enfin, ça prouve bien que c'est juste une mauvaise idée que d'utiliser les regex quand on veut détecter une syntaxe, du coup. D'ailleurs, existe-t-il un outil raisonnablement utilisable en ligne de commande permettant de détecter un pattern syntaxique? (en espérant ne pas me planter sur le terme "syntaxique"… déjà ridiculisé une fois dans ce thread après tout :))

                      • [^] # Re: Ça manque de classes...

                        Posté par  (site web personnel) . Évalué à 2.

                        M'enfin, ça prouve bien que c'est juste une mauvaise idée que d'utiliser les regex quand on veut détecter une syntaxe, du coup.

                        Ça ne prouve rien du tout, ça dépend juste du type de syntaxe que l'on veut matcher, et si l'on veut juste répondre par oui ou non, ou bien expliquer pourquoi ça ne matche pas (auquel cas une expression régulière à elle toute seule ne suffira pas). Pour le cas des expressions régulières pour les ipv4 et traitant les différents cas, ça existe. Par contre l'expression régulière donnée dans le message juste avant n'est pas exacte (elle matche des trucs en plus qui ne sont pas des ips, en partie parce qu'elle traite le dernier groupe comme les premiers, ce qui permet de mettre un caractère à la fin).

              • [^] # Re: Ça manque de classes...

                Posté par  . Évalué à 2.

                on peut aussi utiliser . à la place de [.] mais j'ai tendance à privilégier les solutions sans \ c'est toujours le bordel dans les imbrications :)

                Pas con du tout ça!

        • [^] # Re: Ça manque de classes...

          Posté par  . Évalué à 8.

          Heureusement que Benoît a précisé qu’il s’agit de classes de caractères. Sinon, on aurait pu croire qu’il s’agit de classes de caractères.

      • [^] # Re: Ça manque de classes...

        Posté par  . Évalué à 5.

        Parce que sinon il suffirait d'avoir [:mot-français-correct:] ou [:commentaire-pertinent-sur-linuxfrorg:] ou [:page-html5:].

        Ça, je parie que Emacs le fait déjà.

    • [^] # Re: Ça manque de classes...

      Posté par  . Évalué à 2.

      À l'origine, les classes n'avaient pas été faite pour simplifier ou donner une notation « human-readable » mais pour résoudre un problème : les intervalles ne donnent pas le même résultat en fonction de l'encodage utilisé. Par exemple la chaine [A-Z] peut donner [ABCD...XYZ] dans un encodage et [AaBbCc...YyZ] dans un autre. [:upper:] donnera toujours [ABCDEF...XYZ].

      • [^] # Re: Ça manque de classes...

        Posté par  (site web personnel) . Évalué à 6.

        [:upper:] donnera toujours [ABCDEF…XYZ].

        Oui, mais ne pas oublier qu'il peut prendre en compte les majuscules accentuées (de même que [ABCDEF…XYZ]).

        $ echo "À" | LC_ALL=C grep -E "[[:upper:]]"
        $ echo "À" | LC_ALL=fr_FR.utf-8 grep -E "[[:upper:]]"
        À
        • [^] # Re: Ça manque de classes...

          Posté par  . Évalué à 2.

          C'est intéressant comme exemple !

          Je suis régulièrement em…bêté par ce genre de chose.
          Mon utilisation des expressions rationnelles est très basique : je m'en sert pour valider (match) le contenu de champs de formulaires sur des applications web php et/ou JS le plus souvent (pas taper !!!).
          La plupart du temps, soit je les ai déjà dans mon arsenal (voir dans celui de PHP), soit je fouille un peu sur le net pour trouver mon bonheur.
          Je cale toujours sur les champs libellé ("texte libre") : impossible de valider un texte accentué sans énumérer (d'une manière¹ ou d'une autre²) l'ensemble de caractères accentués…

          ¹ je met tous les caractères accentués dans mon expression rationnelle,
          ² je transforme ma chaîne pour remplacer tous les caractères accentués par les équivalents non-accentués avant de les passer dans un ^[:alpha:]$* par exemple…

          Ma question est donc la suivante : ton exemple est-il propre à grep ?

          • [^] # Re: Ça manque de classes...

            Posté par  (site web personnel) . Évalué à 5.

            $ echo "ÀA" | LC_ALL=C sed -r 's/[[:alpha:]]/youpi/'
            Àyoupi
            $ echo "ÀA" | LC_ALL=fr_FR.utf-8 sed -r 's/[[:alpha:]]/youpi/g'
            youpiyoupi
            
            $ LC_ALL=C awk 'BEGIN {str = "ÀA"; gsub(/[[:alpha:]]/, "youpi", str); print str;}'
            Àyoupi
            $ LC_ALL=fr_FR.utf-8 awk 'BEGIN {str = "ÀA"; gsub(/[[:alpha:]]/, "youpi", str); print str;}'
            youpiyoupi
            
            $ echo "ÀA" | LC_ALL=fr_FR.utf-8 ruby -pe 'gsub(/[[:alpha:]]/, "youpi")'
            youpiyoupi

            Etc.

    • [^] # Re: Ça manque de classes...

      Posté par  (site web personnel) . Évalué à 2.

      Une qui serait fort utile, éviterait de nombreuses erreurs et arrachages de cheveux aux dév, c'est une classe [:email:] !

      Comme les autres te l'ont fait remarqué, des classes de caractères ne servent pas à définir une classe de chaînes, mais une classe de caractères. Par contre, dans les langages qui permettent de combiner des regexps précompilées, on peut réutiliser des regexps exportées par un module (comme Regexp::Common). Après, pour des choses comme une adresse mail, un module avec une fonction qui le valide peut être suffisant si l'on a pas besoin de combiner ça avec une autre regexp. Et comme ça, pas besoin de savoir si c'est implémenté par une regex ou autre ; et d'ailleurs une simple regexp a ses limites pour donner un bon message d'erreur pour expliquer pourquoi l'adresse n'est pas valide (même si en Perl c'est faisable vu qu'on peut exécuter du code lorsque telle ou telle partie de la regexp a déjà matché).

    • [^] # Re: Ça manque de classes...

      Posté par  (site web personnel) . Évalué à 4.

      Valider proprement une adresse e-mail, c'est chaud.

      Comme on peut le lire ici → http://www.bortzmeyer.org/arreter-d-interdire-des-adresses-legales.html mieux vaut laisser passer des adresses invalides qu'en interdire des valides ;).

      .+@.+ ou .+?@.+

  • # Place aux jeux!

    Posté par  (site web personnel) . Évalué à 4.

    Pour ceux qui aprennent les regex, c'est toujours bien de le faire en jouant:

    Voila quelques liens:

    • [^] # Re: Place aux jeux!

      Posté par  . Évalué à 1.

      Dans le premier jeu ( http://regex.alf.nu/1 ), si on propose par exemple l'expression rationnelle :

      a?+

      on a le droit au message suivant :

      (invalid regex)

      ce qui signifie que la validité de l'expression rationnelle proposée est probablement déterminée par… une expression rationnelle.

      https://xkcd.com/1313/

      • [^] # Re: Place aux jeux!

        Posté par  (site web personnel) . Évalué à 1.

        /a?+/ c'est valide ça?

        • [^] # Re: Place aux jeux!

          Posté par  . Évalué à 4. Dernière modification le 10 février 2016 à 16:15.

          Oui : http://www.regular-expressions.info/possessive.html

          Pour faire court, c’est pour interdire au moteur d’expression régulière de faire du backtracking sur un choix précis. Ici, c’est le choix est : "a?" matche-t-il un caractère ou 0 ?

          /^a?ab/ arrivera a matcher "ab", mais /^a?+ab/ n’y arrivera pas, parce que dans le second cas "a?" a pris la décision de consommer le premier "a", et il ne reviendra pas sur cette décision ("+"), même quand cette décision implique un échec de la reconnaissance globale.

  • # Outil graphique

    Posté par  (site web personnel, Mastodon) . Évalué à 3.

    Il y a quelques années je m'étais amusé à écrire une application Gtk permettant de tester une regex. C'est encore toujours un embryon, je l'ai juste mis à jour pour compiler avec de nouvelles moutures de Boost. Pull requests bienvenues : https://github.com/LupusMichaelis/warg

  • # arf tu oublies le modificateur ? pour les * ? +

    Posté par  . Évalué à 7. Dernière modification le 09 février 2016 à 13:38.

    hé oui depuis quelques temps on peut ajouter ? à '*', '+', '?' pour les transformer les quantificateur en non-glouton.

    Par défaut a*(.*)b* sur aaaaaaaaaacbbbbbbbbbbb
    va capturer cbbbbbbbbbbb dans les ()
    si on fait a*?(.*)b*
    la totalité de la chaine sera prise dans les ()
    si on fait a*?(.*?)b*
    on aura capturé
    aaaaaaaaaac

    Autant dire que pour du parsing simple (sans prise en compte des caractère d'échappement), on peut isoler les chaines de caractères par du "(.*?)" au lieu d'écrire "[^\"]*"

    Il ne faut pas décorner les boeufs avant d'avoir semé le vent

  • # echo "topinambour: légume oublié" | sed -r 's/.+(:.+)/man page\1/'

    Posté par  . Évalué à 10.

    Bonsoir

    Il serait bien de rappeler tout le monde que les man/info pages sont les références qu'il faut privilégier

    par exemple , pour sed : "info sed" a une section sur les regex qu'il supporte

    Pour PCRE (Perl-compatible regular expression) utilisées par perl,python,ruby,java … après avoir installer "pcre-devel" on dispose de "man pcre" et surtout de "man pcrepattern"

    Pour les tests , le "man pcre" nous indique que l'outil pcretest existe

    Pensez à la planète, économisez de la BP: installer les man pages sur vos postes, et n'utilisez le web que quand c'est nécessaire :-)

  • # Question bête

    Posté par  (site web personnel) . Évalué à 2.

    Existe-t-il une regex permettant de valider une regex ?

    De manière plus générale, est-ce qu'il est possible de savoir si une grammaire X peut être validée par regex ? (Je suppose qu'il doit y avoir un champs de recherche qui s'intéresse à ce genre de question ?)

    • [^] # Re: Question bête

      Posté par  (site web personnel) . Évalué à 3.

      Oui, il faut que la grammaire soit rationnelle.

    • [^] # Re: Question bête

      Posté par  (site web personnel) . Évalué à 10.

      Existe-t-il une regex permettant de valider une regex ?

      Si on se limite aux expressions rationnelles au sens strict, non : par exemple le simple fait de pouvoir utiliser des parenthèses pour faire des groupes demande de savoir reconnaître si une expression est bien parenthésée, ce qu'une expression rationnelle ne peut pas faire. Pour ça il faut un automate à pile, car les expressions bien parenthésées ont une grammaire algébrique, mais pas rationnelle (en gros, il faut pouvoir compter, ce qu'une expression régulière ne sait pas faire).

      En pratique les « regex » (qui n'en sont pas vraiment), permettent de faire ce genre de choses grâce aux diverses extensions. Les expressions régulières Perl permettent théoriquement de faire quoi que ce soit étant donné que l'on peut exécuter conditionnellement du code quelconque si une partie de regex a matché. D'autres (comme PCRE) permettent au moins de faire des appels récursifs à des groupes, ce qui permet par exemple de décrire les expressions bien parenthésées, les palindromes, etc.

  • # ack, Unicode

    Posté par  . Évalué à 4. Dernière modification le 13 février 2016 à 10:17.

    Je suis étonné de voir que ce post ne mentionne ni ack, ni Unicode et pas le futur que sont les grammaires Perl 6. Excellent article, mais Il aurait pu être écrit il y a vingt ans.

    ack

    ack est un outil permet de chercher toute une arborescence évitant de jongler avec find et xargs. Il est intégré dans emacs et probablement dans vim.

    grammaires Perl 6

    Les systèmes dit d'expressions régulières ne sont pas modulaires et donc ne peuvent pas traiter les cas complexes.
    Perl 6 utilise un système de grammaires qui sont une forme de classe, les règles y sont des méthodes qui s'appelle les unes les autres. Notionnellement, tout au moins, puisque les grammaires sont un composite de NFA, d'analyse récursive et d'analyse d'expressions (aussi récursive mais à base de piles opérandes et opérateurs) avec des opérateurs.

    Perl 6 et Unicode : forme normalisée NFC

    Faire sérieusement de l'analyse syntaxique avec Unicode est difficile sauf avec Perl 6. Un des problèmes résolu par Perl 6 est que des graphèmes peuvent être composés de plusieurs codepoints. Avec les formes de normalisations actuelles, cela
    signifie que des opérations en O(1) ne le sont plus puisqu'on ne peut pas prévoir l'offset d'un graphème.
    Perl 6 propose la normalisation NFC avec des graphèmes synthétiques. Elle ne sort pas de l'interpréteur Perl 6 donc Perl 6 est par ailleurs conforme à la norme Unicode.
    La liste des fonctionnalités spécifique à Perl 6 serait trop longue pour ce post.
    Un seul exemple, donc.
    En Perl 6, des tâches comme mettre en majuscule une "lettre accentuée" est triviale.

    Mieux que les DSL, Perl 6 et les slangs

    Finalement, Perl 6 est écrit en Perl 6 donc est analysé en Perl 6. Il permet l'usage de slangs (argot en anglais, mais aussi sub*lang*age, c'est à dire de tisser finement syntaxiquement un langage dans un autre. On parle beaucoup de DSL mais ça, c'est beaucoup mieuxmême si pour le moment, certains détails laissent à désirer pour les perfectionnistes que sont les perlers.
    Ainsi Perl6 comprend, le langage de quotes avec un contrôle très fin de ce qui est expansé et de ce qui ne l'est pas.
    et deux langages de regex (le mot pour ne pas le terme expression rationnelles beaucou trop limitatif), celui de Perl 5 et celui de Perl 6. Oui, il y a plusieurs manières d'utiliser Perl 5 dans Perl6 !

    Les grammaires Perl 6 sont le futur

    Je peux dire avec confiance que quelque soit le succès ou l'insuccès de Perl 6, les grammaires Perl 6 influenceront beaucoup de languages comme les expressions rationnelles de Perl 1/2/3/4/5 l'ont fait depuis près de trente ans.

    La doc et les tutoriels

    La doc http://docs.perl6.org/ n'est pas complète, mais il faut déjà un bon niveau pour repérer les manques.

    La communauté Perl 6 francophone bien que non organisée est à la pointe pour les tutoriels, même si seul ceux de Laurent Rosenfeld ont pour original le français.
    Naoum Hankache est l'auteur de Perl 6 intro : http://perl6intro.com/
    Vendethiel celui de Perl 6 in y minutes: https://learnxinyminutes.com/docs/perl6/
    Laurent Rosenfeld a pondu tout une série d'excellent tutorilels: http://laurent-rosenfeld.developpez.com/tutoriels/perl/perl6/les-bases/

    • [^] # Re: ack, Unicode

      Posté par  . Évalué à 1.

      J'ai oublie de dire que ack est un outil Perl 5 et de donner un URL
      http://beyondgrep.com/

    • [^] # Re: ack, Unicode

      Posté par  . Évalué à 4.

      Merci !
      J'avais presque oublié ce langage et en relisant ton post, auquel je n'ai presque rien compris …mais qui m'inspire un total respect, je me suis souvenu:
      http://www.fastcompany.com/3026446/the-fall-of-perl-the-webs-most-promising-language

    • [^] # Re: ack, Unicode

      Posté par  . Évalué à 1.

      regex (le mot pour ne pas le terme expression rationnelles beaucou trop limitatif)

      Je suis curieux, quelle différence fais-tu entre « regular expression (regex) » et « expression rationnelle » ? En effet il y a une équivalence entre langage rationnel et régulier. Donc c'est peut-être que « regex » fait implicitement référence à des expression étendues ?

      Les systèmes dit d'expressions régulières ne sont pas modulaires et donc ne peuvent pas traiter les cas complexes.
      Perl 6 utilise un système de grammaires

      Il faut quand même faire attention, parce qu'une grammaire n'est pas une expression régulière. Le pouvoir expressif d'une expression rationnelle est plus faible, mais en contre partie tout est plus facile : stabilité des langages par intersection, union, complémentaire, morphismes, algorithmes (très) efficaces pour les traiter etc… Donc quand une expression suffit, il ne sert à rien d'aller chercher une grammaire.

      • [^] # Re: ack, Unicode

        Posté par  . Évalué à 1. Dernière modification le 13 février 2016 à 10:18.

        La réponse est le premier paragraphe de https://design.perl6.org/S05.html.
        En gros, en Perl 6, une regex est un terme qui englobe celui d'une expression régulière.
        Selon les choix de défaut pour le backtracking et de traitement des espaces, un utilisera le mot règle, token ou regex (cette fois-ci dans un sens précis).

        Quand je parle de NFA comme un des trois sous-moteurs, c'est justement ce qui implémente
        la partie expression régulière. C'est d'ailleurs pour ça que je parle dans ce cas d'appel "notionnel" des méthodes d'une grammaire. Dans le cas des NFA, c'est une convention syntaxique qui recouvre autre chose qu'un bête appel. C'est pour ça que Larry Wall insiste sur le fait qu'on a un mix de procédural (les expressions avec opérateurs, et le récursif descendant) et de déclaratif (implémenté par les NFA).
        D'ailleurs, techniquement, le moteur d'expression avec opérateurs est implémenté en terme du moteur récursif descendant dont il n'apparait pas dans la documentation. Dommage car il est essentiel pour ceux qui veulent utiliser rakudo/nqp comme base pour implémenter d'autres langage que Perl.

        Bien sûr, Perl 6 n'impose pas d'utiliser une grammaire quand cela n'est pas nécessaire pas plus qu'il n'impose de déclarer explicitement une classe et une fonction pour afficher "Hello word".
        C'est la spécificité de Perl, de proposer des raccourcis syntaxiques pour les cas les plus fréquents.
        Le raccourci allant jusqu'à l'absence syntaxique de l'entité conceptuelle sous-jacente (ou du pattern selon la terminologie de "design patterns").
        Exemple: on itère très souvent en Perl 6 mais il n'y a guère que les implémenteurs de Perl 6 qui manipulent explicitement des opérateurs.
        Là est le manque criant de "design patterns", une partie du design d'un pattern se doit d'être syntaxique.
        Evidemment ce n'est pas possible avec des langages rigides comme C++ donc ça ne leur est même pas venu à l'idée.
        C'est une nouvelle frontière explorée depuis 30 ans par Perl et soigneusement ignorée par ailleurs. Les concepteurs d'un langage vont choisir leur syntaxe dans la famille dérivée d'algol (C, Java… ) où celle dérivée de ISWIM (haskell, elm, idris…) sans trop se poser de questions.
        C'est un souci en moins et ça permet un apprentissage plus facile. Mais ça fige l'état de l'art.

        C'est le propre d'un langage que d'incarner les patterns les plus communs. Avec les slangs, Perl 6 permet de ce faire et cela va plus loin que la notion de DSL qui m'a toujours semblé bien vague.

        Jonathan Worthington, un des implémenteurs de rakudo un (à vrai dire le) compilateur Perl 6 a une belle remarque qui distille l'essence même de Perl comparé à d'autres langages. je vais paraphraser faute de retrouver l'original.
        On reproche à Perl d'être illisible car il faut du temps pour maîtriser sa syntaxe mais c'est justement ça qui permet à un programme Perl d'être compréhensible au programmeur entraîné.
        Les regex et les grammaires en sont un exemple. Un programme C qui parse "à la mano" une chaîne de caractères est très lisible pour le programmeur qui ne connait pas les regex mais le résultat est moins compréhensible qu'une regex et un réceptacle de bug potentiels dû à la manipulation explicite de pointeurs.
        Pour paraphraser une expression anglopone : Perl est un goût acquis.

        • [^] # Re: ack, Unicode

          Posté par  . Évalué à 4.

          C'est une nouvelle frontière explorée depuis 30 ans par Perl et soigneusement ignorée par ailleurs

          De très très loin, ça ressemble pourtant très fort à la métaprogrammation de F#, et aux macros de Rust, Nim et Scala, non ?

          • [^] # Re: ack, Unicode

            Posté par  . Évalué à 1.

            Oui. Je ne connaissais pas Nim. Merci pour le pointeur.

  • # fichier french

    Posté par  . Évalué à 1.

    le fichier french d'une Debian stable

    Où se trouve ce fichier ?

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.