<chapter id="chap_04"><title>Expressions régulières</title>

<abstract>
<para>Dans ce chapitre nous abordons&nbsp;:</para>
<para><itemizedlist>
<listitem><para>L'utilisation des expressions régulières</para></listitem>
<listitem><para>Les métacaractères des expressions régulières</para></listitem>
<listitem><para>Trouver des patrons dans les fichiers et autres résultats</para></listitem>
<listitem><para>Les intervalles de caractères et les classes en Bash</para></listitem>
</itemizedlist></para>
</abstract>
<sect1 id="sect_04_01"><title>Expressions régulières</title>
<sect2 id="sect_04_01_01"><title>Qu'est-ce qu'une expression régulière&nbsp;?</title>
<para>Une <emphasis>expression régulière</emphasis> est un patron qui recouvre un ensemble de chaînes de caractères.  Les expressions régulières sont construites de façon analogique aux expressions arithmétiques par l'emploi de diverses opérateurs qui combinent d'autres expressions réduites.</para>
<para>Les briques de base sont les expressions régulières qui correspondent à un seul caractère.  La plupart des caractères, incluant toutes les lettres et les chiffres, sont des expressions régulières qui correspondent exactement à elle même.  Tout métacaractère portant un sens spécial peut être protégé en le précédant d'un slash inversé.</para>



</sect2>
<sect2 id="sect_04_01_02"><title>Les métacaractères des expressions régulières</title>
<para>Une expression régulière peut être suivi par un ou plusieurs opérateurs de répétition (métacaractère)&nbsp;:</para>
<table id="table_04_01" frame="all"><title>Opérateurs d'expression régulière</title>
<tgroup cols="2" align="left" colsep="1" rowsep="1"><thead>
<row><entry>Opérateur</entry><entry>Effet</entry></row>
</thead>
<tbody>
<row><entry>.</entry><entry>Correspond à tout caractère</entry></row>
<row><entry>?</entry><entry>L'élément précédent est optionnel et sera présent au plus une fois.</entry></row>
<row><entry>*</entry><entry>L'élément précédent sera présent zéro fois ou plus.</entry></row>
<row><entry>+</entry><entry>L'élément précédent sera présent une fois ou plus.</entry></row>
<row><entry>{N}</entry><entry>L'élément précédent sera présent exactement N fois.</entry></row>
<row><entry>{N,}</entry><entry>L'élément précédent sera présent N ou plus de fois.</entry></row>
<row><entry>{N,M}</entry><entry>L'élément précédent sera présent au moins N fois, mais pas plus de M fois.</entry></row>
<row><entry>-</entry><entry>représente l'intervalle si il n'est pas premier ou dernier dans une liste ou le dernier point d'un intervalle dans une liste.</entry></row>
<row><entry>^</entry><entry>Correspond à une chaîne vide au début de la ligne&nbsp;; représente aussi les caractères ne se trouvant pas dans l'intervalle d'une liste.</entry></row>
<row><entry>$</entry><entry>Correspond à la chaîne vide à la fin d'une ligne.</entry></row>
<row><entry>\b</entry><entry>Correspond à la chaîne vide au début ou à la fin d'un mot.</entry></row>
<row><entry>\B</entry><entry>Correspond à la chaîne vide à l'intérieur d'un mot.</entry></row>
<row><entry>\&lt;</entry><entry>Correspond à la chaîne vide au début d'un mot.</entry></row>
<row><entry>\&gt;</entry><entry>Correspond à la chaîne vide à la fin d'un mot.</entry></row>
</tbody>
</tgroup>
</table>
<para>2 expressions régulières peuvent être concaténées&nbsp;; l'expression régulière résultant correspond à toute chaîne formée par 2 sous-chaînes concaténées qui respectivement correspondent aux sous-expressions.</para>
<para>2 expressions régulières peuvent être jointes par l'opérateur  <quote>|</quote>&nbsp;; l'expression régulière résultant correspond à toute chaîne qui correspond à l'une ou l'autre des sous-expressions.</para>
<para>La répétition a la préséance sur la concaténation, laquelle a la préséance sur la jointure.  Toute une expression peut être mise entre parenthèses pour éviter ces règles de préséance.</para>


</sect2>
<sect2 id="sect_04_01_03"><title>Expressions régulières basiques versus celles étendues</title>
<para>Dans les expressions régulières basiques les métacaractères  <quote>?</quote>, <quote>+</quote>, <quote>{</quote>, <quote>|</quote>, <quote>(</quote>, et <quote>)</quote> perdent leur sens spécial&nbsp;; à la place employez la version avec slash inversé <quote>\?</quote>, <quote>\+</quote>, <quote>\{</quote>, <quote>\|</quote>, <quote>\(</quote>, et <quote>\)</quote>.</para>
<para>Vérifiez dans la documentation de votre système si les commandes admettent les expressions étendues dans les expressions régulières.</para>
</sect2>

</sect1>

<sect1 id="sect_04_02"><title>Exemples en utilisant grep</title>
<sect2 id="sect_04_02_01"><title>Qu'est-ce que grep&nbsp;?</title>
<para><command>grep</command> cherche dans les fichiers en entrée les lignes qui contiennent une correspondance dans une liste donnée de patrons.  Quand il trouve une correspondance dans une ligne, il copie la ligne sur la sortie standard (par défaut), ou sur tout autre type de sortie que vous avez requise par les options.</para>
<para>Bien que <command>grep</command> s'attende à établir une correspondance sur du texte, il n'a pas de limite sur la longueur de la ligne lue autre que celle de la mémoire disponible, et il trouve la correspondance sur n'importe quel caractère dans la ligne.  Si le dernier octet d'un fichier en entrée n'est pas un <emphasis>saut de ligne</emphasis>, <command>grep</command> discrètement en ajoute un.  Parce que le saut de ligne est aussi un séparateur dans la liste des patrons, il n'y a pas moyen de repérer les caractères saut de ligne dans le texte.</para>
<para>Quelques exemples&nbsp;:</para>
<screen>
<prompt>cathy ~&gt;</prompt> <command>grep <parameter>root</parameter> <filename>/etc/passwd</filename></command>
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

<prompt>cathy ~&gt;</prompt> <command>grep <option>-n</option> <parameter>root</parameter> <filename>/etc/passwd</filename></command>
1:root:x:0:0:root:/root:/bin/bash
12:operator:x:11:0:operator:/root:/sbin/nologin

<prompt>cathy ~&gt;</prompt> <command>grep <option>-v</option> <parameter>bash</parameter> <filename>/etc/passwd </filename></command>| <command>grep <option>-v</option> <parameter>nologin</parameter></command>
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
news:x:9:13:news:/var/spool/news:
mailnull:x:47:47::/var/spool/mqueue:/dev/null
xfs:x:43:43:X Font Server:/etc/X11/fs:/bin/false
rpc:x:32:32:Portmapper RPC user:/:/bin/false
nscd:x:28:28:NSCD Daemon:/:/bin/false
named:x:25:25:Named:/var/named:/bin/false
squid:x:23:23::/var/spool/squid:/dev/null
ldap:x:55:55:LDAP User:/var/lib/ldap:/bin/false
apache:x:48:48:Apache:/var/www:/bin/false

<prompt>cathy ~&gt;</prompt> <command>grep <option>-c</option> <parameter>false</parameter> <filename>/etc/passwd</filename></command>
7

<prompt>cathy ~&gt;</prompt> <command>grep <option>-i</option> <parameter>ps</parameter> <filename>~/.bash*</filename></command> | <command>grep <option>-v</option> <parameter>history</parameter></command>
/home/cathy/.bashrc:PS1="\[\033[1;44m\]$USER is in \w\[\033[0m\] "

</screen>
<para>Avec la première commande, l'utilisateur <emphasis>cathy</emphasis> affiche les lignes de  <filename>/etc/passwd</filename> contenant la chaîne <emphasis>root</emphasis>.</para>
<para>Puis elle affiche le numéro des lignes contenant cette chaîne.</para>
<para>Avec la troisième commande elle vérifie quels utilisateurs n'utilisent pas <command>bash</command>, mais les comptes avec <command>nologin</command> ne sont pas affichés.</para>
<para>Puis elle compte le nombre de comptes qui ont <filename>/bin/false</filename> comme Shell.</para>
<para>La dernière commande affiche les lignes de tous les fichiers dans son répertoire racine qui commencent par <filename>~/.bash</filename>, excluant les correspondances avec  <emphasis>history</emphasis>, afin d'exclure des correspondances de <filename>~/.bash_history</filename> qui pourrait contenir la même chaîne en majuscule et minuscule.  Remarquez que la recherche se fait sur la <emphasis>chaîne</emphasis> <quote>ps</quote>, et pas sur la <emphasis>commande</emphasis> <command>ps</command>.</para>
<para>Maintenant voyons ce que nous pouvons faire d'autre avec grep et des expressions régulières.</para>
</sect2>
<sect2 id="sect_04_02_02"><title>Grep et les expressions régulières</title>
<note><title>Si vous n'êtes pas sous Linux</title><para>Nous utilisons GNU <command>grep</command> dans ces exemples, parce qu'il admet les expressions régulières étendues.  GNU <command>grep</command> se trouve par défaut sur les systèmes Linux. Si vous travaillez sur un système propriétaire, vérifiez avec l'option <option>-V</option> la version utilisée.  GNU <command>grep</command> peut être téléchargé depuis <ulink url="http://gnu.org/directory/" />.</para></note>
<sect3 id="sect_04_02_02_01"><title>Ancres de lignes et de mots</title>
<para>A partir de l'exemple précédent, nous voulons maintenant n'afficher que les lignes commençant par la chaîne <quote>root</quote>&nbsp;:</para>
<screen>
<prompt>cathy ~&gt;</prompt> <command>grep <parameter>^root</parameter> <filename>/etc/passwd</filename></command>
root:x:0:0:root:/root:/bin/bash
</screen>
<para>Si nous voulons voir quels comptes n'ont aucun Shell de défini, nous cherchons les lignes finissant par <quote>:</quote>&nbsp;:</para>
<screen>
<prompt>cathy ~&gt;</prompt> <command>grep <parameter>:$</parameter> <filename>/etc/passwd</filename></command>
news:x:9:13:news:/var/spool/news:
</screen>
<para>Pour vérifier que <varname>PATH</varname> est exporté dans <filename>~/.bashrc</filename>, d'abord sélectionner les lignes <quote>export</quote> puis chercher les lignes commençant avec la chaîne  <quote>PATH</quote>, afin de ne pas afficher <varname>MANPATH</varname> et autres chaînes possibles&nbsp;:</para>
<screen>
<prompt>cathy ~&gt;</prompt> <command>grep <parameter>export</parameter> <filename>~/.bashrc</filename></command> | <command>grep <parameter>'\&lt;PATH'</parameter></command>
  export PATH="/bin:/usr/lib/mh:/lib:/usr/bin:/usr/local/bin:/usr/ucb:/usr/dbin:$PATH"
</screen>
<para>Pareillement, <emphasis>\&gt;</emphasis> cherche à la fin d'un mot.</para>
<para>Si vous voulez trouver une chaîne qui est un mot isolé (entre espaces), le mieux est d'employer <option>-w</option>, comme dans cet exemple où nous affichons des informations de la partition root&nbsp;:</para>
<screen>
<prompt>cathy ~&gt;</prompt> <command>grep <option>-w</option> <parameter>/</parameter> <filename>/etc/fstab</filename></command>
LABEL=/                 /                       ext3    defaults        1 1
</screen>
<para>Si cette option est absente, toutes les lignes de la table du système de fichiers seront affichées.</para>
</sect3>

<sect3 id="sect_04_02_02_02"><title>Classes de caractères</title>
<para>Une <emphasis>expression entre crochet</emphasis> est une liste de caractère entre <quote>[</quote> et <quote>]</quote>.  Elle fera correspondre tout caractère présent dans cette liste&nbsp;; si le premier caractère de la liste est le caret, <quote>^</quote>, alors elle fera correspondre tout caractère NON présent dans la liste.  Par exemple, l'expression régulière <quote>[0123456789]</quote> repère n'importe quelle  chiffre.</para>
<para>Dans une expression entre crochet, une <emphasis>expression intervalle</emphasis> consiste en 2  caractères séparés par un tiret.  Elle repère tout caractère qui se retrouve dans l'intervalle des 2 caractères inclus, employant pour ce faire la séquence du jeux de caractères définit localement.  Par exemple, dans le jeux particulier par défaut en C, <quote>[a-d]</quote> est équivalent à <quote>[abcd]</quote>.  la plupart des jeux sont ordonnés selon l'ordre du dictionnaire, et dans ces jeux  <quote>[a-d]</quote> est classiquement pas équivalent à <quote>[abcd]</quote>&nbsp;; cela peut être équivalent à  <quote>[aBbCcDd]</quote>, par exemple.  Pour obtenir l'interprétation classique des expressions entre crochets, vous pouvez employer le jeux de C en déclarant la variable d'environnement <varname>LC_ALL</varname> à la valeur  <quote>C</quote>.</para>
<para>Pour finir, des jeux de caractères nommés sont prédéfinis dans des expressions entre crochets.  Voir les pages man ou info de <command>grep</command> pour plus d'informations au sujet de ces  expressions prédéfinies.</para>
<screen>
<prompt>cathy ~&gt;</prompt> <command>grep <parameter>[yf]</parameter> <filename>/etc/group</filename></command>
sys:x:3:root,bin,adm
tty:x:5:
mail:x:12:mail,postfix
ftp:x:50:
nobody:x:99:
floppy:x:19:
xfs:x:43:
nfsnobody:x:65534:
postfix:x:89:
</screen>
<para>Dans cet exemple, toutes les lignes contenant soit <quote>y</quote> ou <quote>f</quote> sont affichées.</para>
</sect3>

<sect3 id="sect_04_02_02_04"><title>Jokers</title>
<para>Employez le <quote>.</quote> pour cibler la correspondance avec un seul caractère.  Si vous voulez obtenir une liste de tous les mots d'un dictionnaire anglais de 5 caractères et commençant par <quote>c</quote> et finissant par <quote>h</quote> (pratique pour les mots-croisés )&nbsp;:</para>
<screen>
<prompt>cathy ~&gt;</prompt> <command>grep <parameter>'\&lt;c...h\&gt;'</parameter> <filename>/usr/share/dict/words</filename></command>
catch
clash
cloth
coach
couch
cough
crash
crush
</screen>
<para>Si vous voulez afficher les lignes contenant le caractère littéral point, employez l'option <option>-F</option> de <command>grep</command>.</para>
<para>Pour cibler plusieurs caractères, employez l'astérisque.  Cet exemple cible tous les mots commençant par <quote>c</quote> et finissant par  <quote>h</quote> dans le dictionnaire du système&nbsp;:</para>
<screen>
<prompt>cathy ~&gt;</prompt> <command>grep <parameter>'\&lt;c.*h\&gt;'</parameter> <filename>/usr/share/dict/words</filename></command>
caliph
cash
catch
cheesecloth
cheetah
--output omitted--
</screen>
<para>Si vous voulez cibler le caractère astérisque littéral dans un fichier ou un résultat, employez les apostrophes.  Cathy dans l'exemple ci-dessous essaye d'abord de trouver le caractère astérisque dans <filename>/etc/profile</filename> sans les apostrophes, ce qui ne ramène aucune ligne.  Avec les apostrophes, un résultat est généré&nbsp;:</para>
<screen>
<prompt>cathy ~&gt;</prompt> <command>grep <parameter>*</parameter> <filename>/etc/profile</filename></command>

<prompt>cathy ~&gt;</prompt> <command>grep <parameter>'*'</parameter> <filename>/etc/profile</filename></command>
for i in /etc/profile.d/*.sh ; do
</screen>
</sect3>
</sect2>

</sect1>
<sect1 id="sect_04_03"><title>La correspondance de patron dans les fonctionnalités Bash</title>
<sect2 id="sect_04_03_01"><title>Intervalle de caractère</title>
<para>En plus de <command>grep</command> et des expressions régulières, il y a un bon paquet de correspondances que vous pouvez faire directement dans le Shell, sans avoir besoin d'un programme externe.</para>
<para>Comme vous savez déjà, l'astérisque (*) et le point d'interrogation (?) ciblent toute chaîne ou tout caractère, respectivement.  Protégez ces caractères spéciaux pour les cibler littéralement&nbsp;:</para>
<screen>
<prompt>cathy ~&gt;</prompt> <command>touch <filename>"*"</filename></command>

<prompt>cathy ~&gt;</prompt> <command>ls <filename>"*"</filename></command>
*
</screen>  
<para>Mais vous pouvez aussi employer les crochets pour cibler tout caractère ou intervalle de caractères compris, si la paire de caractères est séparée par un tiret.  Un exemple&nbsp;:</para>
<screen>
<prompt>cathy ~&gt;</prompt> <command>ls <option>-ld</option> <filename>[a-cx-z]*</filename></command>
drwxr-xr-x    2 cathy    cathy          4096 Jul 20  2002 app-defaults/
drwxrwxr-x    4 cathy    cathy          4096 May 25  2002 arabic/
drwxrwxr-x    2 cathy    cathy          4096 Mar  4 18:30 bin/
drwxr-xr-x    7 cathy    cathy          4096 Sep  2  2001 crossover/
drwxrwxr-x    3 cathy    cathy          4096 Mar 22  2002 xml/
</screen>
<para>Ceci liste tous les fichiers dans le répertoire racine de <emphasis>cathy</emphasis>, commençant par <quote>a</quote>, <quote>b</quote>, <quote>c</quote>, <quote>x</quote>, <quote>y</quote> ou <quote>z</quote>.</para>
<para>Si le premier caractère entre crochet est <quote>!</quote> ou <quote>^</quote>, tout caractère non inclus sera ciblé.  Pour cibler le (<quote>-</quote>), l'inclure en premier ou dernier caractère de l'ensemble.  L'ordre dépend du paramétrage local et de la valeur de la variable  <varname>LC_COLLATE</varname>, si elle est définie.  Rappelez-vous que d'autres paramétrages pourraient interpréter <quote>[a-cx-z]</quote> comme <quote>[aBbCcXxYyZz]</quote> si le tri est fait selon l'ordre du dictionnaire.  Si vous voulez être sûr d'avoir l'interprétation classique des intervalles, forcer ce paramétrage en définissant <varname>LC_COLLATE</varname> ou <varname>LC_ALL</varname> à <quote>C</quote>.</para>
</sect2>
<sect2 id="sect_04_03_02"><title>Classes de caractères</title>
<para>Les jeux de caractères peuvent être spécifiés entre crochets, avec la syntaxe  <command>[:CLASS:]</command>, où CLASS est une classe définie dans le standard POSIX et a une des valeurs</para>
<para><quote>alnum</quote>, <quote>alpha</quote>, <quote>ascii</quote>, <quote>blank</quote>, <quote>cntrl</quote>, <quote>digit</quote>, <quote>graph</quote>, <quote>lower</quote>, <quote>print</quote>, <quote>punct</quote>, <quote>space</quote>, <quote>upper</quote>, <quote>word</quote> or <quote>xdigit</quote>.</para>
<para>Quelques exemples&nbsp;:</para>
<screen>
<prompt>cathy ~&gt;</prompt> <command>ls <option>-ld</option> <filename>[[:digit:]]*</filename></command>
drwxrwxr-x    2 cathy   cathy           4096 Apr 20 13:45 2/

<prompt>cathy ~&gt;</prompt> <command>ls <option>-ld</option> <filename>[[:upper:]]*</filename></command>
drwxrwxr--    3 cathy   cathy           4096 Sep 30  2001 Nautilus/
drwxrwxr-x    4 cathy   cathy           4096 Jul 11  2002 OpenOffice.org1.0/
-rw-rw-r--    1 cathy   cathy         997376 Apr 18 15:39 Schedule.sdc
</screen>
<para>Quand l'option Shell <option>extglob</option> est activée (par l'intégrée <command>shopt</command>), plusieurs autres opérateurs de correspondance sont reconnus.  Plus dans les pages Bash info, section <menuchoice><guimenu>Basic shell features</guimenu><guisubmenu>Shell Expansions</guisubmenu><guisubmenu>Filename Expansion</guisubmenu><guimenuitem>Pattern Matching</guimenuitem></menuchoice>.</para>
</sect2>


</sect1>
<sect1 id="sect_04_04"><title>Résumé</title>
<para>Les expressions régulières sont puissantes pour extraire des lignes particulières d'un fichier ou d'un résultat.  Beaucoup de commandes UNIX emploient des expressions régulières&nbsp;: <command>vim</command>, <command>perl</command>, la base de données <application>PostgreSQL</application> etc.  Elles peuvent être interprétées par tout langage et application par l'emploi de bibliothèques externes, et on les retrouve même sur des systèmes non-UNIX. Par exemple, les expressions régulières sont employées dans le tableur Excel qui est fourni avec la suite Office de Microsoft.  Dans ce chapitre nous avons eu un avant-goût de la commande <command>grep</command> qui est indispensable à tout environnement UNIX.</para> 
<note><para>La commande <command>grep</command> peut faire bien plus que les quelques opérations vues ici&nbsp;; nous l'avons juste utilisée à titre d'illustration pour les expressions régulières.  La version GNU de <command>grep</command> est fourni avec un bon lot de documentations, qu'il vous est recommandé de lire&nbsp;!</para></note>
<para>Bash a des fonctionnalités intégrées pour cibler des patrons et peut reconnaître des classes de caractères et des intervalles.</para>

</sect1>
<sect1 id="sect_04_05"><title>Exercices</title>
<para>Ces exercices vous aideront à maîtriser les expressions régulières.</para>

<orderedlist>
<listitem><para>Afficher une liste de tous les utilisateurs de votre système qui se connectent avec le Shell Bash par défaut.</para></listitem>
<listitem><para>Depuis le répertoire <filename>/etc/group</filename>, afficher toutes les lignes commençant avec la chaîne <quote>daemon</quote>.</para></listitem>
<listitem><para>Imprimer toutes les lignes de ce même fichier qui ne contiennent pas la chaîne.</para></listitem>
<listitem><para>Afficher les informations du système local à partir du fichier <filename>/etc/hosts</filename>, afficher le numéro des lignes qui correspondent à la chaîne recherchée et compter le nombre d'occurrences de cette chaîne.</para></listitem>
<listitem><para>Afficher une liste des sous-répertoires de <filename>/usr/share/doc</filename> contenant des informations au sujet des Shell.</para></listitem>
<listitem><para>Combien de fichiers <filename>README</filename> ces sous-répertoires contiennent-ils&nbsp;?  Ne pas prendre en compte les fichiers de la forme <quote>README.a_string</quote>.</para></listitem>
<listitem><para>Faire une liste des fichiers du répertoire racine qui ont changés moins de 10 heures auparavant, en employant <command>grep</command>, mais sans prendre les répertoires.</para></listitem>
<listitem><para>Mettre ces commandes dans un script Shell qui générera un résultat lisible.</para></listitem>
<listitem><para>Pouvez-vous trouver une alternative à <command>wc <option>-l</option></command>, avec <command>grep</command>&nbsp;?</para></listitem>
<listitem><para>Avec la table des fichiers du système (<filename>/etc/fstab</filename> par exemple), lister les disques locaux.</para></listitem>
<listitem><para>Faire un script qui contrôle si un utilisateur apparaît dans <filename>/etc/passwd</filename>.  Pour cette fois-ci, vous pouvez spécifier le nom dans le script, vous n'êtes pas tenu de travailler avec des paramètres et des conditions à ce stade.</para></listitem>
<listitem><para>Afficher les fichiers de configuration de <filename>/etc</filename> qui contiennent des chiffres dans leur nom.</para>
</listitem>
</orderedlist>

</sect1>
</chapter>
Heberge par Ikoula