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

1208 lines
36 KiB
Text
Raw Normal View History

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
PUBLIC "-//ENS/Tuteurs//DTD TML 1//EN"
"tuteurs://DTD/tml.dtd">
<html>
<head>
<title>Pipes</title>
</head>
<body>
<h1><em>Pipes</em>, jokers et redirections : corrigés</h1>
<h2><a name="cat">La commande <tt>cat</tt></a></h2>
<!-- ########################################################## !-->
<ol>
<li> <strong> <a name="cat1">Comment visualiser deux fichiers l'un après
l'autre à l'aide de la commande <tt>cat</tt> ? </a> </strong><p>
Il suffit de taper les deux noms de fichiers à la suite; par exemple, pour
visualier <tt>bla</tt> puis <tt>blo</tt>, on tape :</p>
<pre>
cat bla blo
</pre></li>
<li> <strong> <a name="cat2">Comment faire une copie d'un fichier sans
utiliser <tt>cp</tt> ni ouvrir d'éditeur de texte ? </a> </strong>
<p>
On demande à <tt>cat</tt> d'afficher le contenu d'un fichier, puis de placer
le résultat dans un fichier :</p>
<pre>
cat toto &gt; copie
</pre></li>
<li> <strong> <a name="cat3">Utiliser <tt>cat</tt> pour écrire un peu de
texte et mettre le résultat dans un fichier <tt>notes</tt>. </a> </strong>
<p>
On demande à <tt>cat</tt> de rediriger sa sortie dans un fichier, puis on n'a
plus qu'à taper le texte et à indiquer la fin du texte avec <tt>^D</tt> :
</p>
<pre>
<span class="prompt">galion ~ $</span> ls
maitrise.tex
<span class="prompt">galion ~ $</span> cat &gt; notes
Faire la bibliographie
Revoir le chapitre 1.
^D
<span class="prompt">galion ~ $</span> ls
maitrise.tex notes
<span class="prompt">galion ~ $</span> cat notes
Faire la bibliographie
Revoir le chapitre 1.
<span class="prompt">galion ~ $</span>
</pre>
<p>
C'est bien sûr un moyen un peu spartiate pour écrire du texte, mais dans le
cas de choses très courtes dans ce style, ce peut être plus rapide que de
lancer un véritable éditeur.
</p></li>
<li> <strong> <a name="cat4">Quelle différence y a-t-il entre </a> </strong>
<pre>cat bla blo &gt; blu </pre> <p class="continue"><strong>et</strong></p>
<pre>cat bla blo &gt;&gt; blu </pre>
<p>
<strong>Que se passe-t-il, pour chaque ligne, selon que le fichier <tt>blu</tt>
existe ou n'existe pas ?</strong></p>
<pre>cat bla blo &gt; blu </pre> <p class="continue">
concatène les deux fichiers <tt>bla</tt> et
<tt>blo</tt>, et place le résultat dans un nouveau fichier appelé
<tt>blu</tt>. Si <tt>blu</tt> existe déjà, le shell affiche un message
d'erreur et ne fait rien.</p>
<p>
<tt>cat bla blo &gt;&gt; blu </tt> place la concaténation de <tt>bla</tt> et
<tt>blo</tt> à la fin d'un fichier <tt>blu</tt> déjà existant. S'il n'existe
pas, le shell affiche un message d'erreur et en reste là, sans créer de
fichier <tt>blu</tt>. </p>
<p>
Les deux redirections ne sont donc pas du tout équivalentes, mais leur action
dépend du shell, qui a des options qui modifie leur comportement par défaut.
</p></li>
<li> <strong> <a name="cat5">Comment obtenir un fichier <tt>blo</tt> qui
corresponde à un fichier <tt>bla</tt> dont les lignes seraient désormais
numérotées ? </a> </strong>
<p>
Il faut utiliser l'option <tt>-n</tt> de <tt>cat</tt>. Par exemple :
</p>
<pre>
<span class="prompt">galion ~ $</span> cat bla
Pomme
Poire
Prune
<span class="prompt">galion ~ $</span> cat -n bla &gt; blo
<span class="prompt">galion ~ $</span> cat blo
1 Pomme
2 Poire
3 Prune
<span class="prompt">galion ~ $</span>
</pre></li>
</ol>
<h2><a name="jokers">Jokers et expressions régulières</a></h2>
<ol>
<li> <strong> <a name="jokers1">Vous avez chez vous des fichiers appelés
<tt>essai1</tt>, <tt>essai2</tt>, <tt>essai3</tt> et <tt>essai4</tt>. Comment
les effacer en une seule ligne de commande ? </a> </strong>
<p>
Il s'agit de donner le nom des fichiers en indiquant qu'ils commencent par
<tt>essai</tt> et qu'ils finissent par un chiffre. Il y a en fait plusieurs
façons de faire :
</p>
<ul>
<li> <tt>rm essai[1234]</tt> : les crochets signifient «l'un des caractères
qui se trouvent entre crochets»;</li>
<li> <tt>rm essai[1-4]</tt> : c'est le même principe, sauf que l'on indique un
intervalle au lieu d'écrire explicitement les chiffres;</li>
<li> <tt>rm essai?</tt> : le point d'interrogation désigne n'importe quel
caractère unique, le point y compris, sauf si le nom du fichier commence par
un point (<tt>ls ?pine</tt> dans <tt>$HOME</tt> ne vous donnera pas, par
exemple, la liste des fichiers de configuration de Pine : <tt>.pinerc</tt>,
etc).
<p>
Ici, on trouvera tous les fichiers commençant par <tt>essai</tt>, suivi d'un
unique caractère qui peut ne pas être un chiffre.</p>
</li>
<li> <tt>rm essai*</tt> : c'est la formulation la plus vague : effacer les
fichiers dont le nom commence par <tt>essai</tt> suivi de quelque chose,
c'est-à-dire d'un nombre indéfini de caractères, ou aucun.</li>
</ul></li>
<li> <strong> <a name="jokers2">Dans mon répertoire d'accueil, j'ai un
certain nombre de fichiers avec un suffixe <tt>.c</tt>. Je désire les
regrouper dans un répertoire que j'appelerai <tt>C/</tt>. Quelles sont les
commandes que je dois taper ? </a> </strong>
<p>
On commande par créer un répertoire <tt>C</tt> avec <tt>mkdir</tt>, puis, avec
<tt>mv</tt>, on déplace tous les fichiers ayant un suffixe <tt>.</tt> et
dont le nom est fait d'une suite de caractères quelconques :
</p>
<pre>
<span class="prompt">galion ~ $</span>  ls
hello* zoinx*
hello.c zoinx.c
<span class="prompt">galion ~ $</span>  mkdir C
<span class="prompt">galion ~ $</span>  mv *.c C/
<span class="prompt">galion ~ $</span>  ls
C/ hello* zoinx*
<span class="prompt">galion ~ $</span>  ls C/
hello.c zoinx.c
</pre>
</li>
<li> <strong> <a name="jokers3">Vous désirez regrouper dans un répertoire
<tt>Rangement</tt> les fichiers dont le nom contient un caractère minuscule
suivi d'un caractère majuscule. Quelle(s) est/sont la/les commande(s) à
donner ? </a> </strong>
<p>
On commence par créer le répertoire <tt>Rangement</tt> avec <tt>mkdir</tt>.
Pour désigner les noms des fichiers, il faut indiquer la notion de
«minuscule». On pourrait écrire explicitement l'alphabet entre crochets, pour
dire «l'un de ces caractères», puis faire de même avec les majuscules. Mais on
gagne du temps en utilisant des intervalles (<tt>[a-z]</tt> et
<tt>[A-Z]</tt>). Le reste du nom du fichier, avant et après la minuscule puis
la majuscule, est indéfini. On écrit donc :
</p>
<pre>
<span class="prompt">galion ~ $</span> mkdir Rangement
<span class="prompt">galion ~ $</span> mv *[a-z][A-Z]* Rangement/
</pre>
</li>
<li> <strong> <a name="jokers4">Même chose avec les fichiers dont le nom
contient trois voyelles à la suite. </a> </strong>
<p>
Le principe est le même, sauf que l'on indique explicitement les voyelles
entre crochets :
</p>
<pre>
<span class="prompt">galion ~ $</span> mkdir Rangement
<span class="prompt">galion ~ $</span> mv *[aeiou][aeiou][aeiou]* Rangement/
</pre>
</li>
<li> <strong> <a name="jokers5">En utilisant <tt>ls</tt> et <tt>grep</tt>,
affichez la liste des fichiers dans <tt>/bin</tt> dont le nom correspond à un
motif donné. </a>
</strong>
<p>
On peut procéder de deux façons : utiliser <tt>ls</tt> seul et des jokers, ou
rediriger <tt>ls</tt> dans <tt>grep</tt> et utiliser les expressions
régulières de <tt>grep</tt>.
</p>
<div class="attention">
<h1>&icone.attention; Attention &icone.attention;</h1>
<p>Dans la suite du corrigé, on suppose que l'on se trouve déjà dans
<tt>/bin/</tt>.</p>
<p>On met des apostrophes autour des expressions de
<tt>grep</tt> pour les protéger contre le shell. Enfin, on ne détaille pas les
expressions régulières; à noter ces points importants à propos de
<tt>grep</tt> :</p>
<ul>
<li> Le chapeau (<tt>^</tt>) en début d'expression désigne le début de la
ligne;</li>
<li> Le dollar en fin d'expression désigne la fin de la ligne;</li>
<li> Le point désigne n'importe quel caractère; </li>
<li> L'étoile signifie «zéro ou plus de fois le caractère qui précède»; c'est
un multiplicateur;</li>
<li> Comme avec les jokers du shell, <tt>[^m]</tt> veut dire «sauf le
caractère m».</li>
</ul>
</div>
<p>
On constate que <tt>grep</tt> est plus complexe mais bien plus puissant que
les jokers du shell.
</p>
<table class="tableau">
<tr>
<td> </td>
<td> Avec <tt>ls</tt> seul </td>
<td> Avec <tt>ls</tt> et <tt>grep</tt> </td>
</tr>
<tr>
<td> Commence par «a» et dont la deuxième lettre est «s» ou «t» </td>
<td> <tt> ls a[st]* </tt> </td>
<td> <tt> ls | grep '^a[st].*' </tt> </td>
</tr>
<tr>
<td> Contient «un» et se termine par «t» </td>
<td> <tt>ls *un*t </tt> </td>
<td> <tt>ls | grep '.*un.*t$' </tt> </td>
</tr>
<tr>
<td> Contient «gre» ou «st» </td>
<td> <tt> ls *(gre|st)* </tt> </td>
<td> <tt> ls | grep '\(gre\|st\)' </tt> </td>
</tr>
<tr>
<td> Contient exactement deux lettres «m» </td>
<td> </td>
<td> <tt>ls | grep '[^m]*m[^m]*m[^m]*' </tt> </td>
</tr>
<tr>
<td> Contient au moins deux lettres «m» </td>
<td> </td>
<td> <tt>ls | grep '.*m.*m.*' </tt> </td>
</tr>
<tr>
<td> Contient au moins quatre caractères et aucun chiffre </td>
<td> </td>
<td> <tt>ls | grep '^[^0-9]\{4,\}$' </tt> </td>
</tr>
<tr>
<td> Est constitué de deux lettres exactement </td>
<td> <tt>ls ?? </tt> </td>
<td> <tt>ls | grep '^..$' </tt> </td>
<td> </td>
</tr>
<tr>
<td> Commence et finit par un chiffre </td>
<td> <tt>ls [0-9]*[0-9] </tt> </td>
<td> <tt>ls | grep '^[0-9].*[0-9]$' </tt> </td>
</tr>
</table>
</li>
<li> <strong> <a name="jokers6"> Comment éliminer les lignes vides dans un
fichier ? Comment éliminer les lignes ne contenant que des blancs ?
Comment éliminer toutes les lignes blanches ?</a> </strong>
<p class="continue">
<a name="precision">Une ligne vide</a> est différente d'une ligne ne contenant
que des blancs, c'est-à-dire des espaces ou des tabulations, même si pour un
oeil humain cela revient au même. La commande de recherche sera différente
selon le cas :
</p>
<ul>
<li> <strong>Lignes vides</strong> : c'est «rien entre le début et la fin de
la ligne»; on écrit donc :
<pre>grep '^$' <em>fichier</em></pre></li>
<li> <strong>Lignes ne contenant que des blancs</strong> : on utilise une
classe de caractère préexistante : <tt>[:space:]</tt>. On écrit donc :
<pre>grep '^[[:space:]]$' <em>fichier</em></pre>
</li>
</ul>
<p>
Pour éliminer toutes les lignes blanches pour un oeil humain, on combine les
deux expressions et on utilise l'option <tt>-v</tt> qui inverse le sens de la
recherche. On n'a plus qu'à rediriger la sortie dans un fichier. On écrit
donc : </p>
<pre>grep -v '\(^[[:space:]]$\|^$\)' fichier1 &gt; fichier2 </pre>
<p class="continue">
en remplaçant éventuellement le motif par <tt>'^$'</tt> ou
<tt>^[[:space:]]$'</tt> selon que l'on veut ôter les lignes vides ou les
lignes contenant des blancs.
</p></li>
</ol>
<h2><a name="germinal">Exercices sur <em>Germinal</em></a></h2>
<ol>
<li><a name="germinal1"> <strong>Combien de fois le mot «mine» apparaît dans
chaque chapitre de la première partie de <em>Germinal</em> ?</strong></a> <p>
On cherche la chaîne de caractère «mine», qui soit un mot, éventuellement
suivi d'un «s». On va donc désigner le début du mot avec le symbole
<tt>\&lt;</tt>, la fin du mot avec le symbole <tt>\&gt;</tt>, et le «s»
optionnel avec le point d'interrogation. On veut aussi faire apparaître le
nombre d'occurences du mot, dans chaque fichier du répertoire <tt>zola</tt>,
avec l'option <tt>-c</tt> de <tt>grep</tt>. </p>
<p>
Les noms des fichiers de <em>Germinal</em> sont de la forme
<tt>zola1.txt</tt>, de 1 à 6. Pour chercher dans tous les fichiers de cette
sorte, on peut écrire, au choix (attention à ne pas confondre les
<em>wildcards</em> du shell avec les expressions régulières de grep).
</p>
<ul>
<li> <tt>zola*.txt</tt> (un ou plusieurs caractères entre <tt>zola</tt> et
<tt>.txt</tt>).</li>
<li> <tt>*.txt</tt> (tous les fichiers <tt>.txt</tt> du répertoire, mais il
pourrait y en avoir d'autres que <tt>zola*.txt</tt>).</li>
<li> <tt>zola?.txt</tt> (un seul caractère entre <tt>zola</tt> et le
suffixe).</li>
<li> <tt>zola[1-6].txt</tt> (un chiffre entre 1 et 6 inclus entre
<tt>zola</tt> et le suffixe).</li>
</ul>
<p>
On peut donc écrire :
</p>
<pre>grep -c '\&lt;mines?\&gt;' *.txt</pre>
<p>Et on obtient :</p>
<pre>
zola1.txt:5
zola2.txt:0
zola3.txt:3
zola4.txt:3
zola5.txt:1
zola6.txt:2
</pre>
<p>
Attention : les chiffres indiqués correspondent au nombre de lignes où figure
le mot «mine», mais pas au nombre réel d'occurences du mot. Pour un mot précis
comme celui-ci, la marge d'erreur est faible, mais si vous cherchez un article
défini, par exemple, cette méthode est insuffisante. C'est là qu'un langage
comme Perl est très utile.
</p></li>
<li><a name="germinal2"> <strong>Combien y a-t-il de lignes blanches dans
<tt>zola1.txt</tt> ? </strong> </a> <p>
<a name="blanche"> Une ligne blanche, </a> c'est rien entre un début de ligne
et une fin de ligne, ou alors une ligne ne contenant que des espaces et des
tabulations. On va donc chercher toutes les lignes correspondant à ce schéma
(voir <a href="#precision">plus haut</a> pour ces questions de définitions).
Comme il y a deux motifs à chercher, on utilise l'expression régulière qui
permet de chercher un motif ou un autre : </p>
<pre>\(<em>motif1</em>\|<em>motif2</em>\)</pre>
<p>
La commande à taper est :
</p>
<pre>grep '\(^[[:space:]]$\|^$\)' zola1.txt</pre>
<p>
Ensuite, on veut compter le nombre de lignes ainsi produites par
<tt>grep</tt>, avec la commande <tt>wc</tt> (<em>word count</em>); on va
utiliser l'option <tt>-l</tt> (<em>lines</em>, lignes). On tape donc :
</p>
<pre>grep '\(^[[:space:]]$\|^$\)' zola1.txt | wc -l</pre>
<p>
Et on obtient «81». Bien évidemment, on aurait pu se rappeler que l'option
<tt>-c</tt> de <tt>grep</tt> donne le nombre de lignes matchant la chaîne
recherchée, et obtenir le même résultat en ne lançant qu'un seul processus...
</p></li>
</ol>
<h2><a name="headtail"><tt>head</tt> et <tt>tail</tt></a></h2>
<!-- #################################################### !-->
<ol>
<li> <strong> <a name="headtail1">Affichez les 15 premières lignes du fichier
<tt>/etc/hosts</tt>, les 15 dernières lignes, toutes les lignes à partir de la
quinzième, les lignes 15 à 20. </a> </strong> <p>
<strong>À propos du fichier <tt>/etc/hosts</tt></strong> : les ordinateurs sur
l'Internet sont désignés par une adresse IP, constituée de 4 nombres entre 0
et 255 séparés par des points. C'est cette adresse IP qui permet à un
ordinateur d'envoyer un message (datagramme IP) à un autre.
</p>
<p>
Cependant, mémoriser les adresses IP n'est pas commode pour un humain. Pour
cette raison, les ordinateurs ont aussi un «nom», constitué d'un nombre
variable de composantes séparées par des points (par exemple,
<tt>research.att.com</tt>).
</p>
<p>
Le mécanisme de conversion d'un nom en adresse IP (ou le mécanisme inverse)
s'appelle la «résolution de noms». Elle se fait normalement par
l'intermédiaire de <em>nameservers</em>, ordinateurs sur lequel tourne le
programme BIND, qui se chargent de répondre aux questions de résolution de
noms.
</p>
<p>
Cependant, pour certaines machines très communément accédées depuis un
ordinateur donné (par exemple, les machines de la même salle) ou bien
pour éviter d'avoir à configurer un nameserver, on préfère parfois
utiliser le fichier <tt>/etc/hosts</tt> : celui-ci définit un certain nombre
de conversions nom -&gt; adresse IP qui seront consultées avant les
<em>nameservers</em>.</p>
<p>
La bibliothèque qui se charge de faire la traduction, et qui consulte d'abord
le <tt>/etc/hosts</tt> puis les <em>nameservers</em>, s'appelle la
<tt>libresolv</tt>.</p>
<ul>
<li> Afficher les 15 premières lignes de <tt>/etc/hosts</tt> :
<pre>head -15 /etc/hosts</pre></li>
<li> Afficher les 15 dernières lignes de <tt>/etc/hosts</tt> :
<pre>tail -15 /etc/hosts</pre></li>
<li> Afficher toutes les lignes à partir de la quinzième :
<pre>tail +15 /etc/hosts</pre></li>
<li> Affichez les lignes 15 à 20 :
<pre>head -20 /etc/hosts | tail -6 </pre></li>
</ul>
</li>
<li><a name="headtail2"> <strong>Récupérer les lignes 5 à 9 d'un fichier de 12
lignes</strong> </a> <p>
Il y a deux solutions :
</p>
<ol>
<li> Prendre les neuf premières lignes, et n'en garder que les cinq dernières
(de 5 à 9 inclus) :
<pre>head -9 fichier | tail -5</pre>
</li>
<li> Prendre les 8 dernières lignes (de 5 à 12 inclus) et n'en garder que
les 5 premières :
<pre>tail -8 fichier | head -5</pre>
</li>
</ol></li>
<li> <strong> <a name="headtail4">Comment afficher la cinquième ligne d'un
fichier ? </a> </strong>
<p>
On utilise la commande <tt>head</tt> pour extraire les cinq premières
lignes du fichier, puis la commande <tt>tail</tt> pour ne conserver que la
dernière des cinq : </p>
<pre> head -5 fichier | tail -1 </pre>
</li>
</ol>
<h2><a name="syst">Filtres et redirections</a></h2>
<ol>
<li> <strong> <a name="syst1">Créez un fichier dont chaque ligne commence
par un chiffre, suivi d'un slash (<tt>/</tt>), puis d'un ou plusieurs mots.
</a> </strong>
<p>
C'est la commande <tt>sort</tt> qui permet de trier des fichiers selon un
ordre donné.</p>
<ol>
<li> <strong>Affichez les lignes de ce fichier triées en ordre croissant,
suivant le nombre placé en début de ligne</strong> <p>
<tt>sort</tt> sans option trie automatiquement en fonction du premier caractère,
par ordre alphabétique si c'est une lettre, par ordre croissant si ce sont des
chiffres. Il suffit donc de taper :
</p>
<pre>sort fichier</pre>
</li>
<li> <strong>Éliminez de chaque ligne le chiffre et le caractère
«<tt>/</tt>»</strong>. <p> Pour cela, sans utiliser d'éditeur, on peut
utiliser la commande <tt>cut</tt>, qui élimine des champs dans une ligne. Par
défaut, le séparateur de champs est une tabulation. Ici, on peut demander à
<tt>cut</tt> de considérer le slash comme un séparateur de champs (option
<tt>-d</tt>, «délimiteur»), et de ne conserver que le deuxième champ du
fichier (option <tt>-f</tt>, <em>field</em>). On redirige ensuite la sortie
dans un autre fichier : </p>
<pre> cut -d/ -f2 fichier &gt; fichier2</pre>
</li>
<li> <strong>Triez ces lignes par ordre alphabétique inverse</strong>.
C'est l'option <tt>-r</tt> (<em>reverse</em>) de <tt>sort</tt> qui inverse le
résultat de la comparaison. Par défaut, <tt>sort</tt> trie par ordre
alphabétique, il suffit donc de taper :
<pre>sort -r fichier2</pre></li>
</ol>
</li>
<li><a name="syst2"> <strong>Combien de fichiers de configuration avez-vous
?</strong> </a>
<p>
La réponse n'est pas simple, car vous avez également des répertoires de
configuration, comme <tt>.netscape/</tt> qui contient entre autres vos
marque-pages (<tt>bookmarks.html</tt>), vos préférences
(<tt>preferences</tt>), etc. On va compter le nombre de fichiers commençant
par un point situés dans votre répertoire principal, en éliminant les copies
de sauvegarde (qui finissent par un tilde). Il y a plusieurs manières de
procéder.</p>
<ol>
<li> <strong>ls, grep, tr, cut</strong> <p>
On peut penser à une solution complexe et bien lourde utilisant toutes ces
commandes, ce qui permet de les apprendre plus qu'autre chose... On affiche
une liste longue avec l'ensemble des fichiers (<tt>ls -la</tt>) puis qui
élimine tous les répertoires et les liens avec <tt>grep</tt> en ne gardant que
les lignes qui commencent par un tiret : </p>
<pre>ls -al | grep '^-'</pre>
<p>
Ensuite, on utilise <tt>tr</tt> pour ne conserver qu'un seul espace entre
chaque champ de la liste, en utilisant l'option <tt>-s</tt> (<em>squeeze</em>)
qui remplace par une seule occurence la répétition d'un caractère spécifié; on
met des apostrophes autour de l'espace pour qu'il soit compris comme tel et
non comme un simple espace :
</p>
<pre>tr -s ' ' </pre>
<p>
On utilise <tt>cut</tt> pour conserver le dernier champ avec les noms de
fichiers (en l'occurence le neuvième champ), en indiquant que l'espace sert de
délimiteur de champ : </p>
<pre> cut -d' ' -f9 </pre>
<p>
Enfin, on demande à <tt>grep</tt> de ne retenir que les fichiers commençant
par un point, et ne finissant pas par un tilde, et on les comptel'option
<tt>-c</tt> de <tt>grep</tt>. Cela donne pour finir : </p>
<pre> ls -al | grep '^-' | tr -s ' ' | cut -d' ' -f 9 | grep -c '^\..*[^~]$'
</pre>
<p>
Mais que de tourments pour si peu... :-)
</p></li>
<li> <strong>ls et grep </strong> <p> Il y a plus simple... On demande à
<tt>ls</tt> une liste des fichiers commençant par un point (avec l'option
<tt>-d</tt> pour ne pas descendre dans les répertoires de configuration), on
ne garde que les lignes ne finissant pas par un <tt>/</tt> ou un tilde, et on
compte : </p>
<pre>ls -ad .* | grep -vc '\(~\|/\)$' </pre>
</li>
<li> <strong>find</strong>
<p>
On demande à <tt>find</tt> de chercher dans le répertoire courant
(<tt>.</tt>), sans descendre dans l'arborescence (<tt>-maxdepth 1</tt>), tous
les fichiers normaux (<tt>-type f</tt> : <em>file</em>), dont le nom commence
par un point (<tt>-name '.*'</tt>). On les compte ensuite avec <tt>wc</tt> :
</p>
<pre>find . -maxdepth 1 -name '.*' -type f | wc -l</pre>
<p>
Cette solution est préférable car elle ne lance que deux processus et non pas
au moins cinq comme la précédente.
</p></li>
</ol>
</li>
<li><a name="syst3"> <strong>Combien de répertoires de configuration avez-vous
?</strong> </a> <p>
Il y a deux solutions, l'une faisant appel à <tt>ls</tt> et <tt>grep</tt>,
l'autre faisant appel à <tt>find</tt>.
</p>
<ol>
<li> <strong><tt>ls</tt> et <tt>grep</tt></strong>
<p>
Le principe est le même que dans l'exercice précédent. La différence est que
<tt>grep</tt> va chercher les lignes qui commencent par un point
<strong>et</strong> finissent par un slash. Cela se fait de la manière
suivante : </p>
<pre>grep '^\..*/$'</pre>
<p>
<tt>.*</tt> signifie «n'importe quel caractère (point) répété zéro ou
plusieurs fois (étoile)».
</p>
<p>
On tape donc : </p>
<pre>ls -ad .* | grep '^\..*/$' | wc -l</pre>
<p>
On part ici du principe que vous avez un alias de <tt>ls</tt> sur <tt>ls
-F</tt> (qui met un slash après les noms de répertoires, une étoile après les
noms d'exécutables et une arobase après les noms de liens). Si ce n'est pas le
cas, ajoutez-le dans un fichier <tt>.alias</tt> ou tapez <tt>ls -lF</tt>.
</p></li>
<li> <strong><tt>find</tt></strong>
<p>
On demande à <tt>find</tt> de chercher tous les fichiers de type «répertoire»
(<tt>-type d</tt> : <em>directory</em>), et dont le nom commence par un point
(<tt>-name '.*'</tt>, les quotes servant à protéger du shell le contenu de
l'expression), dans descendre dans l'arborescence.
</p>
<p>
On tape donc : </p>
<pre>find . -maxdepth 1 type d -name '.*' | wc -l</pre>
</li>
</ol>
<p>
Il y a une inexactitude dans le résultat : <tt>find</tt> compte aussi
<tt>./</tt> dans le résultat.
</p></li>
<li> <strong> <a name="syst4">Comment mettre dans un fichier la liste de
tous les fichiers de l'arborescence à partir du répertoire courant ?
</a> </strong> <p>
C'est l'option <tt>-R</tt> de <tt>ls</tt> qui permet d'afficher
récursivement le contenu d'un répertoire et de ses sous-répertoires (à ne
pas confondre avec <tt>-r</tt> qui inverse le tri). Pour rediriger la
sortie d'une commande dans un fichier, on utilise le caractère
<tt>&gt;</tt>. Pour mettre la liste de tous les fichiers de l'arborescence
dans un fichier <tt>toto</tt>, il faut donc taper : </p>
<pre> ls -R &gt; toto </pre>
</li>
<li><a name="syst5"> <strong>Créez un fichier <tt>liste</tt> contenant la
liste de tous vos fichiers, avec leur taille, leurs droits, etc</strong></a>
<p>
Il va s'agir de faire une liste récursive de l'ensemble de votre compte
(option <tt>-R</tt> de <tt>ls</tt>), incluant les fichiers de configuration
(option <tt>-a</tt>) et les renseignements donnés par l'option <tt>-l</tt>.
</p>
<p>
De la longue liste obtenue, on ne veut retenir que les fichiers, dont les
droits commencent par un tiret (un <tt>d</tt> identifie les répertoires). On
va donc demander à <tt>grep</tt> de chercher toutes les lignes commençant par
un tiret.
</p>
<p>
Enfin, on va placer le résultat dans un fichier appelé <tt>liste</tt>. On tape
donc : </p>
<pre>ls -alR | grep '^-' &gt; liste</pre>
</li>
<li> <strong> <a name="syst6">Comment afficher uniquement les fichiers
du répertoire courant qui sont des liens symboliques ? </a> </strong>
<p>
L'option <tt>-l</tt> de <tt>ls</tt> donne une liste longue des fichiers,
incluant les droits et le type des fichiers, identifiés par une
lettre : rien pour un fichier, «d» pour un répertoire
(<em>directory</em>), «l» pour un lien (<em>link</em>); par exemple :
</p>
<pre>
drwxr-xr-x 2 emilia users 1024 mai 10 02:27 www/
</pre>
<p>
Pour obtenir la liste des fichiers du répertoire courant qui sont des liens
symboliques, il suffit donc d'afficher cette liste longue et de ne retenir
que les lignes qui commencent par un «l», grâce à la commande
<tt>grep</tt>. On utilise une expression régulière pour cela : le
chapeau (<tt>^</tt>) désigne le début de la ligne, et l'ensemble de
l'expression est placée entre apostrophes pour qu'elle ne soit pas
interprétée par le shell. On tape donc :
</p>
<pre>ls -l | grep '^l' </pre>
</li>
<li><a name="syst7"> <strong>Combien de lignes contiennent le mot
«<em>file</em>» dans la page de man de <tt>less</tt> ? </strong> </a> <p>
Il faut lancer le man de <tt>less</tt>, et ensuite chercher dedans le nombre
de lignes contenant le mot <tt>file</tt> («fichier» en anglais), avec
<tt>grep</tt>. C'est l'option <tt>-c</tt> (<em>count</em>) de <tt>grep</tt>
qui va servir.
</p>
<p>
Finalement, on écrit : </p>
<pre>man less | grep -c file</pre>
<p>
Et on obtient 205. C'est le nombre de lignes contenant au moins une fois la
chaîne de caractères <em>file</em>, mais pas le nombre d'occurences du mot
dans le fichier : il pourrait y avoir plusieurs fois <em>file</em> sur la même
ligne. On atteint là encore la limite de ce que sait faire <tt>grep</tt>.
</p></li>
<li><a name="syst8"> <strong>Quels sont les dix plus gros fichiers de
<tt>/usr/bin/</tt> ?</strong> </a>
<p>
Il faut d'abord faire une liste des fichiers avec leurs tailles (option
<tt>-l</tt> de <tt>ls</tt>. Ensuite, il faut trier ces lignes en fonction de
la taille, avec la commande <tt>sort</tt>. Une ligne est de la forme
suivante :
</p>
<pre>
-rwxr-xr-x 1 root staff 5872 Jan 21 1994 /usr/bin/rm*
</pre>
<p>
C'est le cinquième champ, celui de la taille, qui nous intéresse. La
numérotation commençant à 0 (comme les étages), pour indiquer le cinquième
champ on écrit <tt>+4</tt>. Ensuite, on veut que le tri se fasse selon un
critère numérique (option <tt>-n</tt>) et à l'envers, en commençant par les
plus grandes valeurs (option <tt>-r</tt>, <em>reverse</em>).
</p>
<p>
Pour finir, on veut les dix premières lignes du résultat. On les sélectionne
avec la commande <tt>head</tt>; par défaut, elle prend les dix premières
lignes d'un fichier, il n'est donc pas nécessaire de préciser le nombre de
lignes voulues. Finalement, on écrit :
</p>
<pre>ls -l /usr/bin | sort -nr +4 | head</pre>
</li>
<li> <strong> <a name="syst9">Pour chaque ligne du fichier
<tt>/etc/hosts</tt>, affichez : </a> </strong>
<ul>
<li> Le cinquième caractère;</li>
<li> Les caractères 5 à 10, et le treizième;</li>
<li> Tous les caractères à partir du quinzième.</li>
</ul>
<p>Une manière de résoudre cet exercice consiste à utiliser le programme
<code>sed</code> qui sert à manipuler du texte.</p>
<p>Nous allons utiliser la commande <code>s</code> de <code>sed</code>.
Sa syntaxe est la</p> <pre>s/<em>regexp</em>/<em>remplacement</em>/</pre> <p
class="continue">où <em>regexp</em> est une <a
href="&url.tuteurs;unix/exercices/grep.html">expression régulière</a> et
<em>remplacement</em> une chaîne devant la remplacer.</p>
<ul>
<li>
Pour afficher le cinquième caractère de chaque ligne, on peut faire
ceci :
<pre>
2013-10-20 00:04:58 +02:00
<span class="prompt">sas ~ $</span> cat /etc/hosts | sed -e "s/^.\{4\}\(.\).*/\1/"
</pre>
<p class="continue">Le <code>"^.\{4\}"</code> du début sert à filtrer les
4 premiers caractères de chaque ligne. Ensuite, <code>\(.\)</code> filtre
n'importe quel caractère, le fait de mettre des parenthèses enregistre le
caractère en question dans une variable que l'on peut utiliser dans
la chaîne de remplacement avec <code>"\1"</code>. Ensuite,
<code>.*</code> filtre le reste de la ligne.</p></li>
<li>
Sur le même principe, pour afficher les caractères 5 à 10 et le treizième :
<pre>
2013-10-20 00:04:58 +02:00
<span class="prompt">sas ~ $</span> cat /etc/hosts | sed -e "s/^.\{4\}\(.\{6\}\)..\(.\).*/\1 \2/"
</pre>
</li>
<li>
Et enfin, pour afficher tous les caractères à partir du quinzième :
<pre>
2013-10-20 00:04:58 +02:00
<span class="prompt">sas ~ $</span> cat /etc/hosts | sed -e "s/^.\{14\}//"
</pre>
</li>
</ul>
</li>
</ol>
<h2><a name="yp">Exercices sur les pages jaunes</a></h2>
<!-- #################################################### !-->
<p>
Les pages jaunes (<em>yellow pages</em>) sont un système qui centralise
certaines bases de données communes à toutes les machines du système (logins
et noms utilisateurs, mots de passes, groupes d'utilisateurs, noms et adresses
des autres machines sur le réseau) sur un petit nombre de serveurs redondants;
cela permet de garder des bases de données cohérentes, plus facilement que si
on devait mettre à jour leur contenu sur plusieurs centaines de machines à la
fois.
</p>
<p>
On dit plutôt aujourd'hui NIS ou Network Information System, Yellow Pages
étant une marque déposée de British Telecom.</p>
<p>
La commande <tt>ypcat</tt> permet de visualiser une base de données: citons en
particulier: <tt>ypcat passwd</tt>, <tt>ypcat group</tt>, <tt>ypcat
hosts</tt>.</p>
<p>
Ces bases de données sont les versions centralisées des fichiers
<tt>/etc/passwd</tt>, <tt>/etc/group</tt> et <tt>/etc/hosts</tt>, lesquels ne
contiennent alors plus que quelques informations; par exemple, le fichier
<tt>/etc/hosts</tt> contient tout de même des adresses de machines, ne
serait-ce que celle du serveur de pages jaunes....
</p>
<ol>
<li><a name="yp2"> <strong>Combien de personnes de la promotion 1996 ont un
login commençant par «l» ? </strong> </a> <p>
Pour avoir une liste de tous les comptes, on tape </p>
<pre>ypcat passwd</pre>
<p>
On veut ensuite sélectionner les gens dont le répertoire personnel contient
<tt>/96/</tt>, et dont le login commence par <tt>l</tt>, et compter le nombre
de lignes (option <tt>-c</tt> de <tt>grep</tt>). On tape donc : </p>
<pre>ypcat passwd |grep c '^l.*/96/'</pre>
<p>
et on obtient 19.
</p>
</li>
<li><a name="yp3"> <strong>Est-ce que des gens des promotions de la décennie
1980 ont encore un compte sur clipper ? </strong> </a> <p>
On veut savoir si des chemins d'accès de répertoires personnels sont de la
forme </p>
<pre>/users/8[0-9]</pre>
<p>
On tape donc :
</p>
<pre>ypcat passwd | grep '/users/8[0-9]'</pre></li>
<li><a name="yp4"> <strong>Combien y a-t-il de comptes d'élèves, magistériens,
etc (appartenant à une promotion) sur clipper ? </strong> </a> <p>
On veut avoir une liste des gens dont le chemin d'accès du répertoire
personnel commence par <tt>/users/</tt> et un numéro : <tt>/users/9[0-9]</tt>
(puisqu'on vient de voir qu'il n'y a pas de gens des promotions antérieures).
On utilise l'option <tt>-c</tt> (<em>count</em>) pour avoir le nombre de
personnes : </p>
<pre>ypcat passwd | grep -c '/users/9[0-9]/'</pre>
</li>
<li><a name="yp5"> <strong>Classer les comptes de ces utilisateurs par numéro
d'UID</strong> </a> <p>
Le numéro d'ID (<em>user identification</em>) identifie les utilisateurs, les
numéros les plus bas correspondant au système (l'UID de root est 0). C'est le
troisième champ, les champs étant séparés par des deux-points.
</p>
<p>
On va utiliser <tt>sort</tt> pour trier, selon un critère numérique (option
<tt>-n</tt>). Par défaut, avec <tt>sort</tt> les champs sont délimités par des
blancs, il faut donc indiquer qu'ici ce sont des deux-points, avec l'option
<tt>-t</tt>. Enfin, on trie en fonction du troisième champ (<tt>2</tt>,
la numérotation commençant à 0). </p>
<p>
On suppose aussi que l'on veut pouvoir lire le résultat du tri avec
<tt>less</tt>. On tape donc : </p>
<pre>ypcat passwd | grep '/users/9[0-9]' | sort -n -t: +2 | less</pre>
</li>
<li> <strong> <a name="yp6">Affichez le login des personnes ayant
<tt>zsh</tt> comme shell de login.
</a> </strong>
On utilise <code>grep</code> pour ne garder que les lignes des pages
jaunes correspondant aux personnes ayant <code>zsh</code> comme shell de
login et on utilise <code>sed</code> pour n'afficher que le login :
<pre>
<span class="prompt">bireme ~ $</span> ypcat passwd | grep "zsh$" | sed -e "s/:.*//"
</pre>
<div class="ens">
Comme vous pourrez le constater, sur <code>clipper</code>, tout le
monde à <code>/bin/sh</code> comme shell de login. Le shell (en général
<code>zsh</code>) n'est lancé qu'à partir du script
<code>.profile</code>. Il s'agit d'un choix de l'administrateur système.
</div>
</li>
<li> <strong> <a name="yp7">Affichez le nom et le prénom des personnes
ayant un compte sur <tt>clipper</tt>, et dont le nom de famille contient au
moins 2 voyelles côte à côte. Compter ces personnes. </a>
</strong>
<pre>
<span class="prompt">bireme ~ $</span> ypcat passwd | sed -e "s/:\/.*//
s/.*://" | grep " .*[aeiou][aeiou].*"
</pre>
</li>
</ol>
<h2><a name="divers">Divers</a></h2>
<ol>
<li> <strong> <a name="divers1">Comment conserver les informations concernant
le disque dans un fichier ? </a> </strong>
<p>
C'est la commande <tt>df</tt> qui donne des informations sur le disque et sur
les partitions montées. Pour rediriger la sortie d'une commande dans un
fichier, on utilise l'opérateur de redirection <tt>&gt;</tt> suivi du nom du
fichier. Dans notre cas, pour conserver les informations dans un fichier
appelé <tt>disque</tt>, on écrira :
</p>
<pre>
df &gt; disque
</pre></li>
<li>
<strong> <a name="divers3">Comment connaître la dernière date de connexion au
système d'un utilisateur de login <tt>martin</tt> ? </a> </strong>
<p>
C'est la commande <tt>finger</tt> qui donne des renseignements sur les
utilisateurs. Si on tape
</p>
<pre>
finger martin
</pre>
<p class="continue">
ou mieux (plus rapide),
</p>
<pre>
finger -m martin
</pre>
<p class="continue">
les renseignements concernant l'utilisateur s'affichent. On y trouve la
date de son dernier login sur la machine où vous êtes, ainsi que la date de la
dernière fois où il a lu son courrier (mais ce à condition qu'il ait reçu du
courrier depuis...).
</p></li>
<!-- <p>
<li><strong> <a name="divers4"> Créez chez vous un fichier contenant des mots
(sur le modèle d'un carnet de vocabulaire), un par ligne, avec des
répétitions. </a></strong> <br>
Utilisez la commande <tt>uniq</tt> :
<ul>
<li> Pour éliminer les lignes redondantes (dans notre exemple, la ligne
«Pomme» en double exemplaire);
<li> Pour donner le nombre d'occurrences de chaque ligne;
<li> Pour ne faire apparaître que le ou les mots redondants avec le nombre
d'occurrences; ici, ce sera : <br>
<tt> 2 Pomme </tt>
</ul>
!-->
<li><strong> <a name="divers5"> Affichez le type des fichiers <tt>/etc/passwd</tt>,
<tt>/usr/include/stdio.h</tt>, <tt>/bin/ls</tt> et de votre répertoire de
travail. </a></strong>
<p>
C'est la commande <tt>file</tt> qui permet d'afficher le type des fichiers.
Dans notre cas, il suffit d'écrire:
</p>
<pre>
<span class="prompt">galion ~ $</span> file /etc/passwd /usr/include/stdio.h /bin/ls .
/etc/passwd: ASCII text
/usr/include/stdio.h: C program text
/bin/ls: ELF 32-bit MSB executable, SPARC, version 1, stripped
.: directory
<span class="prompt">galion ~ $</span>
</pre>
</li>
</ol>
<div class="metainformation">
Auteur : Anonyme, Joël Riou. <date value="from git" />
</div>
</body>
</html>