Simulation de la commande grep

Sujet proposé par Daniel Krob

dk@lix.polytechnique.fr

1  La recherche de motif avec grep

L'utilitaire grep1 est une commande Unix qui permet de rechercher dans un ensemble de fichiers toutes les lignes qui contiennent un motif spécifié à l'aide d'une expression régulière.

Exemple : L'exécution de la commande
    egrep 'chercher|galere' Fourberies-de-Scapin
permet par exemple de rechercher dans le fichier ``Fourberies-de-Scapin" toutes les lignes qui contiennent soit la chaîne de caractère ``chercher", soit la chaîne de caractère ``galere".

1.1  Représentation des expressions régulières

La syntaxe des expressions régulières étendues 2 manipulées par grep est décrite ci-dessous :
A regular expression is a pattern that describes a set of strings.
Regular expressions are constructed analogously to arithmetic
expressions, by using various operators to combine smaller
expressions.

Grep understands  two different versions of regular expression
syntax: "basic" and "extended." In GNU grep, there is no
difference in available functionality using either syntax. In
other implementations, basic regular expressions are less
powerful. The following description applies to extended regular
expressions; differences for basic regular expressions are
summarized afterwards.

The fundamental building blocks are the regular expressions that
match a single character. Most characters, including all letters
and digits, are regular expressions that match themselves. Any
metacharacter with special meaning may be quoted by preceding it
with a backslash.

A bracket expression is a list of characters enclosed by [ and ].
It matches any single character in that list; if the first
character of the list is the caret ^ then it matches any character
not in the list. For example, the regular expression [0123456789]
matches any single digit when the regular expression [^0123456789]
matches all characters that are not digits.

Within a bracket expression, a range expression consists of two
characters separated by a hyphen. It matches any single character
that sorts between the two characters, inclusive, using the
locale's collating sequence and character set. For example, in the
default C locale, [a-d] is equivalent to [abcd]. Many locales sort
characters in dictionary order, and in these locales [a-d] is
typically not equivalent to [abcd]; it might be equivalent to
[aBbCcDd], for example. To obtain the traditional interpretation
of bracket expressions, you can use the C locale by setting the
LC_ALL environment variable to the value C.

Finally, certain named classes of characters are predefined within
bracket expressions, as follows. Their names are self explanatory,
and they are [:alnum:], [:alpha:], [:cntrl:], [:digit:],
[:graph:], [:lower:], [:print:], [:punct:], [:space:], [:upper:],
and [:xdigit:]. For example, [[:alnum:]] means [0-9A-Za-z], except
the latter form depends upon the C locale and the ASCII character
encoding, whereas the former is independent of locale and
character set. (Note that the brackets in these class names are
part of the symbolic names, and must be included in addition to
the brackets delimiting the bracket list.) Most metacharacters
lose their special meaning inside lists. To include a literal ]
place it first in the list. Similarly, to include a literal ^
place it anywhere but first. Finally, to include a literal - place
it last.

The period . matches any single character. The symbol \w is a
synonym for [[:alnum:]] and \W is a synonym for [^[:alnum]].

The caret ^ and the dollar sign $ are metacharacters that
respectively match the empty string at the beginning and end of a
line. The symbols \< and \> respectively match the empty string at
the beginning and end of a word. The  symbol \b matches the empty
string at the edge of a word, and \B matches the empty string
provided it's not at the edge of a word.

A regular expression may be followed by one of several repetition
operators:

?      The preceding item is optional and matched at most once.
*      The preceding item will be matched zero or more times.
+      The preceding item will be matched one or more times.
{n}    The preceding item is matched exactly n times.
{n,}   The preceding item is matched n or more times.
{n,m}  The preceding item is matched at least n times, but not
       more than m times.

Two regular expressions may be concatenated; the resulting regular
expression matches any string formed by concatenating two
substrings that respectively match the concatenated
subexpressions.

Two regular expressions may be joined by the infix operator |; the
resulting regular expression matches any string matching either
subexpression.

Repetition takes precedence over concatenation, which in turn
takes precedence over alternation. A whole subexpression may be
enclosed in parentheses to override these precedence rules.

The backreference \n, where n is a single digit, matches the
substring previously matched by the nth parenthesized
subexpression of the regular expression.

In basic regular expressions the metacharacters ?, +, {, |, (, and
) lose their special meaning; instead use the backslashed versions
\?, \+, \{, \|, \(, and \).

Traditional egrep did not support the { metacharacter, and some egrep
implementations support \{ instead, so portable scripts should avoid {
in egrep patterns and should use [{] to match a literal {.

GNU egrep attempts to support traditional usage by assuming that {
is not special if it would be the start of an invalid interval
specification. For example, the shell command egrep '{1' searches
for the two-character string {1 instead of reporting a syntax
error in the regular expression. POSIX.2 allows this behavior as
an extension, but portable scripts should avoid it.

1.2  Syntaxe d'utilisation de grep

La syntaxe d'utilisation de base de grep est la suivante :
    grep motif fichier
motif est une expression régulière étendue représentée à l'aide de la syntaxe qui a été décrite dans le paragraphe précédent et où fichier représente le nom d'un fichier à examiner.
La sortie produite par cette ligne de commande est alors simplement la liste -- qui est dirigée vers la sortie standard, c'est-à-dire affichée à l'écran -- de toutes les lignes du fichier fichier qui contiennent le motif motif, c'est-à-dire qui appartiennent au langage rationnel représenté par l'expression régulière motif.

Exemple : La ligne de commande
    grep '^y.*y$' /usr/dict/words'
recherche dans le fichier /usr/dict/words, c'est-à-dire dans le dictionnaire Unix des mots anglais, l'ensemble des lignes qui commencent par la lettre y et qui se terminent par la lettre y. Cette commande renvoie ici la liste :
    yeasty
    yeomanry
    yesterday

2  Travail demandé

On demande de réaliser un programme Java qui implémente partiellement la commande grep telle qu'elle a été décrite au paragraphe précédent.
On dégagera pour cela un sous-ensemble de l'ensemble des expressions régulières généralisées décrites au paragraphe précédent, qui sera suffisamment simple pour ne pas alourdir artificiellement le projet par trop de prises en compte de détails de syntaxe. Le champ d'application de la commande grep implémentée dans le cadre de ce projet se réduira bien entendu aux catégories d'expressions régulières que vous aurez choisies.

Indication : L'algorithme sur lequel s'appuie la commande grep est bien entendu celui qui reconnait si un mot donné appartient au langage rationnel représenté par une expression régulière donnée. Cet algorithme pourra typiquement être implémenté en construisant un automate fini de petite taille équivalent à une expression régulière donnée, puis en testant si chaque ligne d'un fichier est reconnue par cet automate. La construction d'un tel automate pourra être effectuée en utilisant par exemple l'algorithme de Glushkov décrit dans [2].

3  Extensions possibles

L'instruction grep possède de nombreuses options que l'on trouvera à l'entrée grep du manuel Unix (faire man grep sous Unix). On pourra ainsi étendre le sujet en implémentant en Java une ou plusieurs de ces options d'utilisation de la commande grep.
Une autre extension consiste à étudier l'impact de la modification du noyau de la commande grep en réalisant des implémentations comparatives d'autres algorithmes de reconnaissance de mots par des expressions régulières (voir [2] à ce sujet).

Références

[1]
Abrahams P.W., Larson B.R., Unix for the impatient, Addison Wesley, 1996 3
[2]
Pin J.E., Automates finis et applications, Polycopié de majeure, Département d'informatique, Ecole Polytechnique

1
grep est l'acronyme de global regular expression print.
2
C'est-à-dire incluant la possibilité d'utiliser la complémentation.
3
Traduction française publiée chez Vuibert Informatique

Ce document a été traduit de LATEX par HEVEA.