Retour / Séminaire informatique / Documents et information / Le grep pour tous

Le Grep pour tous


Logiciel requis

Le langage Grep présenté ici est un élément bien établi de la culture Unix. Le présent article se rapporte spécifiquement à l'usage d'un éditeur sur Mac, gratuit et incontournable : BBEdit 3.5. Sous Windows, une fonctionalité similaire est fournie par l'excellent éditeur de pages web EditPlus 2.11. L'argument de l'article concerne la reconnaissance par l'éditeur d'une "expression régulière". L'article n'a guère d'intérêt si vous n'avez pas un éditeur ayant cette capacité.

Le langage qui a pour but l'étude et la manipulation d'assemblages réguliers de signes est simple et puissant.


Le meilleur moyen de saisir l'intérêt du grep est de regarder un exemple. Considérons un fichier qui contient des centaines de définitions : le "Jargon Lexicon.txt 4.0.0". [ Un fragment commenté et traduit est présenté plus loin dans cette série d'articles. ]

grep
----
Unix.  Globally search for the Regular Expression and 
Print the lines containing matches to it.  [Chercher 
globalement une expression régulière et afficher les 
lignes qui contiennent des correspondances à cette 
expression.]

Mot suivant
-----------
Définition etc.

Supposons qu'on veuille présenter une longue liste de ces définitions de telle manière qu'on y reconnaisse sans ambiguïté un signe qui indique le passage d'une définition à la suivante. Ajouter des signes sans rien enlever ou ajouter le contenu est l'action de baliser. Une balise peut être n'importe-quel signe, de préférence un signe qui ne risque pas d'être confondu avec un autre mot.

§fiche§
grep

Unix.  Globally search for the Regular Expression and 
Print the lines containing matches to it.  [Chercher 
globalement une expression régulière et afficher les 
lignes qui contiennent des correspondances à cette 
expression.]

§fiche§
Mot suivant

Définition etc.

On voit bien que le balisage de cette liste ne pose aucun problème de fond parce qu'il y a des indices dans le texte. On ne voit pas, en revanche, comment procéder à un "chercher/remplacer" qui puisse effectuer le balisage immédiatement. C'est pourtant ce qui a été fait !

Le mot défini est toujours précédé d'une ligne vide et toujours suivi d'une ligne comprenant un certain nombre de "-". Il est assez évident que la ligne composée "d'un certain nombre de '-'" justifie à coup sûr la pose d'une balise deux lignes avant. Il est assez évident aussi que le "chercher/remplacer" ordinaire ne nous sera d'aucun secours pour obtenir l'effet désiré. Il est évident enfin que le lexique comprend par nature un très grand nombre de définitions et qu'effectuer le travail à la main n'entre pas en ligne de compte.

La solution est le grep, qui signifie donc "chercher globalement une expression régulière et afficher les lignes qui contiennent des correspondances à cette expression". Nous sommes en plein jargon et cette définition nous apporte peu de lumière. Voyons concrètement comment se présente le grep avec BBEdit pour la proposition de balisage dans notre exemple:

Chercher ceci: "(\r)([^\r]*)(\r)(--*)"
Et remplacer par ceci: "§fiche§\1\2\3"

Donnons une traduction en langue naturelle de ces lignes:

Chercher un retour chariot suivi d'un nombre arbitraire de symboles quelconques autres qu'un retour suivi d'un retour puis d'un "-" suivi enfin d'un nombre arbitraire de "-". Formons des groupes dans cette définition à l'aide de parenthèses:

(retour)(autre chose)(retour)(un ou plusieurs "-")

Il y a 4 groupes.

Remplaçons cela par "§fiche§" suivi des trois premiers groupes tels qu'on les a trouvés et omettons le dernier groupe pour obtenir le résultat ci-dessus.

§fiche§(retour)(autre chose)(retour)

La formulation de l'opération est scandaleusement compacte et illisible. Son exécution est scandaleusement immédiate. Il y a quelque chose de singulièrement élégant dans ce procédé dont on constate l'efficacité quand il s'agit de l'appliquer des milliers de fois.

Motivation

Les modifications en série impossibles à définir comme simples "chercher et remplacer partout" sont loin d'être rares pour qui veut traiter du texte à des fins de publication.

Le Langage

Le Grep est un procédé de Recherche/sélection/remplacement où, au lieu de chercher une chaîne donnée, on cherche une chaîne dans le document qui correspond à un motif donné, selon des règles précises, en partant du début du document. On obtient, le cas échéant, une sélection continue. Le remplacement s'effectue alors encore d'après un autre motif, toujours selon des règles bien précises.

La présentation qui suit est la traduction de celle qui figure dans la documentation en anglais de BBEdit Light 3.5.


[début de citation]

Règles pour trouver

1. Tout caractère, à certaines exceptions près (qui seront décrits plus bas) constitue un motif qui correspond à lui-même.

2. Un motif x suivi d'un motif y forme un motif xy qui correspond à toute chaîne bu où b correspond à x et u à y. Ainsi, nous pouvons prendre le motif composé xy et lui adjoindre le motif z par concaténation pour former le motif xyz.

3. Le caractère . est un motif qui correspond à tout caractère.

4. Le caractère \ suivi de n'importe quel caractère sauf l'un des chiffres 1-9 est un motif qui correspond à ce caractère. Ceci peut servir à reconnaître les caractères spéciaux comme le caractère . (pour trouver un . on chercherait \.). [Autre caractère spécial: # représente un chiffre et \# représente un #. Certains caractères, sans être spéciaux en eux-mêmes le deviennent quand ils sont précédés du signe \: \r (retour) \n (retour Unix) \t (tabulation) \f (saut de page)]

5. Une chaîne de caractères entourée de crochets rectangulaires (un [ et un ]) forme un motif [s] qui correspond à une occurence singulière de l'un des caractères de la chaîne s. Note: la casse est toujours prise en compte pour les caractères entre crochets rectangulaires. Les lettres doivent correspondre exactement.

5a. Le motif [^B] correspond à tout caractère qui n'est pas présent dans la chaîne B. Les caractères spéciaux sont traités en littéraux dans ce cadre. Encore une fois, la casse compte.

5b. Si une chaîne de trois caractères de la forme [a-b] se produit dans le motif p alors le motif correspond à tous les caractères de a à b inclusivement. Les caractères spéciaux sont traités en littéraux, c'est à dire, [!-.] dénote tous les caractères de ! à .. Les caractères isolés et les plages de caractères se rencontrent entre crochets. Notons que la seule façon d'inclure le caractère ] dans p est de le placer au début. De même la seule façon d'inclure - est de le placer au début ou à la fin de p.

6a. Tout motif p formé selon une combinaison quelconque des règles 1 ou 3-5b suivi d'un * forme un motif p* qui correspond à zéro, une ou plusieurs occurences consécutives des caractères correspondant à p.

[ Note de RA : le motif (.*) signifiant "n'importe-quelle chaîne" ne dépasse pas les \r qui séparent les lignes. ]

6b. Tout motif p formé selon une combinaison quelconque des règles 1 ou 3-5b suivi d'un + forme un motif p+ qui correspond à une ou plusieurs occurences consécutives des caractères correspondant à p.

6c. Tout motif p formé selon une combinaison quelconque des règles 1 ou 3-5b suivi d'un ? forme un motif p? qui correspond à zéro ou une occurences des caractères correspondant à p.

[ Note de RA : le motif p* ou p? est toujours trouvé puisque 0 occurences représentent une sélection vide. ]

Nous pouvons ainsi former des motifs composés de sous-motifs. Il sera utile de repérer les sous-chaînes correspondant aux sous-motifs pour manipuler ces sous-chaînes.

7. Un motif entouré de ( et ) est un motif qui correspond à ce à quoi correspond le sous-motif. C'est utile pour trouver deux ou plusieurs occurences d'une même chaîne et aussi pour effectuer des remplacements.

Il est quelquefois utile de limiter le motif en fonction de sa place dans une suite de lignes.

8. Un motif p précédé d'un ^ forme un motif ^p. Si le motif ^p n'est précédé d'aucun autre motif, il correspond à ce à quoi correspond p du moment que le premier caractère correspondant à p se trouve au début d'une ligne. Si le motif ^p est précédé d'un autre motif alors le ^ est pris littéralement.

9. Un motif p qui est suivi d'un $ forme un motif p$. Si le motif p$ n'est suivi d'aucun autre motif alors il corresopnd à ce à quoi correspond p du moment que le dernier caractère de la chaîne qui correspond à p se produit à la fin d'un ligne. Si le motif p$ est suivi d'un autre motif alors le $ est pris littéralement.

Notez que les caractères ^ et $ limitent aux frontières des lignes et peuvent se combiner pour correspondre à une ligne entière.

Règles pour remplacer

Lorsqu'une chaîne de remplacement est prévue, les substitutions suivantes se produisent avant tout remplacement de texte:

1. Chaque occurence du caractère & est remplacée par la denière chaîne correspondant au motif complet.

2. Chaque occurence de la forme \n où n est un chiffre 1-9 est remplacée par la dernière chaîne correspondant au sous-motif commençant avec la nième occurence de (.

3. Chaque occurence de la forme \p où p est une chaîne autre qu'un chiffre 1-9 est remplacée par p. [Evidemment, les caractères "non-spéciaux" s'insèrent dans les chaînes de remplacement sans qu'il soit nécessaire de les faire précéder d'un \.]

(...)
[ Note de RA : l'exposé débouche enfin sur cet exemple d'inversion des arguments : ]

Texte original: foo(1,*bar);
Texte voulu: foo(*bar, 1);

Motif à trouver: foo\(([^,]*),([^)]*)\)
Motif de remplacement: foo(\2, \1)

Echec du procédé à prévoir pour le texte original: foo(1,(*bar)+2);
Nouveau motif corrigé à trouver: foo\(([^,]*),([^;]*)\);

On analyse dans cet exemple le motif Grep en examinant ses sous-motifs. C'est aussi une bonne façon de construire un motif. Le Grep représente un petit langage de programmation plutôt cryptique où chaque motif est un programme et chaque sous-motif une fonction ou commande dans le programme. Si vous commencez la création d'un motif Grep en testant un petit sous-motif et que vous ajoutez ensuite progressivement des sous-motifs supplémentaires, testés eux-aussi, vous devriez découvrir que le construction de motifs Grep complexes n'est pas si inaccessible que vous avez d'abord pensé.

[fin de citation]


Conseils

Si tout cela "n'est pas si inaccessible", il faudrait tout de même avoir un sens de l'humour assez sinistre pour conclure que c'est facile. Deux choses facilitent néanmoins la découverte et la mise en oeuvre de ce langage: L'exposé complet des règles est court et les tâtonnements même malhabiles finissent toujours par donner des résultats. La difficulté n'est guère supérieure à celle de Tetris. Voici quelques recommandations:


Conclusion

Vous vous apercevrez peut-être que des formulations très différentes produisent le même résultat. C'est normal. Tout langage de programmation peut exprimer une idée de multiples façons - et pas seulement par l'emploi de synonymes. C'est cela sans doute qui fait de la programmation une activité joyeuse.

Programmer, c'est guider l'action autonome du logiciel et ce n'est qu'abusivement qu'on l'assimile bien souvent à la rédaction d'un code procédural complexe. La programmation très simple expliquée ici ouvre la voie à une activité qu'on n'imagine tout simplement pas sans la programmation.


Clefs: 
application
découpage
jeu
langue

Retour / Séminaire informatique / Documents et information / Le grep pour tous