<!-- Maintainer: Fabrice Bellet <Fabrice.Bellet@creatis.insa-lyon.fr> -->
<!DOCTYPE ARTICLE PUBLIC "-//Davenport//DTD DocBook V3.0//EN" [
]>
<article id="index">
  <artheader>
    <authorgroup>
        <author>
          <firstname>Fabrice</firstname>
          <surname>Bellet</surname>
          <affiliation>
            <orgname>Laboratoire CREATIS</orgname>
            <address>
            <email>Fabrice.Bellet@creatis.insa-lyon.fr</email>
            </address>
          </affiliation>
        </author>
      </authorgroup>
    <copyright>
      <year>1999</year>
      <holder>Laboratoire CREATIS - Fabrice Bellet</holder>
    </copyright>
    <abstract>
     <para>
      Le développement d'applications dans un environnement Unix nécessite
      des connaissances de l'environnement de développement et des
      différents outils à disposition du développeur pour simplifier son
      cycle de développement. Cette connaissance passe par la maîtrise de
      l'utilisation du compilateur (dans le cas qui nous interesse, il
      s'agit du compilateur C), ses options, les principes de création de
      librairies de programmes. 
      L'automatisation du développement nécessite l'utilisation des
      Makefiles pour automatiser les étapes de génération de code.
      Enfin, une connaissance des systèmes Unix et de ses interpréteurs
      de commandes s'avère indispensable.
     </para>
     <para>
      Cette documentation ne se veut pas exhaustive mais fournit plutot
      quelques points de départ et quelques outils pour la personne 
      curieuse qui souhaiterais approfondir certains points par la suite.
     </para>
     <para>
      Les points abordés ici seront davantage ce que je considère comme
      des méthodes au mieux, ou des astuces au pire qui permettent
      d'exploiter un environnement Unix. Il s'agit du fruit
      de mon expérience, avec tout ce que cela peut avoir de partial
      et de subjectif.
     </para>
    </abstract>
    <title>Développement sous Unix</title>
  </artheader>

<sect1 id="intro">
 <title>Introduction</title>
  <para>
   Ce document a pour objectif de vous familiariser avec le cycle
   de développement d'applications en environnement Unix. Il est composé
   de trois parties:
  </para>
  <itemizedlist mark="bullet">
   <listitem>
    <para>
     La première partie concerne la mise en oeuvre des
     outils de compilation.
    </para>
   </listitem>
   <listitem>
    <para>
     La deuxième partie abordera le fonctionnement des
     <firstterm>Makefiles</firstterm>.
    </para>
   </listitem>
   <listitem>
    <para>
     La troisième partie présentera Unix dans ses
     commandes de base, et ses shells de commande.
    </para>
   </listitem>
  </itemizedlist>
</sect1>

<sect1 id="compilation">
 <title>Compilation</title>
  <para>
   La <firstterm>compilation</firstterm>
   d'un programme est un ensemble de traitements
   successifs sur un fichier source dont l'étape ultime est de générer
   un fichier exécutable par la machine. Les traitements successivement
   effectués sur le fichier source sont les suivants:

   <variablelist>
    <varlistentry>
     <term>Pré-processing</term>
     <listitem>
      <para>
       Le <firstterm>pré-processing</firstterm> consiste
       à substituer toutes les macros présentes dans le code par leur
       valeur: <symbol>#ifdef</symbol>, <symbol>#define</symbol>,
       <symbol>#include</symbol>, etc.
       Voici un petit exemple de code sur lequel le préprocesseur
       va fonctionner:

       <programlisting>
/*
 * Exemple d'utilisation de macros
 */

#include &lt;stdio.h&gt;
#define BORNE_MAX      100
#define MAX(a,b)       ((a)>(b)?(a):(b))

int table [BORNE_MAX];

int main() {
  int i;

  for (i=0; i &lt BORNE_MAX; i++)
          table [i]=i;
#ifdef DEBUG
  printf ("DEBUG : table[10]=%d\n", table[10]);
#endif
  return 0;
}
       </programlisting>

       On peut affecter la valeur de macros a la compilation avec l'option
       <userinput>-DMON_SYMBOL</userinput> ou
       <userinput>-DMON_SYMBOL=valeur</userinput>.
       On utilisera généralement des noms de symboles en majuscules pour
       les différencier des simples variables.
      </para>

      <para>
       Les personnes de nature curieuse pourront examiner le code obtenu
       à l'issue du pré-processing avec l'option <userinput>-E</userinput>
       du compilateur:
       <screen>
        <prompt>%</prompt> <userinput>gcc -E fichier.c > fichier.E</userinput>
       </screen>
      </para>
     </listitem>
    </varlistentry>
    <varlistentry>
     <term>Compilation</term>
     <listitem>
      <para>
       L'étape de compilation par elle-même. Elle travaille
       sur le fichier résultat du préprocessing, et produit un fichier 
       texte contenu du code en langage d'assemblage spécifique à la machine
       sur laquelle vous compilez.
      </para>
     </listitem>
    </varlistentry>
    <varlistentry>
     <term>Assemblage</term>
     <listitem>
      <para>
       L'étape d'<firstterm>assemblage</firstterm> prend le fichier
       précédent, et génère du code machine. Le fichier produit est appelé
       <firstterm>fichier objet</firstterm>, et se reconnait en général
       par son extension <filename>.o</filename>.
      </para>
     </listitem>
    </varlistentry>
    <varlistentry>
     <term>Edition de liens</term>
     <listitem>
      <para>
       L'<firstterm>édition de liens</firstterm> prend un ensemble
       de fichiers objets pour produire un programme exécutable.
      </para>
     </listitem>
    </varlistentry>
   </variablelist>

   Heureusement, dans la grande majorité des cas, on n'a pas à se
   préoccuper de tous ces fichiers intermédiaires, car ils sont gérés
   de façon transparente par le compilateur, à l'exception des fichiers
   objets. Ces fichiers et ces formats temporaires ne sont
   pas visibles à l'utilisateur sauf cas spécial. Mais il n'est
   pas inutile de savoir qu'ils existent.
  </para>

  <para>
   Examinons un petit programme d'exemple:
  </para>
  <programlisting>
/*
 * main.c
 */

#include &lt;stdio.h&gt;
#include &lt;file1.h&gt;

int main()
{
   printf ("%d\n",file1_proc (10));
   return 0;
}
  </programlisting>
  <programlisting>
/*
 * file1.h
 */

extern int file1_proc (int i);
  </programlisting>
  <programlisting>
/*
 * file1.c
 */

int file1_proc (int i)
{
   return i+1;
}
  </programlisting>
  <programlisting>
% gcc -c file1.c
% gcc -I. -c main.c
% gcc -o exemple main.o file1.o
% exemple
11
% gcc -v -c file1.c
Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/specs
gcc version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)
 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/cpp -lang-c -v -undef -D__GNUC__=2 -D__GNUC_MINOR__=91 -D__ELF__ -Dunix -Di386 -D__i386__ -Dlinux -D__ELF__ -D__unix__ -D__i386__ -D__i386__ -D__linux__ -D__unix -D__i386 -D__linux -Asystem(posix) -Asystem(unix) -Acpu(i386) -Amachine(i386) -Di386 -D__i386 -D__i386__ -D__tune_i386__ file1.c /tmp/cc2jRiec.i
GNU CPP version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release) (i386 Linux/ELF)
#include "..." search starts here:
#include &lt;...&gt search starts here:
 /usr/local/include
 /usr/i386-redhat-linux/include
 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include
 /usr/include
End of search list.
 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/cc1 /tmp/cc2jRiec.i -quiet -dumpbase file1.c -version -o /tmp/cc2F6SSd.s
GNU C version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release) (i386-redhat-linux) compiled by GNU C version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release).
 as -V -Qy -o file1.o /tmp/cc2F6SSd.s
GNU assembler version 2.9.1 (i386-redhat-linux), using BFD version 2.9.1.0.24
% gcc -v -o exemple main.o file1.o
Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/specs
gcc version egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)
 /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/collect2 -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o exemple /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/crtbegin.o -L/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66 -L/usr/i386-redhat-linux/lib main.o file1.o -lgcc -lc -lgcc /usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/crtend.o /usr/lib/crtn.o
  </programlisting>

  <para>
   Le compilateur C est appelé par la commande <command>cc</command> ou
   <command>gcc</command>. Son fonctionnement peut être modifié par
   une impressionnante floppée d'options sur la ligne de commande.
   Pour avoir une aide exhaustive sur un compilateur donné, il est
   recommandé de se reporter au manuel en ligne (accessible par
   la commande <userinput>man gcc</userinput>). Les options essentielles
   sont les suivantes:
  </para>

  <variablelist>
   <varlistentry>
    <term>
     <userinput>-c</userinput>
    </term>
    <listitem>
     <para>
      Cette option indique au compilateur de
      s'arrêter après la génération du fichier objet (de ne pas faire
      l'édition de liens). Elle permet la compilation séparée.
     </para>
    </listitem>
   </varlistentry>
   <varlistentry>
    <term>
     <userinput>-Idirectory</userinput>
    </term>
    <listitem>
     <para>
      Cette option indique le chemin
      de recherche pour trouver les fichiers
      inclus dans les macros <symbol>#include &lt;fichier.h&gt</symbol>.
      Un certain nombre de répertoires sont prédéfinis et n'ont pas
      besoin d'être spécifiés. L'ordre des répertoires de recherche
      est important. Une variante existe: les macros
      <symbol>#include "fichier.h"</symbol> se limitent au répertoire
      courant.
     </para>
    </listitem>
   </varlistentry>
   <varlistentry>
    <term>
     <userinput>-o executable</userinput>
    </term>
    <listitem>
     <para>
      Cette option indique le nom
      de l'exécutable qui sera généré à l'édition de liens. Par défaut, 
      si on ne précise rien, l'exécutable est appelé
      <filename>a.out</filename> en référence au format dans lequel
      est écrit ce fichier.
      <note>
       <para>
        Le format <firstterm>a.out</firstterm> a disparu de la circulation
        au profit du format <firstterm>ELF</firstterm>, plus portable
        et plus extensible.
       </para>
      </note>
     </para>
    </listitem>
   </varlistentry>
  </variablelist>

  <para>
   Toute liste de fichiers sera considérée comme
   des fichiers devant être traités dans le processus de
   compilation. Le traitement à leur appliquer dépendra de leur
   suffixe. Par exemple <userinput>gcc file1.c file2.o</userinput>
   va compiler <filename>file1.c</filename>, générer temporairement 
   <filename>file1.o</filename>, lier ensuite <filename>file1.o
   </filename> et <filename>file2.o</filename> dans un 
   exécutable nommé <filename>a.out</filename>.
  </para>

  <para>
   D'autres options de compilation peuvent s'avérer utiles:
  </para>

  <variablelist>
   <varlistentry>
    <term>
     <userinput>-g</userinput>
    </term>
    <listitem>
     <para>
      Cette option rajoute des informations
      supplémentaires dans chacun des fichiers produits pour faciliter la
      tache de debuggage. Cela permet en utilisant un debugger (par exemple
      <command>gdb</command>) d'avoir accès aux noms de variables
      utilisées dans le source, de savoir pour chaque instruction machine
      exécutée à quel fichier source, et à quelle ligne de code cela
      correspond. La taille des fichiers ainsi générés augmente
      sensiblement, on évitera donc de laisser trainer ces options
      en cycle de production ou lorsque l'on fournit un produit fini.
     </para>
    </listitem>
   </varlistentry>
   <varlistentry>
    <term>
     <userinput>-On</userinput>
    </term>
    <listitem>
     <para>
      Cette option indique si
      l'on souhaite produire un code optimisé. La valeur <symbol>n</symbol>
      indique le niveau d'optimisation souhaité, de zéro (pas
      d'optimisation, jusqu'à 6 ou plus, ceci dépend du compilateur).
      Chaque niveau correspond à la sélection d'un groupe d'options 
      d'optimisation indépendantes. On peut choisir une optimisation
      specifique en précisant l'option
      <userinput>-foptimisation_specifique</userinput> ou
      au contraire pour interdire une optimisation spécifique par
      <userinput>-fno_optimisation_specifique</userinput>.
      Chaque option d'optimisation est décrite abondamment dans les pages
      du manuel en ligne du compilateur. Il est possible de mixer les
      options d'optimisation et de débugage (<userinput>-g -O2</userinput> 
      par exemple), mais cela n'est pas recommendé car cela rend le code
      délicat à débugger. L'optimiseur se réserve le droit de
      supprimer des variables inutiles, de précalculer des expressions,
      de changer l'ordre d'exécution des instructions. Toutes ces
      modifications perturbent l'utilisateur qui s'attend à avoir une
      exécution séquentielle des lignes de son code source sous le
      debugger. 
     </para>
    </listitem>
   </varlistentry>
   <varlistentry>
    <term>
     <userinput>-DSYMBOL[=valeur]</userinput>
    </term>
    <listitem>
     <para>
      Cette option permet de définir des constantes ayant une valeur pour le
      préprocesseur. L'exemple précédent montre que le compilateur
      ajoute lui-même un grand nombre de ces constantes, permettant
      de caractériser le système sur lequel la compilation s'exécute
      (compilation conditionnelle).
     </para>
    </listitem>
   </varlistentry>
   <varlistentry>
    <term>
     <userinput>-Ldirectory -llibrary</userinput>
    </term>
    <listitem>
     <para>
      Ces options sont utilisées pour l'édition de liens. Elles permettent
      d'inclure des fichiers objets supplémentaires à ceux que l'on a
      compilé. Ces fichiers objets sont regroupés dans un fichier unique,
      nommé <firstterm>librairie</firstterm>. Une librairie est donc un
      ensemble de fichiers <filename>.o</filename>.
      Elle peut être liée statiquement ou dynamiquement à l'exécutable.
     </para>
 
     <para>
      Dans le premier cas, le contenu de la librairie est ajouté
      dans le fichier exécutable une fois pour toute. Cela rend
      l'exécutable <emphasis>self-contained</emphasis>. Tout le code dont
      il a besoin pour fonctionner est inclus dans le fichier executable.
      Le désavantage de cette méthode est que la mise à jour de la
      librairie ne pourra se faire que par une recompilation de notre
      fichier source qui est à la charge de l'utilisateur.
     </para>
 
     <para>
      Dans le second cas, l'exécutable ne contient qu'un lien
      vers cette librairie. Il est donc de plus petite taille. Il n'y a
      cependant pas de miracle, le code de la librairie devra pourtant
      être chargé au moment de l'exécution (on parle de 
      <firstterm>runtime</firstterm>). Pour cela, une variable
      d'environnement <envar>LD_LIBRARY_PATH</envar> contient une liste
      de répertoire dans laquelle le loader dynamique recherchera la
      librairie souhaitée au lancement de l'exécutable. L'intérêt est
      d'avoir des exécutables qui ne dupliquent pas le code d'une même
      librairie, et de pouvoir mettre à jour une librairie sans avoir
      besoin de recompiler les programmes qui l'utilisent (pour peu que
      cette librairie continue d'utiliser la même <firstterm>API</firstterm>,
      <emphasis>Application Programming Interface</emphasis>. Des numéros
      de versions majeurs et mineurs sont utilisés pour
      gérer la compatibilité entre les versions d'une librarie).
     </para>
 
     <para>
      L'option <userinput>-Ldirectory</userinput>
      indique le directory où le linker doit rechercher la librairie
      (au moment de l'édition des liens), et l'option
      <userinput>-llibrary</userinput> precise le nom
      de cette librairie. Selon les architectures, le fichier aura
      comme nom <filename>liblibrary.so</filename> pour une librairie
      dynamique ou <filename>liblibrary.a</filename> pour une librairie
      statique.
     </para>
    </listitem>
   </varlistentry>
  </variablelist>
 
  <para>
   Un petit exemple sur la création de librairies:
  </para>

  <screen>
   <prompt>%</prompt> <userinput>gcc -c fichier1.c fichier2.c fichier3.c</userinput>
   <prompt>%</prompt> <userinput>ar cr libessai.a fichier1.o fichier2.o fichier3.o</userinput>
   <prompt>%</prompt> <userinput>ranlib libessai.a</userinput>
   <prompt>%</prompt> <userinput>gcc -c main.c</userinput>
   <prompt>%</prompt> <userinput>gcc -static -o executable main.o -L. -lessai</userinput>
   <prompt>%</prompt> <userinput>gcc -static -o executable main.o libessai.a</userinput>
  </screen>

  <para>
   Les deux dernières commandes sont équivalentes.
   Cet exemple illustre la création d'une librairie statique et son
   utilisation pour générer <filename>executable</filename>.
  </para>

  <screen>
   <prompt>%</prompt> <userinput>gcc -c -fPIC fichier1.c fichier2.c fichier3.c</userinput>
   <prompt>%</prompt> <userinput>gcc -shared -o libessai.so fichier1.o fichier2.o fichier3.o</userinput>
   <prompt>%</prompt> <userinput>gcc -c main.c</userinput>
   <prompt>%</prompt> <userinput>gcc -o executable main.o -L. -lessai</userinput>
  </screen>

  <para>
   Cet exemple illustre la création d'une librairie dynamique et son
   utilisation pour générer <filename>executable</filename>.
  </para>

  <para>
   Selon les cas, les systèmes Unix utilisés, et le type de compilateur
   utilisé, la syntaxe peut varier. Ces exemples s'appuient sur les
   outils <ulink url="http://www.gnu.org">GNU</ulink>. Pour avoir les
   syntaxes à s'appliquant à un système praticulier, il faut se
   référer aux documentations, en particulier les pages de manuel
   de <command>cc</command>, et <command>ld</command> pour le linker dynamique.
  </para>
 </sect1>

 <sect1 id="makefile">
   <title>Makefiles</title>
   <sect2 id="makefiles-intro">
    <title>Introduction</title>

    <para>
     La partie précédente a montré aux naifs qui en doutaient encore qu'une
     gestion de projet nécessitait de mettre en oeuvre la compilation
     séparée des composantes de l'application. Le compilateur est invoqué
     pour chaque fichier source et une fois supplémentaire pour l'édition
     de liens, dans le cas où l'on ne génère pas de librairies
     intermédiaires.
    </para>

    <para>
     Lorsqu'un seul des fichiers sources est modifié, il n'est pas
     forcément utile de recompiler <emphasis>tous</emphasis> les
     fichiers sources pour regénérer l'application. On gagnera
     du temps à ne recompiler que les fichiers sources sur lesquels
     la modification du code effectuée a un réel impact. C'est à ce
     moment-là que le <firstterm>Makefile</firstterm> entre en jeu.
    </para>

    <para>
     Le principe du Makefile est de construire un graphe de dépendance
     des fichiers constituant l'application, surchargé pour chaque
     fichier par la commande qui permet de le regénérer à partir des
     fichiers dont il dépend.
    </para>
   </sect2>

   <sect2 id="makefiles-simple">
    <title>Un exemple simple</title>

    <para>
     Le <filename>Makefile</filename> le plus simple à comprendre est
     celui où toutes les dépendances apparaissent explicitement. Exemple:
    </para>

    <example>
     <title>Un exemple simple de Makefile</title>
     <programlisting>
all : executable
executable : file1.o file2.o
        gcc -o executable file1.o file2.o
file1.o : file1.c file1.h
        gcc -c file1.c
file2.o : file2.c file1.h file2.h
        gcc -c file2.c
clean :
        rm file1.o file2.o executable core
     </programlisting>
    </example>

    <para>
     On lance l'interpréteur de Makefile par la commande
     <command>make</command>. Par défaut, la première dépendance
     rencontrée tentera d'être résolue : <userinput>all</userinput>.
     Cette <firstterm>target</firstterm> nécessite de remettre à jour
     toutes les conditions situées à droite des <userinput>:</userinput>.
     <command>make</command> passe donc à l'unique target suivante, 
     <filename>executable</filename>
    </para>
    <para>
     D'après la deuxième ligne du <filename>Makefile</filename>, on
     voit que <filename>executable</filename> dépend des deux fichiers
     objets <filename>file1.o</filename> et <filename>file2.o</filename>.
     Récursivement, les fichiers dont dépendent <filename>file1.o</filename>
     et <filename>file2.o</filename> sont recherchés. Si un des fichiers 
     dépendant est plus récent qu'un des fichiers cible au cours de 
     cette recherche, le fichier cible doit être regénéré, et la règle
     de compilation associée est exécutée.
    </para>

    <programlisting>
% make clean
rm file1.o file2.o executable core
rm: cannot remove `core': No such file or directory
% make
gcc -c file1.c
gcc -c file2.c
gcc -o executable file1.o file2.o
% touch file2.h
% make
gcc -c file2.c
gcc -o executable file1.o file2.o
% touch file2.o
% make
gcc -o executable file1.o file2.o
% touch file1.h
% make
gcc -c file1.c
gcc -c file2.c
gcc -o executable file1.o file2.o
    </programlisting>
   </sect2>

   <sect2 id="makefiles-regles">
    <title>Règles génériques</title>

    <para>
     Des règles génériques évitent d'avoir à écrire ligne
     de Makefile pour chaque fichier source.
    </para>

    <example>
     <title>Un autre exemple de Makefile</title>
     <programlisting>
CC = gcc
CFLAGS = -O2 -c
OBJS = file1.o file2.o

all : executable
.c.o :
        $(CC) $(CFLAGS) $<
executable : $(OBJS)
        $(CC) -o $@ $(OBJS)
     </programlisting>
    </example>

    <para>
     La règle <userinput>.c.o:</userinput> s'applique pour tous les
     fichiers <filename>.o</filename> et ont comme unique dépendance
     le fichier <filename>.c</filename> correspondant.
     <symbol>$&lt;</symbol> est alors substitué par le nom
     du fichier sur lequel cette règle est instanciée.
    </para>

    <para>
     On englobe pratiquement le premier exemple de
     <filename>Makefile</filename>, à la seule différence que l'on
     ne tient pas compte des dépendances sur les fichiers
     <filename>.h</filename>. La commande <command>makedepend</command>
     permet d'automatiser la génération de ces dépendances supplémentaires.
    </para>

    <example>
     <title>Les dépendances supplémentaires</title>
     <programlisting>
% cat Makefile
CC = gcc
CFLAGS = -O2 -c
OBJS = file1.o file2.o
SRCS = file1.c file2.c

all : executable
.c.o :
        $(CC) $(CFLAGS) $<
executable : $(OBJS)
        $(CC) -o $@ $(OBJS)CC = gcc
depend :
        makedepend -I. $(SRC)
% cat file1.c
#include "file1.h"
main() {}
% cat file2.c
#include "file2.h"
#include "file1.h"
% makedepend -I. file1.c file2.c
% cat Makefile
CC = gcc
CFLAGS = -O2 -c
OBJS = file1.o file2.o
SRCS = file1.c file2.c

all : executable
.c.o :
        $(CC) $(CFLAGS) $<
executable : $(OBJS)
        $(CC) -o $@ $(OBJS)CC = gcc
depend :
        makedepend -I. $(SRC)

# DO NOT DELETE

file1.o: ./file1.h
file2.o: ./file2.h ./file1.h
     </programlisting>
    </example>
   </sect2>

   <sect2 id="makefiles-extensions">
    <title>Plus loin</title>

    <para>
     Les makefiles peuvent servir à automatiser toutes les tâches
     nécessitant d'appliquer plusieurs traitements consécutifs à un
     document. A titre d'exemple, ce document a été généré en utilisant
     un <filename>Makefile</filename>. Le fichier source est un document
     SGML, il est traité par la commande <command>jade</command> pour produire
     un document HTML et un document TeX. Le document TeX est ensuite
     compilé à son tour. Le makefile utilisé dans ce cas est le suivant:
    </para>

    <programlisting>
all : development.ps index.html 

index.html : development.sgml
        jade -ihtml -t sgml -d formation.dsl\#html development.sgml
development.tex : development.sgml
        jade -t tex -d formation.dsl\#print development.sgml
development.dvi : development.tex
        jadetex $<
development.ps : development.dvi
        dvips -o $@ $<
    </programlisting>
   </sect2>
 </sect1>

 <sect1 id="unix">
   <title>Unix</title>
   <sect2 id="unix-intro">
    <title>Introduction</title>

    <para>
     Unix est une marque déposée par la société AT&amp;T d'un systeme
     d'exploitation d'ordinateur. <note><para> A définir. </para></note>
     L'objectif d'un systeme d'exploitation
     est de faciliter l'interface entre l'utilisateur
     et le matériel. Par des abstractions, un OS (Operating System,
     abbrévation de système d'exploitation) masque à l'utilisateur 
     les ressources matérielles et les lui présente sous une 
     forme unifiée. Ceci est valable pour le/les processeurs, la mémoire, 
     les périphériques, les disques, etc. Ces abstractions se font
     par le biais d'une API qui standardise les interactions entre
     l'utilisateur et le matériel.
    </para>

    <para>
     Unix est un système multi-utilisateur, multi-taches, préemptif
     (termes a définir). Il repose donc à ce titre sur des principes de
     partage des ressources entre les différents utilisateurs.
    </para>

   </sect2>
   <sect2 id="unix-fichiers">
    <title>Gestion de fichiers</title>

    <para>
     On distingue plusieurs types de fichiers, parmi lesquels
     les deux plus importants : les fichiers
     <firstterm>réguliers</firstterm> et les
     <firstterm>répertoires</firstterm>.
     Le séparateur de noms de répertoires est le
     symbole <userinput>/</userinput>. Peu ou pas de contraintes 
     existent sur les noms de fichiers.  Leur longueur est généralement
     limitée à 512 ou 1024 caractères selon les systèmes, ce qui laisse
     le temps de voir venir.
    </para>

    <sect3 id="unix-directories">
     <title>Gestion des répertoires</title>

     <simplelist>
      <member><command>mkdir</command>: création</member>
      <member><command>rmdir</command>: destruction</member>
      <member><command>pwd</command>: répertoire courant</member>
      <member><command>cd</command>: changement du répertoire courant</member>
     </simplelist>

    </sect3>
    <sect3 id="unix-file-attributes">
     <title>Attributs des répertoires et des fichiers</title>
     <para>
      <command>ls</command>
     </para>

     <para>
      Un utilisateur est identifié sous Unix pour un nom et
      un numéro d'utilisateur, ainsi que par son appartenance
      à un ou plusieurs groupes. Les fichiers Unix disposent donc
      de droits d'acces à ces deux niveau. Les droits se décomposent
      classiquement en droits de lecture (r), écriture (w) et
      exécution (x) et sont définis respectivement pour l'utilisateur,
      pour les membres du groupe et pour les autres utilisateurs
      (chaine <userinput>rwxr-xr-x</userinput> dans le listing).
     </para>
     <para>
      Les droits sur répertoires ont des significations particulieres,
      indiquant la possibilite de lire le contenu d'un répertoire (r),
      de créer de nouveaux fichiers dans un répertoire (w), et la
      possibilité de faire un <command>cd</command> dans ce répertoire (x).
     </para>
     <para>
      On change les droits des fichiers avec la commande
      <command>chmod</command> qui a la syntaxe suivante:
      <userinput>chmod [ugoa][+-=][rwx] files...</userinput>.
     </para>

     <example>
      <title>Les droits des fichiers</title>
      <programlisting>
% cd /
% ls -l
total 108
drwxr-xr-x   2 root     root         4096 Oct 24 16:02 bin/
drwxr-xr-x   2 root     root         4096 Oct 24 17:02 boot/
drwxr-xr-x   6 root     root        36864 Oct 26 15:26 dev/
drwxr-xr-x  41 root     root         4096 Oct 26 22:30 etc/
-rw-r--r--   1 root     root            2 Sep 24 23:53 fonts.dir
drwxr-xr-x   6 root     root         4096 Feb  6  1996 home/
drwxr-xr-x   4 root     root         4096 Oct 24 16:00 lib/
drwxr-xr-x   2 root     root        16384 Sep 24 23:27 lost+found/
drwxr-xr-x   2 root     root         4096 Sep 20 17:13 misc/
drwxr-xr-x   4 root     root         4096 Oct  9  1998 mnt/
drwxr-xr-x   2 root     root         4096 Aug 23 18:03 opt/
dr-xr-xr-x  70 root     root            0 Oct 26 15:25 proc/
drwxr-x---   9 root     root         4096 Oct 27 11:06 root/
drwxr-xr-x   3 root     root         4096 Oct 24 17:17 sbin/
drwxrwxrwt  13 root     root         4096 Oct 27 14:45 tmp/
drwxr-xr-x  21 root     root         4096 Aug  2  1998 usr/
drwxr-xr-x  21 root     root         4096 Oct 24 16:02 var/
      </programlisting>
     </example>

     <para>
      Les manipulations possibles de fichiers sont:
     </para>

     <simplelist>
      <member><command>mv</command>: renommer, déplacer</member>
      <member><command>cp</command>: copier</member>
      <member><command>rm</command>: effacer</member>
      <member>
       <command>cat</command> ou <command>more</command>:
       visualiser
      </member>
     </simplelist>

    </sect3>
   </sect2>
   <sect2 id="unix-shell">
    <title>Shell</title>

    <para>
     L'interaction avec l'utilisateur passe par un 
     interpréteur de commandes (aussi appelé <firstterm>shell</firstterm>).
     Il s'agit d'un processus Unix, un programme,
     qui interprete séquentiellement chaque commande
     entrée au clavier. Chaque commande est un processus
     Unix.
     (<note><para>parler des filiations entre les processus</para></note>).
     On termine l'exécution d'un shell de commande par la commande
     <command>exit</command>.
    </para>

    <para>
     Les processus lancés par le shell, peuvent fonctionner en
     mode synchrone (par défaut) ou asynchrone. Exemple:
    </para>

    <example>
     <title>Exemples de processus en tâche de fond</title>
     <programlisting>
% sleep 2; echo coucou; sleep 3; echo coucou
% sleep 2 & echo coucou & sleep 3 & echo coucou
     </programlisting>
    </example>

    <para>
     La commande <command>ps</command> permet de lister tous 
     les processus.
    </para>

    <programlisting>
% ps axu
USER       PID %CPU %MEM   VSZ  RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.2  1104  464 ?        S    00:39   0:03 init
root         2  1.1  0.0     0    0 ?        SW   00:39  10:14 [kapmd]
root         3  0.0  0.0     0    0 ?        SW   00:39   0:00 [kswapd]
root         4  0.0  0.0     0    0 ?        SW   00:39   0:00 [kflushd]
bin        157  0.0  0.2  1200  396 ?        S    00:40   0:00 portmap
root       173  0.0  0.2  1100  496 ?        S    00:40   0:01 /usr/sbin/apmd -p
root       226  0.0  0.2  1160  524 ?        S    00:40   0:00 syslogd -m 0
root       237  0.0  0.4  1416  768 ?        S    00:40   0:00 klogd
daemon     253  0.0  0.2  1132  484 ?        S    00:40   0:00 /usr/sbin/atd
root       269  0.0  0.3  1308  600 ?        S    00:40   0:00 crond
bellet   28007  0.1  2.3  7372 4412 ?        S    09:48   0:31 gvim development.sgml
bellet   28013  0.0  2.0  7180 3976 ?        S    09:56   0:01 gvim corba.sgml
bellet   28418  0.0  0.7  2440 1520 ?        S    14:35   0:00 rxvt
bellet   28419  0.0  0.6  2252 1304 pts/3    S    14:35   0:00 -csh
bellet   28488  0.0  1.2  3608 2364 pts/3    S    14:40   0:00 gv development.ps
bellet   28497  0.0  1.6  5136 3104 pts/3    S    14:44   0:01 gs -dNOPLATFONTS -sDEVICE=x11alpha -dNOPAUSE -dQUIE
bellet   28559  0.0  0.7  2444 1520 pts/3    S    15:14   0:00 rxvt
bellet   28560  0.0  0.5  2112 1144 pts/4    S    15:14   0:00 -csh
bellet   28627  0.0  0.4  2548  912 pts/0    R    15:42   0:00 ps axu
    </programlisting>

    <programlisting>
% ps axgf
  PID TTY      STAT   TIME COMMAND
    1 ?        S      0:03 init
    2 ?        SW    10:14 [kapmd]
    3 ?        SW     0:00 [kswapd]
    4 ?        SW     0:00 [kflushd]
  157 ?        S      0:00 portmap
  173 ?        S      0:01 /usr/sbin/apmd -p 10 -w 5 -W -u
27981 ?        S      0:07 /usr/lib/netscape/netscape-communicator -irix-session-management corba-gnome-gnorba.htm
27996 ?        S      0:00  \_ (dns helper)
28007 ?        S      0:31 gvim development.sgml
28013 ?        S      0:01 gvim corba.sgml
28418 ?        S      0:00 rxvt
28419 pts/3    S      0:00  \_ -csh
28488 pts/3    S      0:00      \_ gv development.ps
28497 pts/3    S      0:01      |   \_ gs -dNOPLATFONTS -sDEVICE=x11alpha -dNOPAUSE -dQUIET -dSAFER -
28559 pts/3    S      0:00      \_ rxvt
28560 pts/4    S      0:00          \_ -csh
    </programlisting>

    <para>
     On termine un processus avec la commande
     <function>kill</function>.  On peut envoyer à un processus plusieurs
     types de signaux (<function>kill -l</function> pour avoir la liste).
     Le plus connu est le signal 9 (<symbol>KILL</symbol>) pour tuer
     un processus lancé en tâche de fond. Les shells modernes permettent
     aussi d'utiliser des numéros de jobs locaux au shell, ce qui évite 
     de faire la commande <command>ps</command>. Exemple:
    </para>
    <programlisting>
% yes > /dev/null &
[1] 28646
% yes > /dev/null &
[2] 28647
% ps axg | grep yes
28646 pts/0    R      0:43 yes
28647 pts/0    R      0:14 yes
% kill -STOP %1
[1]  + Suspended (signal)            yes > /dev/null
% ps axg | grep yes
28646 pts/0    T      1:01 yes
28647 pts/0    R      0:48 yes
% kill -CONT %1
[1]    yes > /dev/null &
% kill -TERM %1
[1]    Terminated                    yes > /dev/null
71.700u 0.220s 2:44.82 43.6%    0+0k 0+0io 88pf+0w
% fg %2
yes > /dev/null
CTRL-Z

Suspended
% jobs
[2]  + Suspended                     yes > /dev/null
% bg %2
[2]    yes > /dev/null &
% jobs
[2]    Running                       yes > /dev/null
% kill -KILL %2
[2]    Killed                        yes > /dev/null
160.400u 0.400s 3:47.71 70.6%   0+0k 0+0io 88pf+0w
    </programlisting>

    <para>
     La commande <command>top</command> permet d'avoir la liste
     remise régulièrement à jour des processus sur la machine, classé
     par utilisation du temps CPU. Elle est particulièrement pour
     contrôler que des processus oubliés ne continuent pas à tourner
     inutilement sur la machine. Exemple:
    </para>
    <programlisting>
  3:57pm  up 15:18,  4 users,  load average: 0.17, 0.54, 0.32
60 processes: 59 sleeping, 1 running, 0 zombie, 0 stopped
CPU states:  0.0% user,  0.9% system,  0.0% nice, 99.0% idle
Mem:  191208K av, 178356K used,  12852K free,      0K shrd,   2052K buff
Swap: 385452K av,      0K used, 385452K free                 38648K cached

  PID USER     PRI  NI  SIZE  RSS SHARE STAT  LIB %CPU %MEM   TIME COMMAND
28654 bellet    16   0  1064 1064   856 R       0  0.5  0.5   0:00 top
25525 bellet     3   0  1588 1588  1112 S       0  0.3  0.8   0:02 rxvt
    1 root       0   0   464  464   392 S       0  0.0  0.2   0:03 init
    2 root       0   0     0    0     0 SW      0  0.0  0.0  10:14 kapmd
    3 root       0   0     0    0     0 SW      0  0.0  0.0   0:00 kswapd
    4 root       0   0     0    0     0 SW      0  0.0  0.0   0:00 kflushd
  157 bin        0   0   396  396   316 S       0  0.0  0.2   0:00 portmap
  173 root       0   0   496  496   424 S       0  0.0  0.2   0:01 apmd
  226 root       0   0   524  524   424 S       0  0.0  0.2   0:00 syslogd
  237 root       0   0   768  768   392 S       0  0.0  0.4   0:00 klogd
  253 daemon     0   0   484  484   404 S       0  0.0  0.2   0:00 atd
  269 root       0   0   600  600   504 S       0  0.0  0.3   0:00 crond
  285 root       0   0   512  512   432 S       0  0.0  0.2   0:00 inetd
    </programlisting>
   </sect2>
   <sect2 id="unix-shell-advanced">
    <title>Utilisation avancée du C-Shell</title>

     <para>
      Plusieurs shells (interpréteurs de commandes) existent sous Unix.
      Ils possedent chacun leur syntaxe et leurs 
      <emphasis>built-ins</emphasis> spécifiques,
      mais ils se composent en deux grandes catégories:
     </para>

     <variablelist>
      <varlistentry>
       <term>Compatibles Bourne Shell</term>
       <listitem>
        <para>
         Les shells compatibles avec le Bourne Shell
         (<filename>/bin/sh</filename>): 
         <command>ksh</command>, <command>bash</command>,
         <command>zsh</command>, etc.
         Ils se distinguent entre eux par leur facilité d'édition
         des commandes, par la gestion ou pas d'un historique,
         par leur license d'utilisation entre autres. Ils sont
         généralement dédiés à l'écriture de scripts système.
        </para>
       </listitem>
      </varlistentry>

      <varlistentry>
       <term>Compatibles C-Shell</term>
       <listitem>
        <para>
         Les shells de la famille des C-shells: csh, tcsh.
         Leur syntaxe est proche de celle du langage C,
         ce qui leur a donné ce nom.
        </para>
       </listitem>
      </varlistentry>
     </variablelist>

     <para>
      L'utilisateur a la possibilité de changer son shell courant avec la
      commande <command>chsh</command> ou <command>passwd</command>.
      Pour qu'un shell soit autorisé, il doit être référencé dans le
      fichier <filename>/etc/shells</filename> pour des raisons de 
      sécurité.
     </para>

     <para>
      Les shells ne sont pas une simple boucle d'attente des
      commande de l'utilisateur. Ils sont des langages de commandes
      à part entiere, avec leurs variables, leurs structures de controle.
     </para>

     <sect3 id="unix-csh-variables">
      <title>Les variables</title>
      <para>
       Les variables ne sont pas typées.
      </para>

      <programlisting>
% set X=123
% set Y=toto
% echo $X $Y
123 toto
      </programlisting>

      <para>
       Les variables peuvent être visibles uniquement dans le shell
       courant (<userinput>set</userinput>) ou bien dans tous les
       processus fils (<userinput>setenv</userinput>).
       <userinput>setenv</userinput> sert pour
       affecter les variables d'environnement, par exemple
       <envar>DISPLAY</envar>.
      </para>

      <programlisting>
% set V1=1
% setenv V2 2
% csh
% echo $V1
V1: Undefined variable.
% echo $V2
2
% exit
% echo $V1 $V2
1 2
      </programlisting>
     </sect3>

     <sect3 id="unix-csh-control">
      <title>Les structures de controle</title>

      <para>
       Les boucles, les itérations, les tests sont au
       rendez-vous:
      </para>

      <programlisting>
% foreach i ( 1 2 3 4 )
? echo $i
? end
1
2
3
4

% foreach file ( * )
? echo $file
? end
fichier.c
fichier.o
Makefile

% if ( $i < 4 ) then
?    echo oui
? else
?    echo non
? fi
oui

% while ( $condition )
? instruction
? instruction
? end
      </programlisting>
     </sect3>

     <sect3 id="unix-csh-var-substitute">
      <title>Substitution de variables</title>

      <programlisting>
% set variable = 123
% echo $variable
123
% set v2 = un nom avec un espace
% echo $v2
un
% set v2 = "variable vaut $variable"
% echo $v2
variable vaut 123
% set v2 = 'variable vaut $variable'
% echo $v2
variable vaut $variable
% set v2 = variables\ vaut\ $variable
% echo $v2
variable vaut 123
% set d  = "la date est `date`"
% echo $d
la date est Wed Oct 27 17:16:05 MET DST 1999
      </programlisting>

      <para>
       Les simples quotes interdisent l'évaluation du contenu à la
       différence des doubles quotes.
      </para>

      <para>
       L'anti-slash <symbol>\</symbol> interdit l'interprétation du
       caractère qui le suit.
      </para>

      <programlisting>
% set v2 = xxxx\'yyyy
% echo $v2
% xxxx'yyyy
      </programlisting>

      <para>
       Il est possible de compléter des noms de fichiers.
      </para>

      <programlisting>
% set v2 = fichier.*
% echo $v2
fichier.c fichier.o
      </programlisting>
     </sect3>

     <sect3 id="unix-csg-io">
      <title>Les flots standards d'entrée/sortie</title>

      <para>
       Tout processus Unix lit les données en entrée sur un fichier
       <function>stdin</function>, écrit les données en sortie sur un fichier
       <function>stdout</function>, et écrit les messages d'erreur sur un
       troisième fichier <function>stderr</function>. Clavier et écran ne
       sont, pour le système Unix, que des fichiers particuliers, en lecture
       seule pour le premier et en écriture seule pour le deuxième.
       Ces trois flots sont représentés en csh par les symboles suivants:
      </para>

      <table>
       <title>Les flots d'entrées/sorties</title>

       <tgroup cols="2">
        <thead>
         <row>
          <entry>Flots</entry>
          <entry>Symbole</entry>
         </row>
        </thead>
        <tbody>
         <row>
          <entry>stdin</entry>
          <entry><userinput>&lt;</userinput></entry>
         </row>
         <row>
          <entry>stdout</entry>
          <entry><userinput>&gt;</userinput></entry>
         </row>
         <row>
          <entry>stdout (append)</entry>
          <entry><userinput>&gt;&gt;</userinput></entry>
         </row>
         <row>
          <entry>stdout + stderr</entry>
          <entry><userinput>&gt;&amp;</userinput></entry>
         </row>
         <row>
          <entry>stdout + stderr (append)</entry>
          <entry><userinput>&gt;&gt;&amp;</userinput></entry>
         </row>
        </tbody>
       </tgroup>
      </table>

      <para>
       On connecte les flux d'entrée/sorties de deux processus avec
       un &ldquo;pipe&rdquo; Unix:
      </para>
      <programlisting>
% cat development.sgml | wc
    571    3147   25034
% make |& more
      </programlisting>

      <para>
       On utilise un sous-shell pour séparer <function>stdin</function>
       et <function>stdout</function>:
      </para>
      <programlisting>
% ( make > make.log ) >& make.err
      </programlisting>
     </sect3>

     <sect3 id="unix-csh-subshell">
      <title>Intérêt des sous-shells</title>

      <para>
       L'utilisation des <firstterm>back-quotes</firstterm>
       permet de laner des commandes dans des sous-shells et de
       substituer la commande par son résultat dans le shell
       courant.
      </para>
      <programlisting>
% foreach f (`cat file_list`)
? rm -f $file
? end
      </programlisting>
     </sect3>

     <sect3 id="unix-csh-filename-subst">
      <title>Substitutions des noms de fichiers</title>

      <para>
       Il existe un certain nombre de regles de substitution, qui, même
       si une certaine ressemblance existe, ne s'apparentent pas à des
       expression régulières.
      </para>

      <itemizedlist>
       <listitem>
        <para>
         <filename>*</filename>:
         plusieurs caractères différents de ".".
        </para>
       </listitem>

       <listitem>
        <para>
         <filename>?</filename>:
         un seul caractère different de ".".
        </para>
       </listitem>

       <listitem>
        <para>
         <filename>[abc]</filename>:
         une alternative de caractères.
        </para>
       </listitem>

       <listitem>
        <para> 
         <filename>{chaine1,chaine2}</filename>:
         une alternative de chaines de caractères.
        </para>
       </listitem>
      </itemizedlist>

      <para>
       Exemples:
      </para>
      <itemizedlist>
       <listitem>
        <para>
         <filename>*.*</filename>:
         tous les fichiers contenant un point.
        </para>
       </listitem>

       <listitem>
        <para>
         <filename>.??*</filename>:
         tous les fichiers commencant par un point et avec au moins deux
         autres caractères. Cela permet de lister les fichiers cachés
         (commencant par <filename>.</filename>) et d'exclure
         <filename>.</filename> et <filename>..</filename> (le répertoire
         courant et le répertoire parent respectivement).
        </para>
       </listitem>

       <listitem>
        <para>
         <filename>~/src/[12]/Makefile</filename>:
         permet de matcher les deux fichiers 
         <filename>/home/bellet/src/1/Makefile</filename> et
         <filename>/home/bellet/src/1/Makefile</filename>. En effet, le
         symbole <symbol>~</symbol> est un raccourci pour la variable
         d'environnement <envar>HOME</envar>, indiquant le home-directory
         de l'utilisateur.
        </para>
       </listitem>

       <listitem>
        <para>
         <filename>*.[hc]</filename>:
         permet de matcher tous les fichiers <filename>.c</filename>
         et <filename>.h</filename> du répertoire courant.
        </para>
       </listitem>
      </itemizedlist>
     </sect3>

     <sect3 id="unix-csh-numerical-expr">
      <title>La galere des expressions numériques</title>
      <para>
       Il faut utiliser la commande externe <command>expr</command>.
      </para>

      <programlisting>
% set a = 3
% set b = `expr \( 1 + 3 \) \* $a`
% echo $b
12
      </programlisting>

      <para>
       Faire un compteur est possible:
      </para>

      <programlisting>
% set a = 0
% while ( $a < 10 )
?   echo Traitement du fichier numéro $a
?   set a = `expr $a + 1`
? end
      </programlisting>
     </sect3>

     <sect3 id="unix-csh-conds">
      <title>Les conditions</title>
      <para>
       L'écriture des conditions est tres similaire à la syntaxe
       du langage C. Encore une chance, ce shell a été écrit pour ca!
      </para>

      <programlisting>
% if ( $x == "chaine" ) ...
% if ( $x <= 10 && $y == 2 ) ...
% if ( $x != 1 || "$2" == `hostname` ) ...
% if ( -r fichier1 && -x fichier2 && -d fichier3 ) ...
      </programlisting>

      <para>
       Un autre moyen d'écrire des tests conditionnels consiste à
       utiliser une caractéristique des processus Unix, qui retournent un 
       entier lorsqu'ils se terminent indiquant si la commande s'est bien 
       exécutée ou pas. Si la commande s'est bien terminée, la variable
       d'environnement <symbol>$status</symbol> vaut 0, sinon tout autre
       valeur correspond à un code d'erreur. Voir les pages de manuel
       de chaque commande Unix pour connaitre la signification des codes
       d'erreur retournés. Cet entier est la valeur renvoyée par les
       programmes en C écrit scrupuleusement:
      </para>

      <programlisting>
int main() {
        ... plein de choses;
        procedure_truc();
        ... encore plein d'autres choses;
        /* 
         * fin normale du programme
         */
        return 0;
}
void procedure_truc () {
        ...
        if (erreur) {
           fprintf (stderr,"erreur irrecuperable. bye.\n");
           /*
            * fin anormale
            */
           exit (1);
        }
}
     </programlisting>
     <para>
      Ainsi, les primitives de comparaison <symbol>&amp;&amp;</symbol> et
      <symbol>||</symbol> utilisent la valeur de <symbol>$status</symbol>
      pour fonctionner. On peut écrire des conditions également et
      de façon très compactes ainsi:
     </para>
     <programlisting>
% echo azerty | grep -q aze && echo found
found
% echo azerty | grep -q qwe && echo found
% echo azerty | grep -q qwe || echo not found
not found
      </programlisting>
     </sect3>

     <sect3 id="unix-csh-grep">
      <title>Gestions de flot d'entrées/sortie et grep</title>
      <para>
       La commande <command>grep</command> lit des données
       depuis un fichier ou depuis l'entrée standard, et affiche sur
       sa sortie standard les lignes contenant la chaine de caractères
       passée en paramètre sur la ligne de commande.
      </para>

      <para>
       Les instructions <function>grep</function> et
       <function>egrep</function> permettent de faire des recherches
       d'expression régulières dans le flux <function>stdin</function>.
       Par exemple on peut effectuer une recherche de deux chaines
       de caractères dans un fichier en utilisant une expression
       régulière dans la commande <command>egrep</command>.
       On notera qu'il est nécessaire de mettre l'expression régulière
       entre quotes, afin que le <symbol>|</symbol> ne soit pas 
       interprété comme le pipe Unix dans cet exemple:
      </para>
      <programlisting>
% egrep 'NNTPSERVER|PATH' ~/.cshrc 
setenv  NNTPSERVER      demo2.univ-lyon1.fr
setenv  LD_LIBRARY_PATH ${HOME}/creatis/lib:/usr/local/lib
setenv  CLASSPATH       /usr/lib/netscape/java/classes/java40.jar
      </programlisting>

      <para>
       On peut faire un ET avec un pipe Unix cette fois-ci. Dans cet
       exemple, on va matcher par exemple la ligne qui contient les deux
       chaines passées a chacun des <command>grep</command>:
      </para>
      <programlisting>
% grep define *.[hc] | grep MA_CONSTANTE
fichier.c:#define MA_CONSTANTE 10        /* Ma constante */
      </programlisting>

      <para>
       L'option <userinput>-i</userinput> est très utile pour
       ne pas différencier majuscules et minuscules dans la recherche.
      </para>

      <para>
       A savoir sur les expressions régulières: 
      </para>
      <table>
       <title>Expression régulières</title>
       <tgroup cols="2">
        <thead>
         <row>
          <entry>Symbole</entry>
          <entry>Signification</entry>
         </row>
        </thead>
        <tbody>
         <row>
          <entry><symbol>.</symbol></entry>
          <entry>un caractère quelconque</entry>
         </row>
         <row>
          <entry><symbol>|</symbol></entry>
          <entry>une alternative</entry>
         </row>
         <row>
          <entry><symbol>()</symbol></entry>
          <entry>une sous-expression régulière</entry>
         </row>
         <row>
          <entry><symbol>*</symbol></entry>
          <entry>
           le caractère ou l'expression précédents
           se répètent <symbol>n</symbol> fois, avec
           <symbol>n</symbol> &ge; 0
          </entry>
         </row>
         <row>
          <entry><symbol>+</symbol></entry>
          <entry>
           le caractère ou l'expression précédents
           se répètent <symbol>n</symbol> fois, avec
           <symbol>n</symbol> &gt; 0
          </entry>
         </row>
         <row>
          <entry><symbol>^</symbol></entry>
          <entry>le début de la ligne</entry>
         </row>
         <row>
          <entry><symbol>$</symbol></entry>
          <entry>la fin de la ligne</entry>
         </row>
        </tbody>
       </tgroup>
      </table>
     </sect3>

     <sect3 id="unix-find">
      <title>La commande find</title>

      <para>
       La commande <command>find</command> permet de rechercher
       des fichiers selon certains critères. Sa syntaxe est :
       <userinput>
        <filename>find [chemin] [exp1] [exp2] ...</filename>
       </userinput>, où
       <userinput>chemin</userinput> est le répertoire de départ
       de la recherche.
       Les principales expressions utiles sont:
      </para>

      <itemizedlist>
       <listitem>
        <para>
         <userinput>-name "filename"</userinput>:
         des substitutions sont possibles.
        </para>
       </listitem>

       <listitem>
        <para>
         <userinput>-print</userinput> ou 
         <filename>-ls</filename>:
         affiche les fichiers trouvés.
        </para>
       </listitem>

       <listitem>
        <para>
         <userinput>-type f</userinput> ou 
         <userinput>-type d</userinput>:
         matche les fichiers ou les répertoires.
        </para>
       </listitem>

       <listitem>
        <para>
         <userinput>-exec</userinput>
         execute une commande sur chaque fichier matché.
        </para>
       </listitem>
      </itemizedlist>

      <para>
       Exemple d'utilisation, les trois dernières commandes
       sont équivalentes:
      </para>
      <programlisting>
% find ~ -name "*.o" -print

% find -name core -exec rm -f {} \;
% /bin/rm -f `find -name core -print`
% find -name core -print | xargs rm -f
      </programlisting>
     </sect3>

     <sect3 id="unix-sed">
      <title>sed, stream editor</title>

      <para>
       C'est une commande très complexe. Elle est intéressante
       pour substituer des expressions entre le <function>stdin</function>
       et le <function>stdout</function>. Exemple:
      </para>
      <programlisting>
% sed '30,50s/nom1/nom2/g' < fichier.c > fichier.new
      </programlisting>

      <para>
       <userinput>30,50</userinput> indique les numéros de lignes du flot
       d'entrée concernés par la substitution.
       <userinput>g</userinput> autorise plusieurs substitutions sur la
       même ligne. Un détail important dans
       l'écriture des expressions régulières est que la substitution 
       concernera toujours la chaîne la plus longue possible.
      </para>

      <para>
       L'intérêt de <command>sed</command> est qu'il est utilisable
       sous l'éditeur <command>vi</command>, en mode commande, accessible
       par <userinput>Escape</userinput>. Il peut aussi servir d'excellent
       outils de filtre.
      </para>
     </sect3>

     <sect3 id="unix-awk">
      <title>awk</title>

      <para>
       <command>awk</command>, à la différence de
       <command>sed</command> est un véritable langage de traitement de
       flot, et pas seulement un filtre d'expressions régulières. Quelques
       exemples d'utilisation:</para>

      <para>
       Pour récupérer la troisième colonne d'un tableau de
       résultats:
      </para>

      <programlisting>
% cat fichier | awk '{print $3}'
      </programlisting>

      <para>
       <function>print</function> peut etre remplacé par un 
       <function>printf</function> pour une meilleure mise en forme,
       avec les mêmes arguments qu'en langage C.
      </para>

      <para>
       Pour faire la somme des valeurs d'une colonne:
      </para>

      <programlisting>
% awk 'BEGIN{n=0}{n+=$3}END{print n}' < fichier
      </programlisting>

      <para>
       Il existe une abondante documentation sur le sujet.
      </para>
      <para>
       Le séparateur de champs est l'espace par défaut. Il
       est paramétrable.
      </para>
     </sect3>

     <sect3 id="unix-misc">
      <title>Quelques fonctions largement utilisées dans les shells</title>

      <para>
       <command>basename</command> retourne le nom
       d'un fichier en lui otant les répertoire qui précedent son nom
       et éventuellement l'extension de son nom de fichier.
      </para>

      <programlisting>
% basename /a/b/c/d.txt .txt
d
      </programlisting>

      <para>
       Une application peut etre de changer toutes les extensions
       des fichiers d'un repertoire:
      </para>

      <programlisting>
foreach file ( *.c )
?  mv $file `basefile $file .c`.old
?  end
      </programlisting>

      <para>
       On peut faire la meme chose avec un <command>sed</command>.
      </para>
     </sect3>
    </sect2>
  </sect1>
</article>
