tuteurs.ens.fr/unix/exercices/solutions/grep-sol.tml
Marc Mezzarobba c7f4bbad36 Passage en UTF-8, 2 : lignes <?xml encoding?>
Last-change: ignore this commit
2009-09-27 15:45:57 +02:00

590 lines
17 KiB
XML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
PUBLIC "-//ENS/Tuteurs//DTD TML 1//EN"
"tuteurs://DTD/tml.dtd">
<html>
<head>
<title>grep</title>
</head>
<body>
<h1>Exercices sur <code>grep</code> : corrigés</h1>
<p>
Ces exercices sont des questions de cours : les solutions se trouvent
toutes dans les pages de man des commandes en question. On suppose donc
connues les commandes de <code>less</code>, qui servent à se déplacer dans les
pages de man... et la commande servant à chercher un mot. Testez les
commandes sur des fichiers et répertoires d'essai pour vous faire la main et
comprendre ce qui se passe.
</p>
<h2><a name="options">Les options de <code>grep</code></a></h2>
<ol>
<li><a name="grep1"> <strong>Quelles sont les options de <code>grep</code>
qui permettent d'obtenir des lignes de contexte (qui précèdent et/ou suivent
la ligne où figure le mot) ? </strong> </a> <p>
Il y en a plusieurs, qui se recoupent :</p>
<ul>
<li> <code>-<em>num</em></code> : le <em>numéro</em> indique le nombre de
lignes de contexte que l'on veut voir figurer avant et après la ligne où
figure le mot recherché. Par exemple, si on veut trois lignes de contexte,
avant et après la mot (soit sept lignes au total), on tape :
<pre>
grep -3 ...
</pre>
</li>
<li> <code>-A <em>num</em></code> (<em>after</em>) : le <em>numéro</em>
indique le nombre de lignes qui doivent suivre la ligne où figure le mot. Si
on en veut quatre, on tapera :
<pre>
grep -A 4 ...
</pre>
</li>
<li> <code>-B <em>num</em></code> (<em>before</em>)  : le
<em>numéro</em> indique le nombre de lignes qui doivent précéder la ligne où
figure le mot. Si on en veut dix, on tape :
<pre>
grep -B 10 ...
</pre>
</li>
<li> <code>-C</code> (<em>context</em>), qui donne deux lignes de contexte
avant et après. En fait, les trois lignes suivantes sont strictement
équivalentes :
<pre>
grep -2 ...
grep -C ...
grep -A 2 -B 2 ...
</pre>
</li>
</ul></li>
<li><a name="grep2"> <strong>Comment faire apparaître le numéro de la
ligne où figure le mot recherché ? </strong> </a>
<p>
C'est l'option <code>-n</code> (<em>number</em>) qui sert à cela; le numéro
figure tout au début de la ligne, suivi d'un deux-points (<code>:</code>) et
du texte. Par exemple : </p>
<pre>
<span class="prompt">bireme ~ $</span> grep -n violon verlaine.tex
12:des violons de l'automne
</pre>
<p>
Quand on fait une recherche dans plusieurs fichiers, le nom du fichier figure
d'abord, puis le numéro de la ligne, et enfin le texte, le tout séparé par des
deux-points. Par exemple :
</p>
<pre>
<span class="prompt">bireme ~ $</span> grep -n violon *
verlaine.tex:12:des violons de l'automne
orchestre:45:Cordes : contrebasse, violoncelle, alto, violons.
</pre>
<strong>Que se passe-t-il quand on demande également des lignes de
contexte ?</strong> <p>
La disposition générale ne change pas, par contre, le signe utilisé pour
séparer la ligne de son numéro est un tiret (<code>-</code>) quand il s'agit
des lignes de contexte, et un deux-points quand il s'agit de la ligne voulue.
Par exemple : </p>
<pre>
<span class="prompt">bireme ~ $"</span> grep -nC violon verlaine.tex
10-
11-Les sanglots longs
12:des violons de l'automne
13-bercent mon coeur
14-d'une langueur monotone
</pre>
</li>
<li><a name="grep22"> <strong> Comment faire pour afficher le nombre
d'occurences du mot recherché ? </strong> </a> <p>
On utilise l'option <code>-c</code> (<em>count</em>) :
</p>
<pre>
<span class="prompt">bireme ~ $</span> grep -c violon *
verlaine.tex:1
orchestre:1
</pre>
</li>
<li><a name="grep23"> <strong> Comment faire pour que <code>grep</code>
ignore la casse des caractères (différence entre majuscules et minuscules)
dans sa recherche ? </strong> </a>
<p>
Par défaut, <code>grep</code> fait la différence entre les majuscules et les
minuscules; pour invalider ce comportement, on utilise l'option
<code>-i</code> (<em>ignorecase</em>).</p>
</li>
<li><a name="grep4"> <strong>Comment faire pour faire apparaître non pas les
lignes où figurent le mot, mais les noms des fichiers ?</strong> </a>
<p>
C'est l'option <code>-l</code> qui permet de faire cela : afficher les
noms des fichiers où figure au moins une fois la chaîne de caractères
recherchée.</p></li>
<li><a name="grep5"> <strong>Comment faire apparaître les lignes où ne
figurent pas le mot recherché ? </strong> </a>
<p>
On veut en fait inverser le sens de la recherche : c'est l'option
<code>-v</code> qui fait cela.
</p></li>
<li><a name="grep51"> <strong> Comment faire apparaître les noms des fichiers
ne contenant pas le mot recherché ? </strong> </a>
<p>
On utilise l'option <code>-L</code>, qui affiche les noms de fichiers où ne
figurent pas la chaîne de caractères recherchée. Il ne faut bien sûr pas
confondre les options <code>-l</code> et <code>-L</code>...
</p></li>
<li><a name="grep6"> <strong>Comment faire pour que <code>grep</code> ne
recherche que les lignes où figure le mot tel quel, et non pas ses
variantes ? </strong> </a> <p>
C'est l'option <code>-w</code> (comme <em>word</em>) qui sert à cela : un
mot complet est délimité comme suit :
</p>
<ul>
<li> Début : la chaîne de caractères est placée au début d'une ligne, ou
précédée d'un blanc, d'une tabulation ou d'une ponctuation.</li>
<li> Fin : la chaîne de caractère est placée en fin de ligne, ou suivie
d'un blanc, d'une tabulation ou d'une ponctuation.</li>
</ul>
<p>
Si donc on veut chercher «travail» et aucune forme dérivée de ce mot, on
écrit :
</p>
<pre>
grep -w travail mon-fichier
</pre>
</li>
<li><a name="grep3"> <strong>Comment faire pour chercher plusieurs mots à la
fois en faisant apparaître les numéros des lignes ?</strong> </a> <p>
On veut chercher toutes les occurences des mots «terre» et «ciel» dans les
deux premiers chapitres de la première partie de <em>Germinal</em>, avec les
numéros des lignes. On propose deux solutions, la première utilisant les
ressources de la syntaxe de <code>grep</code>, la seconde utilisant l'option
<code>-f</code> avec un fichier.</p>
<ol>
<li> <strong>Syntaxe de <code>grep</code></strong> : La structure
<code>\(mot1\|mot2\)</code> permet de chercher plusieurs mots. Ici, on tape la
ligne suivante :
<pre>
grep '\(ciel\|terre\)' <em>fichier</em>
</pre>
<p>
On met des apostrophes de part et d'autre de l'expression pour la protéger
contre le shell, c'est-à-dire pour que le shell ne cherche pas à interpréter
l'expression.
</p></li>
<li> <strong>Option «<code>-f</code> <em>fichier</em>»</strong> : dans un
fichier quelconque, que nous appellerons <code>liste</code>, on indique les
mots que l'on recherche : «ciel» et «terre». Chaque ligne correspond à un
mot recherché. Il ne faut donc pas mettre de ligne comme
<pre>
terre ciel
</pre>
<p class="continue">
car le programme chercherait la chaîne de caractères «terre ciel», qui est
assez improbable en français. Il ne faut pas non plus laisser de ligne
blanche : le programme afficherait l'ensemble du texte.
</p>
</li>
</ol>
<p>
Quelle que soit la solution retenue, on veut ensuite afficher le numéro des
lignes (option <code>-n</code>); d'autre part, pour que la recherche soit
exhaustive, il vaut mieux que <code>grep</code> ne fasse pas de différence
entre les majuscules et les minuscules, avec l'option <code>-i</code>
(<em>ignore case</em>, ignorer la casse des caractères). Il faut aussi décider
si on cherche les mots tels quels, sans leurs variantes (comme «terre» au
pluriel), ou si on accepte ces variantes. Si on ne veut que le mot sans ses
dérivés, on utilise l'option <code>-w</code>.
</p>
<p>
Pour désigner les deux fichiers où faire la recherche, on peut les écrire
littéralement :
</p>
<pre>
zola1.txt zola2.txt
</pre>
<p class="continue">
ou, mieux, utiliser les joker du shell :
</p>
<pre>
zola[12].txt
</pre>
<p><code>[12]</code> signifie «le caractère 1 ou le caractère 2».</p>
<p>
<strong>Finalement, on peut taper, au choix</strong> : </p>
<pre>
grep -inw -f liste zola1.txt zola2.txt
grep -inw -f liste zola[12].txt
grep -inw '\(ciel\|terre\)' zola1.txt zola2.txt
grep -inw '\(ciel\|terre\)' zola[12].txt
</pre>
<p>
Et on obtient :
</p>
<pre>
zola1.txt:13:ciel, le pavé se déroulait avec la rectitude d'une jetée, au
milieu de
zola1.txt:36:brûlaient si haut dans le ciel mort, pareils à des lunes fumeuses.
Mais, au
zola1.txt:50:besogne. Les ouvriers de la coupe à terre avaient dû travailler
tar d, on
zola1.txt:124:terre, lorsqu'un accès de toux annonça le retour du charretier.
Le ntement,
zola1.txt:191:bleues en plein ciel, comme des torches géantes. C'était d'une
tristesse
zola1.txt:207: Le manoeuvre, après avoir vidé les berlines, s'était assis à
terre,
zola1.txt:222:fois avec tout le poil roussi, une autre avec de la terre jusque
dans le
(...)
</pre>
<p>
Le résultat est un peu différent quand on n'utilise pas l'option
<code>-w</code>.</p>
</li>
</ol>
<h2><a name="regexp">Introduction aux expressions régulières</a></h2>
<p>
<code>grep</code> recherche des chaînes de caractères, qui peuvent être un mot
complet («terre»), une suite de lettres («tre»), ou une expression régulière.
Les expressions régulières sont des formules qui représentent des chaînes de
caractères. On cherche alors non pas un mot précis, mais des suites de
caractères correspondant aux critères demandés. Elles sont d'un usage fréquent
avec <code>grep</code> bien sûr, mais aussi avec des commandes comme
<code>less</code>, ou encore au sein d'un éditeur.
</p>
<p>
«Expressions régulières» (<em>Regular expressions</em> en anglais) se
traduisent en bon français par «expressions rationnelles», mais l'usage est de
dire «régulières».
</p>
<p>
Ces exercices n'entendent pas remplacer un cours sur les expressions
régulières, ni faire le tour de leurs possibilités.
</p>
<ol>
<li><a name="reg1"> <strong>Chercher toutes les lignes commençant par «a» ou
«A»</strong> </a>. <p>
Il faut indiquer que l'on veut le début de la ligne, avec le chapeau
(<em>caret</em> en anglais). Ensuite, on veut préciser que la ligne commence
par un «a» minuscule ou majuscule. Il y a deux façons de le faire :
</p>
<ul>
<li> Utiliser l'option <code>-i</code> qui fait ignorer la différence entre
les majuscules et le minuscules.</li>
<li> Dire que l'on cherche un «a» ou un «A». C'est à cela que servent les
crochets : <code>[abc]</code> signifie «a ou b ou c». Ici, ce sera
<code>[aA]</code>.</li>
</ul>
<p>
Enfin, il faut protéger les signes contre le shell, pour qu'il ne les
interprète pas; on met donc l'expression entre apostrophes. Remarque :
la protection des expressions régulières contre le shell est une question
complexe....
</p>
<p>
Il faut donc écrire :
</p>
<pre>
grep -i '^a' fichier
</pre>
<p class="continue">
ou
</p>
<pre>
grep '^[aA]' fichier
</pre>
</li>
<li><a name="reg2"> <strong>Chercher toutes les lignes finissant par
«rs»</strong> </a> <p>
C'est le dollar (<code>$</code>) qui représente la fin de la ligne. Il faut
donc écrire : </p>
<pre>
grep 'rs$' fichier
</pre></li>
<li><a name="reg3"> <strong>Chercher toutes les lignes contenant au moins un
chiffre</strong> </a> <p>
Pour désigner un chiffre, on peut en indiquer une liste entre crochets :
<code>[0123456789]</code>. Il est plus simple d'utiliser une classe de
caractères : <code>[0-9]</code> qui désigne, comme la solution précédente,
n'importe quel chiffre de zéro à neuf.
</p>
<p>
Il faut donc taper : </p>
<pre>
grep '[0-9]' fichier
</pre>
</li>
<li><a name="reg4"> <strong>Chercher toutes les lignes commençant par une
majuscule</strong> </a> <p>
Comme on l'a vu, c'est le chapeau qui indique le début de la ligne. Pour
indiquer que l'on cherche une majuscule, on peut soit en donner une liste
(<code>[ABCDEFGHIJKLMNOPQRSTUVWXYZ]</code>), soit utiliser une classe de
caractères : <code>[A-Z]</code>, la seconde solution étant, de loin,
préférable...
</p>
<p>
Il faut donc taper :
</p>
<pre>
grep '^[A-Z]' fichier
</pre>
</li>
<li><a name="reg5"> <strong>Chercher toutes les lignes commençant par «B»,
«E» ou «Q»</strong> </a> <p>
Il faut indiquer entre crochets les trois lettres recherchées :
</p>
<pre>
grep '^[BEQ]' fichier
</pre>
</li>
<li><a name="reg6"> <strong>Chercher toutes les lignes finissant par un point
d'exclamation</strong> </a> <p>
Le point d'exclamation n'a pas de signification particulière avec
<code>grep</code>, on peut donc le mettre tel quel :
</p>
<pre>
grep '!$' fichier
</pre>
</li>
<li><a name="reg7"> <strong>Chercher toutes les lignes ne finissant pas par
un signe de ponctuation (point, virgule, point-virgule, deux-points, point
d'interrogation, point d'exclamation)</strong> </a><p>
Il faut donner une liste de caractères, que l'on ne veut pas voir figurer; la
liste sera entre crochets, comme on l'a déjà vu, et c'est le chapeau qui
signifiera, dans ce contexte, «sauf». Par exemple, si on cherche tous les «a»,
sauf ceux suivi de «b», «c» ou «t», on écrit :
</p>
<pre>
grep 'a[^bct]'
</pre>
<p>
Il y a une seconde difficulté, qui vient de ce que certains caractères sont
spéciaux avec <code>grep</code>. Vous vous doutez que le chapeau est spécial
quand il est placé au début de l'expression, et que le dollar l'est quand il
est placé en fin d'expression. Dans notre cas :
</p>
<ul>
<li> Le point désigne n'importe quel caractère.</li>
<li> Le point d'interrogation signifie «le caractère qui précède apparaît 0 ou
1 fois». Avec <code>egrep</code>, il fonctionne tout seul, avec
<code>grep</code>, il faut le faire précéder d'un backslash pour qu'il
fonctionne; par exemple (avec <code>grep</code>), pour chercher «charbon» ou
«vagabond», on écrit :
<pre>
grep 'ar?bo' fichier
</pre>
<p class="continue">
(chercher la suite de lettre «abo» avec un «r» facultatif entre le «a» et le
«b»).</p>
</li>
</ul>
<p>
Pour que <code>grep</code> interprète littéralement ces caractères, et ne les
considère plus comme spéciaux, il faut les faire précéder d'un backslash
(<code>\</code>). Si par exemple vous cherchez toutes les lignes qui se
terminent par un point, il faut taper :
</p>
<pre>
grep '\.$' fichier
</pre>
<p>
Dans notre cas cependant, ces caractères sont protégés par les crochets. On
peut donc écrire :
</p>
<pre>
grep '[^.,;:?!]$' fichier
</pre>
<p>
On peut aussi utiliser l'option <code>-v</code>, qui prend toutes les lignes
où ne figure pas une chaîne de caractères donnée; dans ce cas, on tape :
</p>
<pre>
grep -v '[.,;:?!]$' fichier
</pre>
</li>
<li><a name="reg8"> <strong>Comment chercher tous les mots contenant un «r»
précédé de n'importe quelle lettre majuscule ou minuscule ? </strong>
</a> <p>
On cherche une chaîne de caractères qui soit indifféremment au début ou au
milieu d'un mot. N'importe quelle lettre, ce sont les classes de caractères
<code>[a-zA-Z]</code> ou <code>[:alpha:]</code>, qui sont équivalentes.
</p>
<p>
Il y a une petite subtilité avec l'emploi de classes du second type; elles
désignent un groupe de caractères, et il faut mettre une seconde paire de
crochets pour dire «n'importe quel caractère de cette classe prédéfinie».
On tape donc au choix :
</p>
<pre>
grep '[a-zA-Z]r' fichier'
</pre>
<p class="continue">
ou
</p>
<pre>
grep '[[:alpha:]]r' fichier'
</pre>
<p>
Attention, dans ces listes ne sont pas compris les caractères accentués...
</p></li>
<li><a name="reg9"> <strong>Chercher tous les mots dont la seconde lettre est
un «r»</strong> </a>.
<p>
C'est le symbole <code>\&lt;</code> qui désigne un début de mot. La première
lettre du mot est indifférente, la seconde est un «r». On écrit donc :
</p>
<pre>
grep '\&lt;.r' fichier
</pre>
<p>
Il y a cependant un problème avec les caractères accentués, que
<code>grep</code> considère comme des blancs. Dans ce cas, il vaut mieux
procéder autrement : un mot est précédé d'un début de ligne, ou d'un
blanc ou d'une tabulation. Un début de ligne, c'est le chapeau, un blanc ou
une tabulation, c'est la classe de caractères <code>[:space:]</code>.
</p>
<p>
On va se servir du pipe (<code>|</code>) qui signifie «ou». Avec
<code>grep</code>, il faut backslasher le pipe, avec <code>egrep</code> ce
n'est pas nécessaire. On écrit donc (avec <code>grep</code>) :
</p>
<pre>
grep '^.r|[[:space:]].r' fichier
</pre>
<p>
Ce n'est quand même pas si simple; les mots peuvent être précédés d'un tiret
(mots composés), d'une apostrophe, de guillemets divers (<code>``</code>,
<code>"</code>, <code>«</code>, <code>&lt;&lt;</code>), et, si l'auteur du
texte n'est pas respectueux des règles de typographie, d'une ponctuation. Il y
a donc bien des cas à envisager...
</p>
</li>
</ol>
<div class="metainformation">
Auteur : Émilia Robin, Joël Riou. Dernière modification le <date value="$Date: 2007-07-17 10:03:37 $"/>.
</div>
</body>
</html>