17. Expressions rationnelles

... l'activité intellectuelle associée avec le développement de logiciels est à coup sûr d'un grand enrichissement.

-- Stowe Boyd

Pour utiliser complètement la puissance de la programmation par script shell, vous devez maîtriser les expressions rationnelles. Certaines commandes et utilitaires habituellement utilisés dans les scripts, tels que grep, expr, sed et awk interprètent et utilisent les ER. À partir de la version 3, Bash possède son propre opérateur de correspondance d'expression rationnelle : =~.

17.1. Une brève introduction aux expressions rationnelles

Une expression est une chaîne de caractères. Ces caractères qui ont une interprétation en dehors de leur signification littérale sont appelés des méta caractères. Par exemple, un symbole entre guillemets peut dénoter la parole d'une personne, ditto, ou une méta-signification [78] pour les symboles qui suivent. Les expressions rationnelles sont des ensembles de caractères et/ou méta-caractères qui correspondent ou spécifient des modèles.

Une expression rationnelle contient un élément ou plus parmi les suivants :

  • Un ensemble de caractères. Ces caractères conservent leur signification littérale. Le type le plus simple d'expression rationnelle consiste en seulement un ensemble de caractères, sans métacaractères.

  • Une ancre. Elles désignent la position dans la ligne de texte à laquelle doit correspondre l'ER. Par exemple, ^ et $ sont des ancres.

  • Modificateurs. Ils étendent ou réduisent l'ensemble de texte auquel l'ER doit correspondre. Les modificateurs incluent l'astérisque, les crochets et l'antislash.

Les principales utilisations des expressions rationnelles (ER) sont la recherche de texte ou la manipulation de chaînes. Une ER correspond à un seul caractère ou à un ensemble de caractères (une sous-chaîne ou une chaîne complète).

  • L'astérisque -- * -- correspond à toute répétition de caractères d'une chaîne ou d'une ER la précédant, incluant zéro caractère.

    « 1133* » correspond à 11 + un ou plus de 3 : 113, 1133, 1133333 et ainsi de suite.

  • Le point -- . -- correspond à un seul caractère, sauf le retour à la ligne. [79]

    « 13. » correspond à 13 + au moins un caractère (incluant une espace): 1133, 11333 mais pas 13 (un caractère supplémentaire manquant).

    Voir Exemple 15.18, « Solutionneur de mots croisés » pour une démonstration de la correspondance par un point (un seul caractère).

  • La puissance -- ^ -- correspond au début d'une ligne mais, quelque fois, suivant le contexte, inverse la signification d'un ensemble de caractères dans une ER.

  • Le signe dollar, $, à la fin d'une ER correspond à la fin d'une ligne.

    « XXX$ » correspond à XXX à la fin d'une ligne.

    « ^$ » correspond à des lignes blanches.

  • Les crochets -- [...] -- englobent un ensemble de caractères pour réaliser une correspondance dans une seule ER.

    « [xyz] » correspond aux caractères x, y ou z.

    « [c-n] » correspond à tout caractère compris entre c et n.

    « [B-Pk-y] » correspond à tout caractère compris entre B et P et entre k et y.

    « [a-z0-9] » correspond à toute lettre en minuscule et à tout chiffre.

    « [^b-d] » correspond à tous les caractères sauf ceux compris entre b et d. Ceci est un exemple de l'inversion de la signification de l' ER suivante grâce à l'opérateur ^ (prenant le même rôle que ! dans un contexte différent).

    Les séquences combinées de caractères entre crochets correspondent à des modèles de mots communs. « [Yy][Ee][Ss] » correspond à yes, Yes, YES, yEs et ainsi de suite. « [0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][0-9][0-9] » correspond à tout numéro de sécurité sociale (NdT : du pays d'origine de l'auteur).

  • L'antislash -- \ -- échappe un caractère spécial, ce qui signifie que le caractère est interprété littéralement.

    Un « \$ » renvoie la signification littérale de « $ » plutôt que sa signification ER de fin de ligne. De même un « \\ » a la signification littérale de « \ ».

  • Les signes « inférieur et supérieur » échappés -- \<...\> -- indiquent les limites du mot.

    Ces signes doivent être échappés, sinon ils n'ont que leur signification littérale.

    « \<le\> » correspond au mot « le » mais pas aux mots « les », « leur », « belle », etc.

    bash$ cat fichiertexte
    This is line 1, of which there is only one instance.
    This is the only instance of line 2.
    This is line 3, another line.
    This is line 4.
    
    
    bash$ grep 'the' fichiertexte
    This is line 1, of which there is only one instance.
    This is the only instance of line 2.
    This is line 3, another line.
    
    
    bash$ grep '\<the\>' fichiertexte
    This is the only instance of line 2.
                  
    
  • ER étendues. Des méta-caractères supplémentaires ajoutés à l'ensemble de caractères. Utilisées dans egrep, awk et Perl.

  • Le point d'interrogation -- ? -- correspond à aucune ou une instance de la précédente ER. Il est généralement utilisé pour correspondre à des caractères uniques.

  • Le signe plus -- + -- correspond à un ou plus de la précédente ER. Il joue un rôle similaire à *, mais ne correspond pas à zéro occurrence.

    # Les versions GNU de sed et awk peuvent utiliser "+",
    # mais il a besoin d'être échappé.
    
    echo a111b | sed -ne '/a1\+b/p'
    echo a111b | grep 'a1\+b'
    echo a111b | gawk '/a1+b/'
    # Tous sont équivalents.
    
    # Merci, S.C.
    
  • Les « accolades » échappées -- \{ \} -- indiquent le nombre d'occurrences à filtrer par une précédente ER.

    Il est nécessaire d'échapper les accolades car, sinon, elles ont leur signification littérale. Cette usage ne fait techniquement pas partie de l'ensemble des ER de base.

    « [0-9]\{5\} » correspond exactement à cinq entiers (caractères entre 0 et 9).

    [Note]

    Note

    Les accolades ne sont pas disponibles comme ER dans la version « classique » (non conforme à POSIX) de awk. Néanmoins, gawk dispose de l'option --re-interval qui les autorise (sans être échappés).

    bash$ echo 2222 | gawk --re-interval '/2{3}/'
    2222
                  
    

    Perl et quelques versions de egrep ne nécessitent pas les accolades échappées.

  • Les parenthèses -- ( ) -- délimitent des groupes d'ER. Elles sont utiles avec l'opérateur « | » et lors de l'extraction de sous-chaînes en utilisant expr.

  • L'opérateur d'ER « ou » -- | -- correspond à n'importe lequel d'un ensemble de caractères constituant l'alternative.

    bash$ egrep 're(a|e)d' misc.txt
    People who read seem to be better informed than those who do not.
     The clarinet produces sound by the vibration of its reed.
                  
    
[Note]

Note

Quelques versions de sed, ed et ex supportent les versions échappées des expressions rationnelles étendues décrites ci-dessus, comme le font les outils GNU.

  • Classes de caractères POSIX. [:class:]

    Ceci est une autre façon de spécifier un intervalle de caractères à filtrer.

  • [:alnum:] correspond aux caractères alphabétiques et numériques. Ceci est équivalent à A-Za-z0-9.

  • [:alpha:] correspond aux caractères alphabétiques. Ceci est équivalent à A-Za-z.

  • [:blank:] correspond à une espace ou à une tabulation.

  • [:cntrl:] correspond aux caractères de contrôle.

  • [:digit:] correspond aux chiffres (décimaux). Ceci est équivalent à 0-9.

  • [:graph:] (caractères graphiques affichables). Correspond aux caractères compris entre ASCII 33 - 126. Ceci est identique à [:print:], ci-dessous, mais exclut le caractère espace.

  • [:lower:] correspond aux caractères alphabétiques minuscules. Ceci est équivalent à a-z.

  • [:print:] (caractères imprimables). Correspond aux caractères compris entre ASCII 32 - 126. C'est identique à [:graph:], ci-dessus, mais en ajoutant le caractère espace.

  • [:space:] correspond à toute espace blanche (espace et tabulation horizontale).

  • [:upper:] correspond à tout caractère alphabétique majuscule. Ceci est équivalent à A-Z.

  • [:xdigit:] correspond aux chiffres hexadécimaux. Ceci est équivalent à 0-9A-Fa-f.

    [Important]

    Important

    Les classes de caractères POSIX nécessitent généralement d'être protégées ou entre doubles crochets ([[ ]]).

    bash$ grep [[:digit:]] fichier.test
    abc=723
                  
    

    Ces classes de caractères pourraient même être utilisées avec le remplacement, jusqu'à un certain point.

    bash$ ls -l ?[[:digit:]][[:digit:]]?
    -rw-rw-r--    1 bozo  bozo         0 Aug 21 14:47 a33b
                  
    

    Pour voir les classes de caractères POSIX utilisées dans des script, référez-vous à l'Exemple 15.21, « toupper : Transforme un fichier en majuscule. » et l'Exemple 15.22, « lowercase : Change tous les noms de fichier du répertoire courant en minuscule. ».

Sed, awk et Perl, utilisés comme filtres dans des scripts, prennent des ER en arguments lorqu'une transformation, ou une analyse de fichiers ou de flux doit se faire. Voir l'Exemple A.12, « behead: Supprimer les en-têtes des courriers électroniques et des nouvelles  » et l'Exemple A.17, « tree: Afficher l'arborescence d'un répertoire » pour des illustrations sur ceci.

La référence sur ce thème complexe est Mastering Regular Expressions de Friedl. Sed & Awk par Dougherty et Robbins donne aussi un traitement très lucide des ER. Voir la Bibliographie pour plus d'informations sur ces livres.



[78] Une méta-signification est la signification d'un terme ou d'une expression sur un plus haut niveau d'abstraction. Par exemple, la signification litérale d'une expression rationnelle est une expression ordinaire qui se conforme à l'usage accepté. La méta-signification est drastiquement différent. C'est discuté en longueur dans ce chapitre.

[79] Comme sed, awk et grep travaillent ligne par ligne, il n'y aura habituellement pas de retour à la ligne à chercher. Dans les cas où il existerait un retour à la ligne dans une expression à plusieurs lignes, le point correspondra au retour à la ligne.

#!/bin/bash

sed -e 'N;s/.*/[&]/' << EOF   # Document en ligne
ligne1
ligne2
EOF
# SORTIE:
# [ligne1
# ligne2]



echo

awk '{ $0=$1 "\n" $2; if (/ligne.1/) {print}}' << EOF
ligne 1
ligne 2
EOF
# SORTIE:
# ligne
# 1


# Merci, S.C.

exit 0