tuteurs.ens.fr/unix/exercices/solutions/grep-sol.tml

591 lines
16 KiB
Text
Raw Normal View History

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