Polycop
Polycop
Polycop
Algorithmique et programmation
dans l'environnement C/Unix
Mamoun ALISSALI
Octobre 1998
1
2
cd cat
00000000000000
11111111111111
Connecteurs
pwd cc
mkdir 00000000000000
11111111111111
de périph.
00000000000000
11111111111111
00000000000000
11111111111111
...
00000000000000
11111111111111
00000000000000
11111111111111
Mémoire
Gestion de11111111111111
00000000000000
00000000000000
11111111111111
00000000000000
11111111111111
Processus Processeur
Édition de prog. 00000000000000
11111111111111
00000000000000
11111111111111
(CPU)
Utilisation Compilation
00000000000000
11111111111111
00000000000000
11111111111111
Edition de liens
Exécution
00000000000000
11111111111111
00000000000000
11111111111111
Programmation
00000000000000
11111111111111
00000000000000
11111111111111
Dynamique 00000000000000
11111111111111
3
4 Chapitre 1 Introduction àUnix
Exemple
1 (alissali@lola) ~>stty erase ^H
Ache le nom de l'utilisateur, son numéro d'identication (UID : User IDentity ), ainsi que le numéro
du groupe actuel (GID : Group IDentity ) et la liste des groupes auxquels il appartient.
newgrp [-] [groupe]
Permet à un utilisateur de changer de groupe, à condition qu'il en fasse partie. Sans paramètre elle
replace l'utilisateur dans son groupe d'origine.
chown utilisateur fichiers
La commande en ligne 2 n'est pas comprise et provoque le message de la ligne 3. Ceci est dû à
l'absence du séparateur entre la commande cd et son argument ...
1. Pour un shell ou pour une commande, les termes entrée et sortie désignent les données, la plupart du temps stockées
dans des chiers, consommées et produites par une commande. Ils peuvent aussi désigner le clavier (entrée standard) et
l'écran (sortie standard) qui sont traités comme des chiers (cf. 1.4.2) par Unix.
La première ligne dénit la variable exemples en lui attribuant une valeur, celle-ci est substituée
avant l'exécution de la commande. On remarque que le nouveau répertoire indiqué par l'invite
(ligne 3) correspond bien à la valeur de exemples.
caractères spéciaux dans les noms de chiers :
? : désigne n'importe quel caractère
1 ~/pub/ExemplesCours>ls triBulTab.?
2 triBulTab.c triBulTab.h triBulTab.o
L'argument de la commande ls est interprété comme tous les chiers dont le nom est composé de
triBulTab. suivi de un (seul) caractère quelconque.
* : désigne n'importe quelle séquence de caractères
1 ~/pub/ExemplesCours>ls *.c
2 affiche.c rechDichRec.c
3 explMdfParFct.c triBulPtr.c
4 rechDichIt.c triBulTab.c
5 ...
6 ~/pub/ExemplesCours>ls tri*.c
7 triBulPtr.c triBulTest.c
8 triBulTab.c triDirectRec.c
9 triDirectTest.c
L'argument de la commande ls en ligne 1 est iterprété comme tous les chiers dont le nom se
termine par .c. En ligne 6 on ajoute la contrainte : commence par tri.
[...] : choix d'un caractères parmi ceux contenus entre les crochets
1 ~/pub/ExemplesCours>ls affiche.[hc]
2 affiche.c affiche.h
La commande en ligne 1 redirige la sortie de ls vers le chier liste, rien n'est aché à l'écran. En-
suite la commande more ache les contenus de liste à l'écran, on remarque qu'ils correspondent
bien au résultat de ls.
< : redirige l'entrée vers un chier (au lieu du clavier entrée standard).
1 ~/pub/ExemplesCours>more < liste
2 Makefile
3 affiche.c
4 affiche.h
5 affiche.o
6 [...]
Sans arguments la commande more attends une entrée au calvier, mais le caractère spécial < lui
indique que l'entrée vient du chier liste. Avec un fonctionnement totalement diérent, cette
commande est équivalente à celle de la ligne 2 de l'exemple précédent.
j : tube, communique la sortie d'une commande à l'entrée d'une autre.
1 ~/pub/ExemplesCours>ls | more
2 Makefile
3 affiche.c
4 affiche.h
5 affiche.o
6 [...]
7 ~/pub/ExemplesCours>nl affiche.c
8 1 #include<stdio.h>
9 2 #include"affiche.h"
10
11 3 void affiche(int tab[], int taille)
12 4 {
13 [...]
14 8 } /* END affiche */
15 ~/pub/ExemplesCours>cat affiche.[hc] | nl | lp
La ligne 1 indique que la sortie de ls doit être acheminé vers la commande more au lieu d'être
aché à l'écran. C'est la commande more qui eectue l'achage 2. Ceci est équivalent aux deux
commandes (lignes 1 et 2) de l'exemple sur > ci-haut, mais sans l'utilisation d'un chier intermé-
diaire.
La commande nl (Number lines ), ligne 7, permet de numéroter les lignes d'un chier. En ligne
13 elle eectue cette opération sur les deux chiers affiche.c et affiche.h achés ensemble
à l'écran grâce à la commande cat. Ensuite, le deuxième tube envoie le résultat directement à
l'impression (commande lp).
; : séparateur de commandes
1 ~/pub/ExemplesCours>cd .. ; ls
2 BDU ExemplesCours
Dans le premier cas, ligne 1, le caractère > est interprété, ce qui a pour conséquence de diriger le
texte aché par echo dans le chier fichierMsg, ce qu'on vérie en lignes 2 et 3. Dans le deuxième
cas, le \ empêche l'interprétation de >, celui-ci est considéré comme un caractère normal, par
conséquent tout ce qui suit est aché à l'écran (ligne 5).
' ' : empêche l'interprétation de toute la séquence :
1 ~/pub>echo $exemples
2 /export/home/ens/cuc/alissali/pub/ExemplesCours
3 ~/pub>echo '$exemples'
4 $exemples
2. Ceci est utile lorsque la liste est trop longue pour être achée en une seule fois à l'écran; more eectue un achage
page par page.
Avant d'exécuter la commande en ligne 1, exemples est remplacé par sa valeur d'où le résultat
en ligne 2. À la deuxième exécution on empêche la séquence $exemples d'être interprétée, elle est
donc achée telle que. On obtient le même résultat avec :
1 ~/pub>echo $exemples
2 $exemples
Ici on empêche l'interprétation du seul caractère $, exemples n'est donc plus interprétée comme
une variable et le tout est aché comme une chaîne de caractères ordinaire.
" " : empêche l'interprétation de la séquence sauf certains caractères (comme $)
Lorsqu'une commande est lancée à partir d'un shell, celui-ci attend la n de l'exécution de la com-
mande pour reprendre la main et permettre à l'utilisateur d'entrer d'autres commandes. L'exécu-
tion en arrière plan indique au shell de continuer à opérer en parallèle avec la commande ce qui
est particulièrement utile dans le cas des commandes interactives telles que emacs. On remarque,
ligne 2, que l'invite a été aché tout de suite sans attendre la n de l'exécution d'emacs.
1.3.2 Variables prédénies
Comme tous les shells, csh maintient un ensemble de variables prédénies qui dénissent le contexte
courant. Parmi ces variables, les plus utilisées sont :
Variable Valeur
cwd nom complet (chemin d'accès absolu) du répertoire courant .
home répertoire racine (ou personnel) de l'utilisateur ( le caractère dans un nom de
chier prend la valeur de cette variable).
path liste des répertoires où seront cherchées les commandes.
prompt chaîne achée dans l'invite. Son absence indique qu'il
s'agit d'un shell non interactif.
shell nom du chier exécutable du programme csh.
status état de l'exécution de la dernière commande. Pour un programme C
prend la valeur retournée par main.
user nom de l'utilisateur.
Exemples
Sur la machine lola le shell par défaut est tcsh, pour travailler en csh il faut lancer l'interpréteur :
1 (alissali@lola) ~>csh
2 (alissali@%m %~)
On remarque le changement d'invite car les séquences qui commencent par % dans prompt sont spéciques
à tcsh : %m et %, interprétée par tcsh comme le nom de la machine et le répertoire courant ,
sont incomprises de csh. Par conséquent, elle sont achées telles que. Il faut alors modier l'invite à
l'aide de l'aectation (commande interne set) :
1 (alissali@%m) %~>set
2 cwd /export/home/ens/cuc/alissali
3 home /export/home/ens/cuc/alissali
4 path (/usr/bin /usr/local/bin .)
5 prompt (alissali@%m) %~>
6 shell /bin/csh
7 status 0
8 user alissali
9 ...
10 (alissali@%m) %~>set prompt='cmd. no. ! > '
11 cmd. no. 3 >
La première utilisation de set (sans argument) permet de consulter les variables actuellement dénies,
en particulier l'invite (prompt). La deuxième utilisation donne une nouvelle valeur à cette variable, le
caractère ! dans l'invite est interprété par csh comme étant le numéro de la commande courante, comme
on le voit en ligne 11.
On eectue quelques opérations pour observer le fonctionnement et l'utilisation de ces variables. Un
changement du répertoire courant modie la valeur de cwd :
1 cmd. no. 3 > cd pub
2 cmd. no. 4 > set
3 [...]
4 cwd /export/home/ens/cuc/alissali/pub
5 [...]
Pour exécuter une commande, csh se réfère à la valeur de path pour savoir dans quels répertoire chercher
l'exécutable correspondant. L'utilisateur peut obtenir la même information grâce à la commande which :
1 cmd. no. 5 > which ls
2 /usr/bin/ls
ce qui veut dire que l'exécutable ls se trouve dans le répertoire /usr/bin, celui-ci fait bien partie de
la valeur de path (cf. 1er exemple ci-dessus). Si on supprime ce répertoire de la valeur de path, csh ne
trouve plus la commande (message d'erreur ligne 3) :
1 cmd. no. 6 > set path='(/usr/local/bin .)'
2 cmd. no. 7 > ls
3 ls: Command not found.
De plus la valeur de status est mise à jour pour indiquer une erreur (la valeur est 0 en cas de bon
déroulement) :
1 cmd. no. 8 > echo $status
2 1
1 (alissali@lola) ~>csh
2 (alissali@%m) %~>set prompt='csh: ! >'
3. Les variables d'environnement sont toujours en majuscules, les variables internes en minuscules. Il s'agit d'une
convention et non pas d'une règle lexicale.
4. L'exemple ne montre que les variables qui servent notre propos.
La première ligne lance un csh, la deuxième modie l'invite. Ensuite on dénit en l'initialisant une nou-
velle variable (maVar, ligne 3), et on vérie le bon déroulement de l'opération (lignes 4 et 5), ainsi que la
nouvelle valeur de prompt (ligne 7). On répète les mêmes opérations avec une variable d'environnement,
MAVAR, en observant les valeurs de quelques autres variables d'environnement (lignes 8 à 13).
Lorsqu'on lance un nouveau csh, on observe que prompt retrouve son ancienne valeur et que maVar
n'est pas dénie : les variables internes ne sont pas communiquées au nouveau processus. Par contre les
variables d'environnement, y compris MAVAR, sont bien reconnues par le nouveau shell.
1.3.4 Conguration du C-Shell
Au lancement d'un interpréteur de commande celui-ci exécute les commandes qui se trouvent dans
un chier de conguration . Dans le cas du C-Shell ce chier s'appelle .cshrc et se trouve (comme pour
les autres interpréteurs) dans le répertoire de l'utilisateur (HOME). Il peut contenir des modications de
variables ou de variables d'environnement prédénies, des dénitions de variables et d'autres commandes
comme la dénition d'alias.
Exemple
1 csh: 10 >more .cshrc
2 set path=(/usr/bin /usr/local/bin .)
3 set prompt="csh: ! >"
4 alias rm 'rm -i'
5 alias dir 'ls -aF'
6 alias x logout
La première ligne ache les contenus de .cshrc , où on trouve l'initialisation des variables path et
prompt et la dénition de quelques alias.
5. La rubrique See also de la documentation en ligne sert d'index croisé permettant de trouver de nouvelles commandes
à partir de celles que l'on connait.
Commande Description
Options
Commandes pour tout type de chier (chiers normaux, répertoires, etc.)
ls [chier] (LiSt) Acher des inforamtions sur le chier ou sur ses contenus.
chmod mode chier (CHange MODe) Changer le mode d'accès (les protections) du chier.
find répertoire critères Rechercher, selon les critères, des chiers à partir du répertoire spécié.
tar opérations archive Eectuer les opérations spéciées (création, consultation, extraction) sur archive.
ln source destination (LiNk) Créer un lien (nom) pour un chier existant.
rm nom (ReMove) Supprimer un lien,
-r (Récursif) supprimer un répertoire et tous ses contenus.
mv source destination (MoVe) Déplacer ou change le nom d'un chier/répertoire.
cp source destination (CoPy) Faire une copie destination de source,
-r (Récursif) Copier tout une arborescence (un répertoire).
Commandes spéciques aux répertoires
pwd (Print Working Directory) Acher le répertoire courant.
cd nom (Change Directory) Changer de répertoire courant.
[sans argument] le répetoire courant devient HOME (répertoire racine de l'utilisateur).
mkdir nom (MaKe DIRectory) Créer un répertoire.
rmdir nom (ReMove DIRectory) Supprimer un répertoire, il doit être vide.
Commandes spéciques aux chiers
cat nom (CATenate) Acher le contenu d'un chier (clavier entrée standard par défaut).
more nom Acher le contenu page par page.
wc (Word Count) Compter les caractères, les mots et les lignes.
cmp Acher les numéros de ligne et d'octet où 2 chiers dièrent.
comm Sélectionner ou rejetter les lignes communes à deux chiers.
diff Acher ligne par ligne les diérences entre deux chiers.
sort Trier par ligne un ou plusieurs chiers (avec fusion dans ce dernier cas).
-u (Unique) Supprimer les duplications.
cut Sélectionner des champs dans les lignes d'un chier.
paste Concaténer les lignes de deux chiers.
1.4.4 La commande ls
Cette commande permet d'acher des informations sur les chiers et les répertoires en se référant à
la table des i-nodes lorsque nécessaire :
ls [-RadLCxmlnogrtucpFbqisf1%] arg1...argn
argi : liste optionnelle de noms de chiers et de répertoires. Pour chaque répertoire argi ache la
liste des chiers qu'il contient. Pour chaque chier argj ache des informations sur le chier. Sans
argument : ache la liste des chiers du répertoire courant.
Les informations achées et leur format dépendent des options.
Quelques options
si aucune option n'est présente ache uniquement les noms ;
-l : liste longue, ache la plupart des informations du i-node ;
-i : achage des noms et des numéros d'i-nodes correspondants ;
-R : achage récursif (exploration des sous-répertoires).
La liste longue a la forme suivante :
-rw-rr 1 alissali cuc 804 Dec 11 15:49 00LISEZ.MOI
Le premier champ décrit le type du chier ( `-': chier normal, `d' répertoire, etc.) et les droits
d'accès (r : lecture, w : écriture, x : exécution) pour, successivement, le propriétaire, son groupe et les
autres utilisateurs.
Le deuxième champ ache le nombre de liens du chier. Dans le troisième et le quatrième champs
on trouve respectivement le nom du propriétaire du chier et celui de son groupe.
Les champs suivants décrivent respectivement la taille (en octets), la date et l'heure de la dernière
modication et le nom du chier.
La commande chmod
chmod [ugoa ]{ + j - j = }[ rwxlsStTugo] rg1 arg2 ... argn
Permet de modier lemode ou droits d'accès (les protections) pour le(s) chier(s) spécié(s).
Exemples
1 ~/pub/ExemplesCours>ls -l rechDichTest
2 -rwxr-xr-x 1 alissali cuc 14528 Nov 25 14:12 rechDichTest
3 ~/pub/ExemplesCours>chmod u-x rechDichTest
4 ~/pub/ExemplesCours>ls -l rechDichTest
5 -rw-r-xr-x 1 alissali cuc 14528 Nov 25 14:12 rechDichTest
6 ~/pub/ExemplesCours>rechDichTest
7 rechDichTest: Command not found.
8 ~/pub/ExemplesCours>./rechDichTest
La dernière opération, ligne 14, se déroule normalement malgré la protection en écriture du chier. En
eet celle-ci n'empêche pas la suppression d'un chier car il s'agit d'une modication sur le répertoire.
Si on protège le répertoire en écriture la suppression n'est plus possible :
1 ~/pub/ExemplesCours>ls rechDichTest
2 UX:ls: ERROR: Cannot access rechDichTest: No such file or directory
3 ~/pub/ExemplesCours>chmod u-w .
4 ~/pub/ExemplesCours>rm rechDichTest.o
5 UX:rm: ERROR: rechDichTest.o not removed: Permission denied.
Pour chaque chemin d'accès dans liste-chemins-d-acces, rechercher les chiers qui vérient l'expres-
sion booléenne expBool. Cette expression est construite de primitives prédénies (cf. man page) reliées
par les opérateurs logiques de base (la juxtaposition joue le rôle du et logique, le `!' celui de la négation
et (-o) celui du ou logique).
Exemple
1 find $HOME \backslash{}( -name a.out -o -name '*.o' \backslash{}) -atime +7 -exec rm {} \backslash{};
Recherche à partir du répertoire racine personnel ($HOME) tous les chiers dont le nom est a.out ou
*.o (tous les chiers objets), et les supprime s'ils n'ont pas été utilisés depuis plus de 7 jours. On note
que les caractères susceptibles d'être interprétés par le shell (les parenthèses, `*' et `;') sont protégés
(précédé par le caractère spécial (cf. 1.2.3) \ ce qui empêche leur interprétation .
tar (Tape ARchiver ) permet de manipuler des archives de chiers. À l'origine il a été conçu pour
l'archivage sur bande magnétique. Aujourd'hui il sert particulièrement pour les transferts sur réseau.
clef détermine la fonction exécutée par tar, les plus utilisées sont : c (Créer), t (acher la lisTe) et x
(eXtraire).
Exemple
Pour créer une archive qui contient les sources (.c et .h) du répertoire courant :
1 tar cvf archive *.c *.h
Après un éventuel transfert, les deux commandes suivantes permettent respectivement de visualiser la
liste de contenus et d'extraire les chiers de l'archive :
1 tar tvf archive
2 tar xvf archive
La concaténation de deux ERs désigne les chaînes constituées de la concaténation des chaînes
désignées par les deux ERs.
Une ER peut être entourée de \( et \) ; les chaînes désignées restent les mêmes.
L'expression \n désigne la même chaîne de caractères que celle incluse entre la ne paire de \( et
\) à compter de la gauche. Par exemple ^\(.*\)\1$ désigne une ligne entière composée de deux
occurrences d'une même chaîne.
Contraindre les ER
Une ER peut être contrainte à désigner des mots :
\< contraint une ER à désigner un début de chaîne (constituée de lettres, chires et sous-tirets).
\> contraint une ER à désigner une n de chaîne.
Une ER peut être contrainte à désigner un segment initial et/ou un segment nal d'une ligne :
Un accent circonexe `^' en début d'ER la contraint à désigner un segment initial d'une ligne.
Un `$' en n d'ER la contraint à désigner un segment nal d'une ligne.
La construction ^ER$ contraint l'ER à désigner une ligne entière.
L'ER nulle (par exemple //) est équivalente à la dernière expression régulière rencontrée.
1.5.2 La commande egrep
egrep [-bchilnsv] [patron] [fichier...]
egrep ache à l'écran toutes les lignes de fichier qui contiennent une chaîne désignée par patron.
Cette commande accepte les expressions régulières à l'exception de \( et \), et avec les ajouts suivants :
Une ER suivie de + désigne une ou plus occurrence de l'ER.
Une ER régulière suivie de ? désigne 0 ou 1 occurrence de l'ER.
La barre verticale | et nouvelle-ligne jouent le rôle de ou entre deux ERs.
Les parenthèses peuvent être utilisées pour grouper.
Il faut faire attention aux caractères qui ont un sens particulier pour les shells, en particulier : $, *,
[, , |, (, ) et \ . Il vaut mieux entourer toute l'ER par des apostrophes '...' pour éviter toute
interprétation par le shell.
L'ordre de précédence des opérateurs est le suivant : [], * ? +, la concaténation puis le ou.
Selon l'implantation egrep accepte des simplication d'écriture en dénissant des classes de carac-
tères . Par exemple [:alnum:] désigne la classe de tout les caractères alpha-numériques.
Exemples
Extraction des directives du préprocesseur (distinguée par #) :
1 Cmd 1 >egrep '#' etudiant.c
2 #include<stdio.h>
3 #include"etudiant.h" /* inclut personne.h */
10 */
11 saisiePersonne (&((*ptrEt).pers));
12 scanf("%d", &((*ptrEt).id));
ce n'est pas susant car certaines lignes contiennent des `*' sans être des commentaires.
Extraction des dates (n'importe quel chire) :
1 Cmd 3 >egrep '[0-9]' etudiant.c
2 * Historique : 10.12.96 : réalisation, test OK.
Toutes les lignes de commentaire n'ont pas la séquence /*. Un meilleur résultat est obtenu si on
désigne (espace) ou / avant l'astérisk :
1 Cmd 5 >egrep '[ /]\*' etudiant.c
2 /* Fichier : etudiant.c
3 * Description : module de traitement d'un enregistrement typeEtudiant.
4 ...
5 */
6 #include"etudiant.h" /* inclut personne.h */
7 ...
Extraction des dates, la version suivante (une date est une succession de chires et de `.') :
1 Cmd 6 >egrep '[0-9.]*' etudiant.c
est inadéquate car elle comprend zéro répétition : toutes les lignes seront sélectionnées. Il faut donc forcer
une occurence au moins :
1 Cmd 7 >egrep '[0-9.][0-9.]*' etudiant.c
sélectionne toutes les lignes avec au moins un `.' ou un chire, y compris celle qui contiennent des
chaînes comme .5 ou 3.14195.
Pour éviter une telle confusion, le cas échéant, la description, plus complexe et plus complète, doit
désigner clairement la forme exacte d'une date ; trois suites de 2 chires séparées par des `.' :
1 Cmd 9 >egrep '[0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]' etudiant.c
Pour extraire les lignes de description (qui commencent par /* ou *) :
1 Cmd 10 >egrep '^[ /]\*' etudiant.c
2 /* Fichier : etudiant.c
3 * Description : module de traitement d'un enregistrement typeEtudiant.
4 ...
5 */
et pour extraire les lignes qui se terminent par un commentaire (éventuellement suivi d'espaces) :
1 Cmd 11 >egrep '\*/ *$' etudiant.c
2 */
3 #include"etudiant.h" /* inclut personne.h */
4 ...
Si on ne désire pas les lignes qui contiennent */ uniquement, l'expression suivante est insusante car
elle exclut complètement les espaces :
1 Cmd 12 >egrep '^[^ ]+\*/ *$' etudiant.c
par contre :
1 Cmd 13 >egrep '^.*[^ ]+.*\*/ *$' etudiant.c
2 #include"etudiant.h" /* inclut personne.h */
veut dire : une ligne où quelque part il y a quelque chose autre que (espace).
Options
Parmi les commandes qui jouent le rôle de ltres citons : grep (dont egrep (cf. 1.5.2) est une variante),
tail (achage d'une partie d'un chier), sort, wc, tr (traduction de caractères), sed (éditeur de texte
non interactif) et awk qui supporte un véritable langage de programmation, proche du C, pour la
manipulation des chiers texte.
Sortie1 Entrée2
Entrée 1 Filtre2 Sortie 2
Filtre
1 Tube
Entrée standard Entrée Sortie Sortie Standard
du tube du tube
Les ltres sont particulièrement intéressants lorsqu'ils sont enchaînés à l'aide de tube s. La gure 1.3
schématise la commande composée : filtre1 | filtre2
Par exemple : ls | wc -w compte le nombre de chiers dans le répertoire courant (l'option -w
(Words ) spécie le comptage de mots uniquement). Notons que, dans ce cas-là la première commande
(ls) n'est pas un ltre ce qui ne change rien au fonctionnement du tube.
Introduction à l'algorithmique en C/Unix
c M. Alissali, Université du Maine 1998
20 Chapitre 1 Introduction àUnix
La commande cat
La commande cat est un cas typique des ltres. Comme on peut le voir dans la version simpliée
proposé dans ce document (cf. 2.4.3), son fonctionnement de base est très simple : sans arguments il
ache à l'écran (sortie standard) ce qui est saisi au clavier (entrée standard). Avec un ou plusieurs
arguments, il ache les contenus des chiers à l'écran.
cat [ -nbsuvet ] [fichier...]
Quelques options
-n Précéder chaque ligne par son numéro.
-b Pareil que le précédent mais ne pas numéroter les lignes blanches (vides).
-v Acher un code pour les caractères non imprimables.
L'exercice suivant illustre quelques unes des utilisations possibles de cette commande.
Exercice
Sachant que le caractère D (ctrl-D) signie n de saisie lors d'une saisie au clavier, commenter
la séquence de commandes qui suit.
1 <harpo.alissali: 1>set prompt = 'Cmd. no. ! > '
2 Cmd. no. 2 > mkdir Annuaire
3 Cmd. no. 3 > cd Annuaire
4 Cmd. no. 4 > ls -l
5 total 0
6 Cmd. no. 5 > cat > an1
7 Dupond 0123456
8 ^D
9 Cmd. no. 6 > cat an1
10 Dupond 0123456
11 Cmd. no. 7 > cat > an2
12 Durans d 9876549
13 Martin 1111222
14 Bertrand 9998887
15 ^D
16 Cmd. no. 8 > cat an1
17 Dupond 0123456
18 Cmd. no. 9 > cat an1
19 Meyer 55544433
20 ^D
21 Cmd. no. 10 > cat an* > an
22 cat: input/output files 'an' identical
23 Cmd. no. 11 > cat an
24 Dupond 0123456
25 Meyer 5554433
26 Durand 9876549
27 Martin 1111222
28 Bertrand 9998887
29 Cmd. no. 12 > sort an
30 Bertrand 9998887
31 Dupond 0123456
32 Durand 9876549
33 Martin 1111222
34 Meyer 5554433
35 Cmd. no. 13 > sort an1 an2 > anTrie
36 Cmd. no. 14 > cd ..
37 Cmd. no. 15 > rmdir Annuaire
38 rmdir: directory "Annuaire": Directory not empty
39 Cmd. no. 16 > rm -r Annuaire
Si valeur est composée de plusieurs mots elle doit être délimitée par des parenthèses. La valeur peut
être le résultat de l'expansion de nom de chier ou d'une commande, dans ce dernier cas la commande
doit être délimité par des guillemets simples (' ').
Le script annuRech.csh utilise la commande set une seule fois ; pour dénir la variable repertoires
et l'initialiser à la liste des répertoires où on veut eectuer la recherche.
La forme :
unset pattern
permet de supprimer toutes les variables dont le nom est mis en correspondance (d'après les règles
de substitution de nom de chier) avec pattern.
Exemples
1 (alissali@lola) ~>csh
2 (alissali@%m) %~>set prompt = 'cmd ! >'
3 cmd 2 >set message = (C-shell est un interprete de commandes)
4 cmd 3 >echo $message
5 C-shell est un interprete de commandes
6 cmd 4 >set message[3] = mon
7 cmd 5 >echo $message
8 C-shell est mon interprete de commandes
9 cmd 6 >echo $message[-3]
10 C-shell est mon
11 cmd 7 >echo $message[4-6]
12 interprete de commandes
13 cmd 8 >set fichPers = pers*
14 cmd 9 >echo $fichPers
15 personne.c personne.h personneTest.c
16 cmd 10 >set fichEtd = `ls etu*`
17 cmd 11 >echo $fichEtd
18 etudiant.c etudiant.h etudiantTest.c
19 cmd 12 >echo $#fichEtd
20 3
21 cmd 13 >echo $#message
22 6
23 cmd 14 >unset mes*
24 cmd 15 >echo $message
25 message: Undefined variable.
!{chaine1}chaine2 rappelle la commande la plus récente qui contient chaine, y ajouter chaine2.
Lors d'un rappel de commande, on peut eectuer des modications à l'aide de l'opérateur : sur une
variable ou un nom de chier. Un modicateur permet de déterminer la modication à eectuer, le
tableau 1.3 résume les modicateurs les plus intéressants.
Modificateur Signication
r Supprimer un suxe de la forme `.xxx', laissant le nom sans extension.
e Supprimer tout sauf le suxe, laissant l'extension.
s/l/r/ Substituer la chaîne `l'par la chaîne `r'.
& Répéter la dernière substitution.
g modication générale (toute occurrence), préxe un autre modicateur (p.e. gr).
p imprimer la nouvelle commande sans l'exécuter.
Tab. 1.3 Modicateurs de rappel de commande et de nom de chier
Sauf s'il est précédé de `g' un modicateur est appliqué une seule fois. Les modicateurs sont parti-
culièrement utiles dans les rappels de commandes (opérateur !!) et lorsqu'ils sont appliqués à des noms
de chiers.
1 (alissali@lola) ~>csh
2 (alissali@%m) %~>set prompt = 'Cmd ! >'
3 Cmd 2 >history
4 Cmd 3 >set history=15
5 Cmd 4 >history
6 3 set history=15
7 4 history
8 Cmd 5 >pwd
9 /export/home/ens/cuc/alissali
10 Cmd 6 >cd pub/BDU
11 Cmd 7 >!p
12 pwd
13 /export/home/ens/cuc/alissali/pub/BDU
14 Cmd 8 >!4
15 history
16 3 set history=15
17 4 history
18 5 pwd
19 6 cd pub/BDU
20 7 pwd
21 8 history
22 Cmd 9 >ls -l etudiant.c
23 -rw-r--r-- 1 alissali cuc 298 Dec 10 18:01 etudiant.c
24 Cmd 11 >set fichier="etudiant.c"
25 Cmd 12 >ls -l $fichier:r
26 UX:ls: ERROR: Cannot access etudiant: No such file or directory
27 Cmd 13 >ls -l $fichier:r.h
28 -rw-r--r-- 1 alissali cuc 240 Dec 10 18:23 etudiant.h
29 Cmd 15 >!ls:p
30 ls $fichier:r.h
31 Cmd 16 >ls -l etudiant.c
32 -rw-r--r-- 1 alissali cuc 298 Dec 10 18:01 etudiant.c
33 Cmd 18 >ls -l etu*.?
34 -rw-r--r-- 1 alissali cuc 298 Dec 10 18:01 etudiant.c
35 -rw-r--r-- 1 alissali cuc 240 Dec 10 18:23 etudiant.h
36 -rw-r--r-- 1 alissali cuc 110 Dec 10 18:01 etudiantTest.c
37 Cmd 19 >!!:s/etu/date/
38 ls -l date*.?
39 -rw-r--r-- 1 alissali cuc 1092 Dec 10 18:01 dates.c
40 -rw-r--r-- 1 alissali cuc 127 Dec 3 18:48 dates.h
41 -rw-r--r-- 1 alissali cuc 92 Dec 10 18:01 datesTest.c
42 Cmd 20 >ls -l etudiant.c etudiant.h
le tableau 1.4 présente quelques expressions sur les variables particulièrement intéressantes pour les
scripts C-Shell.
$< expression remplacée par une ligne de l'entrée standard (le clavier).
$?var
${?var} une expression qui vaut 1 si var est déni, 0 sinon.
$n Trois expressions équivalentes.
${n} Désignent le ne argument (paramètre) de l'appel du script.
$argv[n] L'argument d'indice 0 est le nom du chier qui contient le script.
$* Deux expressions équivalentes.
$argv[*] Désignen tous les arguments (paramètres) du script.
Tab. 1.4 Expressions spéciques sur les varaibles en C Shell
Dans annuRech.csh les opérateurs == et != sont utilisés pour tester la valeur de la variable prédénie
status . La valeur de cette variable est automatiquement mise à jour par le C shell pour indiquer l'état
de terminaison (c.à.d. la valeur de retour) de la dernière commande exécutée (voir par exemple la
commande exit dans le tableau 1.8 ci-après).
Les expressions du tableau 1.6 eectuent des tests sur chiers . Chaque expression retourne true (ou
1) si elle est vériée, false (ou 0) sinon.
Test Valeur vraie si...
-r nomFichier le chier est accessible en lecture.
-w nomFichier le chier est accessible en écriture.
-x nomFichier le chier est accessible en exécution
(ou en recherche pour un répertoire).
-e nomFichier nomFichier exists.
-o nomFichier nomFichier appartient à l'utilisateur.
-z nomFichier nomFichier est vide (taille 0).
-f nomFichier nomFichier est un chier ordinaire.
-d nomFichier nomFichier est un répertoire.
Si nomFichier n'existe pas ou est inaccessible tous ces tests retournent false.
Le premier if dans annuRech.csh teste si la valeur de la variable fichier est un nom de chier
ordinaire accessible en lecture.
1.6.5 Commandes internes (built-in )
Les deux tableaux suivants résument les commandes internes les plus importantes du C Shell : les
commandes de contrôle d'exécution sont groupées dans le tableau 1.7, et les autres commandes dans le
tableau 1.8.
Quelques restrictions syntaxiques s'appliquent aux commandes du tableau 1.7, en particulier chaque
occurrence de foreach, while et end doit paraître seule (ou avec expr le cas échéant) sur sa ligne.
Chaque occurrence de if...then, else et switch, doit être le premier mot (hormis les espaces blancs)
sur sa ligne.
1.6.6 Analyse du script annuRech.csh
Le script annuRech.csh recherche une liste de mots, donnés en paramètres à l'appel du script, dans
les chiers textes des répertoires désignés par la variable repertoires, dénie et initialisée en début du
script.
La boucle foreach est utilisée trois fois :
rep prend les valeurs successives de la liste des répertoires (variable repertoires) ;
mots prend les valeurs successives des paramètres d'appel du script (expression $*, cf. tableau 1.4),
ce sont les mots à rechercher ; et
fichier prend successivement tous les noms des chiers du répertoire courant.
On remarque que la commande grep est référencée de deux manières diérentes : la première fois
par un chemin d'accès absolu et la deuxième fois simplement par son nom. L'explication est qu'il peut y
avoir plusieurs versions d'une même commande (ce qui est habituellement indiquées clairement dans la
man page), l'utilisation du chemin d'accès absolu permet d'imposer l'exécution du programme (version)
désigné au lieu de suivre le schéma habituel de recherche de commande (cf. 1.3). tel est le cas pour le
deuxième appel de grep, et où la version utilisée dépend de la valeur de la variable path (cf. 1.6.2).
la variable status est testeé après chaque exécution de grep ; une valeur diérente de 0 indique que
la recherche a échoué (cf. man page de grep). Après le deuxième test, on ache un message d'erreur
en cas d'échec, on pourrait aussi ajouter une commande exit mais dans ce cas-là il faut utiliser la
commande if...then puisqu'il ne s'agit plus d'une commande simple. C'est le cas pour les deux autres
tests qui portent chacun sur plusieurs commandes. Le premier teste si le chier existe et s'il est accessible
en lecture. Le deuxième teste la valeur de retour de la commande de la ligne précédente. Il s'agit d'une
commande composée à l'aide d'un tube qui permet de vérier que fichier est un chier texte.
L'ensemble peut donc se lire : pour chaque rep de la liste repertoires, pour chaque mot de la liste
d'arguments et pour chaque fichier (texte accessible en lecture) dans le répertoire courant (rep après
l'exécution de cd), acher les lignes qui contiennent le mot courant (commande grep).
Introduction à l'algorithmique en C/Unix
c M. Alissali, Université du Maine 1998
26 Chapitre 1 Introduction àUnix
Commande Description
if (expr) commande commande est une commande simple, elle est exécutée si expr est vraie.
if (expr1) then Une seule séquence de commandes, qui apparaissent à la place des ...
... est exécutée selon les valeurs des expri.
else if (expr2) then
... if doit être seul sur sa ligne, ou suivre un else.
else endif et else doivent être les premiers mots sur leurs lignes respectives.
...
endif
switch (chaine)
case label: Les labels sont des chaînes de caractères qui peuvent utiliser
... les méta-caractères de nom de chier *, ? et [...].
breaksw L'exécution commence au premier label qui se met en correspondace
... avec chaine et se termine par breaksw ou, à défaut, par endsw.
default: Si aucun label ne correspond à chaine c'est la séquence qui suit default
... (s'il est présent) qui est exécutée.
breaksw
endsw
while (expr) Boucle tant que expr est vraie (valeur diérente de zéro)
... break et continue peuvent aussi être utilisés pour terminer la boucle.
end
foreach var (listeMots) Les commandes contenues dans le corps de la boucle sont exécutées
... pour chaque mot de listeMots. La variable var prend
end pour valeur le mot courant pour chaque itération.
continue peut être utilisé pour interrompre l'itération en cours
break peut être utilisé pour terminer la boucle.
repeat nombre commande Répète commande nombre fois.
Les mêmes restrictions que pour if s'appliquent.
goto label Continue (ou reprend) l'exécution après label qui est une chaîne caractères
à laquelle s'appliquent les expansions de commande et de nom de chier.
break Continue l'exécution après la boucle while ou foreach la plus proche.
Les commandes restantes sur la même ligne que break sont exécutées.
continue Continue l'exécution avec la prochaine itération de la boucle while
ou foreach la plus proche.
Tab. 1.7 Commandes interne de contrôle du C Shell
Commande Description
@ [ var =expr ] Aectation du résultat de l'évaluation de expr à (resp. au ne mot de) var.
@ [ var[n] =expr ] Dans expr, les opérateurs du tableau 1.5 sont autorisés, mais ceux susceptible
d'être interprétés par le C-Shell (>, <, &, et |) doivent paraître dans
des parties parenthésées de l'expression.
cd [ dir ] Positionne le répertoire courant (ou répertoire de travail ) du shell à dir (HOME
chdir [ dir ] par défaut). Si dir est un chemin d'accès relatif et s'il n'est pas trouvé à partir du
répertoire courant, rechercher dans les répertoires désignés par la variable cdpath.
exit [ (expr) ] Termine le (script) shell et retourne la valeur de expr ou, à défaut, de status.
exec commande Exécute commande à la palce du shell courant qui se termine.
Tab. 1.8 Autres commandes interne du C Shell
1.6.7 Exercice
Les questions suivantes ont pour but d'écrire un script qui permet de tester si, pour un nom donné,
le chier correspondant a un autre lien dans le même rérertoire. La ou les commandes à utiliser (le
cas échéant) sont notées en début de ligne. Il faut pour chaque question trouver les arguments et les
options appropriées ainsi que l'enchaînement lorsqu'il s'agit d'une commande composée. Faire des essais
sur machine jusqu'à obtention du bon résultat.
1o ln : créer un lien, nouveau de l'un des chiers existants (par exemple combin.c).
2o ls : acher à l'écran la liste des chiers du répertoire courant avec le numéro de i-node de chaque
chier.
3o Répéter la question précédente pour obtenir le même résultat mais pour un seul chier.
4o cut : à l'aide d'un tube acheminer la sortie de la commande précédente vers l'entrée de la
commande cut de manière à obtenir le numéro de i-node seul.
5 set, opérateur `...` : modier la commande précédente pour aecter le résultat à la variable C
o
Shell noInode.
6o Répéter la question précédente pour sauvegarder le numéro de i-node du chier nouveau dans
la variable C Shell noInodeRech.
7o if, echo : écrire une instruction conditionnelle qui ache le message nouveau est un lien si
noInode est égal à noInodeRech.
8o foreach : écrire une boucle qui exécute les commandes obtenues en réponse aux questions 3, 4,
5 et 7 pour chacun des chiers du répertoire courant.
1.7 Processus
On appelle processus l'unité élémentaire d'exécution, c.à.d l'unité de traitement dans le sens dyna-
mique du terme. Unix supporte l'exécution simultanée d'un nombre théoriquement illimité de processus,
dont chacun est identié par un identicateur unique appelé PID (Processus IDentier ).
Un processus est une entité vivante qui exécute la tâche décrite par un programme, habituellement
sauvegardé dans un chier binaire exécutable. Un tel chier résulte de la compilation et de l'édition
de liens d'un programme écrit dans un langange de programmation quelconque. Il contient les instruc-
tions à exécuter ainsi que les descriptions des données statiques (allouées au préalable) et des données
dynamiques (allouées à l'exécution).
1.7.2 Exemples
Démarrage du système Unix
Lors du démarrage de la machine, une procédure spéciale est appliquée pour créer un processus
spécial, nommé init et ayant comme identicateur 1, qui devient l'ancêtre de tous les autres processus.
Ce schéma permet au système de maintenir une structure hiérarchique unique pour tous les processus.
Par exemple, init crée un processus ls, appelé getty pour chaque terminal reconnu par la machine.
Ce processus attend les tentatives de connexion (login et mot de passe ) des utilisateurs. Lorsqu'un
utilisateur est admis, le processus getty est remplacé par un processus shell. Celui-ci appartient à
l'utilisateur, il sera l'ancêtre de tous les processus créés par l'utilisateur durant la session.
Le programme afficheId
Ce programme, donné en exemple pour illustrer la compilation séparée (cf. 2.5.3), ache le PID du
processus qui exécute le programme ainsi que celui de son père. Il ache aussi l'identité de l'utilisateur
et celle de son groupe. On remarque que seul le PID du processus lui-même est modié, son père étant
le shell interactif à partir duquel toutes les exécutions sont lancées.
1.7.3 Processus et langage C
Préparation des arguments d'appel
Dans un programme en langage C la fonction main peut accepter deux paramètres, dans ce cas-là
son prototype (ou signature) est le suivant :
1 int main(int argc, char *argv[]);
argv désigne les arguments d'appel du programme à l'exécution, c.à.d la liste des mots utilisée pour
invoquer le programme, y compris le nom du programme lui même. argc est le nombre des éléments de
cette liste, chaque entrée de argv pointe sur un de ces arguments (sous forme de chaîne de caractères).
Exemple
Le programme suivant ache à l'écran son nom d'appel puis, dans une boucle, ses arguments.
argProg.c
1 #include<stdio . h>
2
3 int main( int argc , char argv [ ])
4 f
5 int i ;
6
7 p r in t f ( "Programme : %s , nombre d ' arguments : %d . n n" , argv [ 0 ] , argc ) ;
8 for ( i = 1 ; i < argc ; i ++)
9 p r in t f ( "argument no. %d : %s n n" , i , argv [ i ] ) ;
10
11 return ( 0 ) ;
12 g
Et voici quelques résultats d'exécution, notons en particulier la création et l'appel du programme à
-p proclist acher des informations sur les processus dont les PIDs sont donnés dans proclist.
-u uidlist acher des informations sur les processus dont les UIDs eectifs sont donnés dans uidlist.
La commande kill
kill [ -signal ] pid...
kill -l
La commande kill fait partie des commandes standards d'Unix, mais elle est aussi implantée
(comme commande interne) par les diérents shells. Elle permet d'envoyer un signal à un processus.
Les signaux disponibles peuvent être achés à l'écran avec l'option -l. Un signal est identié par son
nom ou par son numéro. Parmi les signaux les plus utilisés notons KILL, BUS et SEGV (respectivement
numéros 9, 10 et 11). Le premier permet de tuer le processus numéro pid. Les deux autres sont
souvent rencontrés lors d'une erreur d'exécution de programme : dans ce cas-là un tel signal est envoyé
par le noyau pour tuer le processus en achant un message (respectivement Bus error et Segmentation
fault).
Le langage C
2.1 Le langage C en bref (Rappels)
2.1.1 Variables
Une variable est dénie par un triplet : nom, type (cf. 2.1.2) et valeur. Le nom et le type sont indis-
pensables, ils sont fournis par une dénition de variable (et rappelés si nécessaire par une déclaration).
Une variable sert à stocker et à récupérer des valeurs constantes (cf. 2.1.3) de son même type. Le
langage C n'impose pas d'attribuer une valeur à une variable, mais une variable dont on ne fait pas
varier la valeur ne sert pas à grand-chose. La valeur d'une variable est initialement indénie, elle peut
varier au cours de l'exécution : l'opérateur d'aectation permet l'attribution d'une nouvelle valeur à une
variable.
Une aectation qui a lieu à la dénition de la variable est appelée initialisation).
L'utilisation (l'accès ou la référence) permet de récupérer sa valeur (par exemple dans la partie droite
d'une aectation). Le résultat d'une référence à une variable dont la valeur est indénie est imprévisible.
2.1.2 Types
types simples
Un type simple est déni par le langage. Chaque type peut être accompagné d'un ou plusieurs
qualicatifs de type qui en modie la nature (taille, signe, type de stockage, etc.).
Type mot-clef signe taille défaut
entier int signed/unsigned short/long signé >= 16 bits
réel float double/long double
caractère char signed/unsigned 8 bits signé ou non
Tab. 2.1 Types simples du langage C.
Il n'existe pas de type booléen en C. Une expression booléenne (logique) est évaluée en type entier,
la valeur est traitée comme la valeur logique FAUX, toute autre valeur (diérentes de 0), elle est traitée
comme la valeur logique VRAI.
Types composés
Un type composé (tableau (cf. 2.3.1), enregistrement (cf. 2.3.3), etc.) est créé par le regroupement
d'éléments de type simple ou composé. Les type composés seront discutés plus en détails plus loin
(cf. 2.3.4).
2.1.3 Valeurs constantes
En langage C les constantes sont typées, on déduit le type d'une constante d'après sa forme ou son
utilisation :
de type entier :
qualication/type constante commentaire
int 12234 entier normal .
long int 123456789L une grande valeur (se termine par 'L').
unsigned int 456U un entier non signé (se termine par 'U').
31
32 Chapitre2. Le langage C
de type caractère :
constante description
'c' caractère normal .
'\n' caractère spécial (commence par \) ayant un sens spécique : ici le retour-chariot.
'\013' caractère déni par son code ASCII en octal (commence par \0).
'\xb0f1' caractère déni par son code ASCII en hexadécimal (commence par \x)
Vu que la taille du stockage est ni, les valeurs numériques autorisées ont des limites inférieure et
supérieure. Ces limites sont dépendantes du compilateur et peuvent donc varier d'une machine à l'autre.
Elles sont dénies dans les chiers limit.h et float.h (sous Unix normalement dans le répertoire
/usr/include).
Les caractères sont codés par des valeurs entières et dénis par une table de codage (appelé table
ASCII ) qui dénit la correspondance caractère-code.
2.1.4 Aectation
Opérateur simple : =
Opérateurs composés : += -= *= /= etc.
Exemple : a += b est équivalent à a = a + b
2.1.5 Opérateurs
Un opérateur permet d'eectuer une opération sur un type donné. Les opérations suivantes s'ap-
pliquent à tous les types simples (y compris les caractères même si cela peut paraître absurde). D'autres
opérateurs ont des fonctions plus spéciques en fonction du type de donnée (l'indexage pour les tableaux,
l'indirection pour les pointeurs, la sélection pour les enregistrements, etc.), ils seront présentés lorsque
nous parlerons de ces constructions.
Arithmétiques
Les opérateurs arithmétiques (+, *, -, /, ...) habituels sont reconnus du langage C. Cependant,
puisqu'il s'agit d'un langage typé il faut bien noter que une division entière, par exemple, est diérente
d'une division réelle. Dans les expressions mixtes (entiers et réel par exemple), une conversion de type
est opérée avant de déterminer le type de l'opération.
Comparaisons
Les opérateurs de comparaison sont : == != >= <= >< .
Logiques
Et logique : &&.
Ou logique : ||.
Négation : !.
Les opérateurs de comparaison et les opérateurs logiques rendent une valeur entière selon la règle de
simulation des booléens par des entiers (cf. 2.1.2). Cette valeur est 0 si le résultat de la comparaison ou
de l'expression logique est FAUX, et diérent de 0 sinon. Cette valeur est directement utilisable dans les
instructions conditionnelles (cf. 2.1.7) et les boucles (cf. 2.1.7).
2.1.6 Expressions
Une expression est une construction qui met en ÷uvre des opérateurs, des variables, des valeurs
(constantes) et des appels de fonctions. Les parenthèses peuvent être utilisées pour forcer les priorités
des opérateurs. Par exemple :
1 ( i 3 ) && ( j 3 somme( k, l ) )
On remarque le mélange d'opérations arithmétiques et logiques, ce qui est parfaitement correct en
C puisque les valeurs logiques sont simulées par les entiers. Quant au sens ou à l'utilité d'une telle
expression...
Introduction à l'algorithmique en C/Unix
c M. Alissali, Université du Maine 1998
2.1 Le langage C en bref (Rappels) 33
L'évaluation d'une expression consiste à calculer sa valeur. Dans le cas de l'exemple, en supposant que
la fonction somme calcule la somme de ses arguments et que les variables i, j, k et l ont respectivement
les valeurs 2, 3, 4 et 5, cette expression s'évalue comme suit (en respectant les priorités) :
1 (i-3) -> -1
2 j*3 -> 9
3 somme(j,k) -> 9
4 j*3 - somme(k,l) -> 0
5 -1 && 0 -> 0
La valeur de l'expression est donc 0. Notons que la dernière étape est interprétée comme VRAI ET FAUX,
puisque -1 est diérent de 0 même s'il s'agit d'une valeur négative, d'où la valeur FAUX (0). Dans le cas
contraire, si la valeur était VRAI, la seule chose qu'on aurait pu armer est que la valeur de l'expression
est diérente de 0.
Il existe une forme spéciale des expression : l'expression conditionnelle :
expr_bool ? expr_vrai : expr_faux
Si le résultat de l'évaluation de l'expression expr_bool est égale à (resp. diérente de) 0, la valeur
de l'expression conditionnelle est celle de expr_faux (resp. expr_vrai).
Malgré l'attrait que peut représenter ce type d'expression dans certain cas(éviter une instruction
conditionnelle) , son utilisation est fortement déconseillée.
2.1.7 Instructions
Une instruction simple est une expression se terminant par un ';'. Par exemple :
1 i 3;
2 j;
3 f ();
4 somme( k, 4 ) ;
Les deux premières instructions sont correctes mais complètement inutiles car elle rendent des
valeurs qui ne sont pas utilisées. La troisième instruction est un appel de fonction (cf. 2.1.9) qui
ne rend pas de valeur (ou qui rend une valeur qu'on n'exploite pas). Un tel appel est utile s'il
a un eet de bord , comme la modication de la valeur d'une variable globale ou l'achage d'un
message à l'écran. En supposant que la fonction somme n'a pas d'eet de bord, l'appel de la ligne
4 est aussi inutiles que les deux premières instructions.
Une instruction composée est une séquence d'instructions entourée d'accolades ({...}) qui en
déterminent le début et la n.
Instructions conditionnelles
if (expr) instruction [else instruction]
expr est une expression entière traitée comme si elle était booléenne : la valeur 0 simule la valeur
booléenne FAUX, tout autre valeur simule VRAI.
Boucles
Une boucle est une répétition d'une instruction (simple ou composée) qui s'arrête lorsque une
condition donnée est satisfaite. Il y a trois formes de boucles en C :
while (expression)
instruction
Dans une boucle while, pour chaque itération, expression est évaluée, si sa valeur est diérente de
0 instruction est exécutée avant de commencer l'itération suivante. Il est important de s'assurer que
les variables utilisées dans expression sont bien initialisées avant la boucle et qu'elles sont mises à jour
dans la boucle de telle manière que la condition d'arrêt (expression == 0) nisse par se réaliser.
do
instruction
while (expression);
Dans le cas de la boucle do, instruction est exécutée avant d'évaluer expression. comme le cas
précédent, on passe à l'itération suivante si la valeur de expression est diérente de 0. Dans certain
cas les variables impliquées dans cette évaluation peuvent ne pas être initialisées avant la boucle, par
exemple lorsqu'elles sont saisies au clavier.
for (expr_init; expr_cond; expr_maj)
instruction
La boucle for est la plus utilisée en langage C. Elle équivaut à une boucle while avec initialisation
(expr_init) et mise à jour (expr_maj), c'à.d. :
1 expr_init ;
2
3 while ( expr_cond )
4 f
5 in s t r u c t io n
6 expr_maj
7 g
2.1.8 Lecture du langage C
Dans les cas relativement simple, la lecture d'une déclaration ou d'une instruction ne devrait pas
poser de problème. Cependant il faut s'assurer de la bonne lecture de tous les éléments. Par exemple,
en se référant à la dénition de variable, une lecture complète doit préciser le nom, le type et la valeur
de la variable, comme suit :
1 float x; /*
définition de variable ; nom : x, type float,
2 valeur non initialisée */
3 int i = 10; /* définition de variable ; nom : i, type int, valeur
4 initialisée : 10 */
Dans le cas d'une instruction ou d'une expression il faut décomposer jusqu'au niveau le plus bas. Par
exemple :
1 x = i + 1; /* 1. expression : addition (entière) de i et de 1, résultat : 11.
2 2. expression : affectation (réelle -- implique la
3 conversion du type) du résultat à x.
4 3. Le tout est une instruction (expression suivie de ';').
5 */
Dans certains cas la lecture peut ne pas être immédiate. Sachant que les constructeurs [] et () ont
une priorité égale et supérieure à celle de *, la règle suivante permet de lire les déclarations complexes :
En partant du nom de l'objet déclaré, la lecture se fait de gauche à droite sauf si, parmi les constructeurs
non encore lus, celui de gauche est prioritaire à celui de droite. Les parenthèses permettent de forcer la
priorité
Exercices
Appliquer la règle de lecture pour obtenir les résultats suivants :
1o char *t[] : t est un tableau de pointeurs de caractères.
2o int *f() : f est une fonction retournant un pointeur sur un entier.
3o int *(*f)[]() f est un pointeur sur un tableau de fonctions retournant chacune un pointeur
sur entier. Cet exercice est très complexe et mérite d'être détaillé 1 :
f est l'objet déclaré.
(*f) : à lire avant le reste car la priorité est forcée par les parenthèses ; '*' se lit pointeur :
f est un pointeur.
(*f)[] : poursuite de la lecture de gauche à droite car '[]' est prioritaire sur '*' ; '[]' se lit
tableau : f est un pointeur de tableau.
1. Nous nous intéressons ici uniquement à la lecture et non pas à l'exactitude ou l'utilité d'une telle déclaration.
(*f)[]() : poursuite de la lecture de gauche à droite car '()' est prioritaire sur '*' ; '()' se lit
fonction : f est un pointeur de tableau de fonction.
(*f)[](); : n de la déclaration à gauche (';') mais il reste des choses à droite.
*(*f)[](); : f est un pointeur de tableau de fonction de pointeur.
int *(*f)[](); : f est un pointeur de tableau de fonction de pointeur de int.
2.1.9 Fonctions
Dénition et utilisation des fonctions
Une dénition de fonction comprend une en-tête (ou signature) et un corps.
L'en-tête (ou signature) dénit la nature de la fonction, elle se compose de :
type : c'est le type de la valeur qu'elle retourne;
nom : qui permet l'appel (l'utilisation) de la fonction.
paramètres : suite de variables typés séparées par virgules et entourée de parenthèses
'(...)'.
Le corps est une instruction composée qui comprend les déclarations de variables et la sé-
quence des instructions exécutées par la fonction.
Une fonction peut être utilisée avant d'être dénie, c'est souvent le cas lorsqu'une utilise la compi-
lation séparée (cf. 2.5). Dans d'autres cas on ne dispose pas du code source (en C) de la fonction
comme c'est le cas lorsqu'on utilise les bibliothèques de fonction (cf. 2.1.10). Dans ces deux cas-là,
et à défaut d'être dénie, la fonction doit être déclarée.
Une déclaration de fonction (ou prototype) est simplement son en-tête, suivie de `;'. Elle peut
être placée dans n'importe quel bloc de déclaration de la même manière qu'une variable.
Si à l'appel le type des valeurs passées ne correspond pas au prototype, le compilateur opère une
conversion implicite des types, par exemple la fonction racine carrée de la bibliothèque math
est déclarée comme suit :
lors de l'appel :
1 int n ;
2 sqrt ( n ) ;
l'instruction réellement exécutée correspond à :
1 sqrt ( ( double ) n ) ;
Pour savoir quel chier il faut inclure (et pour obtenir toutes les informations nécessaires) pour
utiliser une fonction : utiliser la documentation en ligne, commande man. Par exemple :
Introduction à l'algorithmique en C/Unix
c M. Alissali, Université du Maine 1998
36 Chapitre2. Le langage C
man getc
produit à l'écran :
1 getc(3S) Standard I/O Functions getc(3S)
2
3 NAME
4 getc, getc_unlocked, getchar, getchar_unlocked, fgetc, getw
5 - get character or word from a stream
6
7 SYNOPSIS
8 #include <stdio.h>
9
10 int getc(FILE *stream);
11
12 int getc_unlocked(FILE *stream);
13 ....
14
15 DESCRIPTION
16 ...
17
18 RETURN VALUES
19 ...
20
21 SEE ALSO
22
23 intro(3), fclose(3S), ferror(3S), flockfile(3S), fopen(3S),
24 fread(3S), gets(3S), putc(3S), scanf(3S), stdio(3S),
25 ungetc(3S)
26
Syntaxe
type nom[taille];
Dénit un tableau à deux dimensions. Cette déntion peut être généralisée davantage pour repré-
senter des tableaux multi-dimensionnels.
Exemples
1 /* Definitions */
2 /* tableau de 10 entiers*/
3 int tabEnt[10];
4
5 /* matrice de reels */
6 float matrice[5][5];
7
8 /* tableau de caracteres initialise, la taille est calcule
9 automatiquement */
10 char tabCar[] = "universite du Maine" ;
11 ...
12
13 /* Instructions */
14 /* Affectation au 1er element (indice 0 !) */
15 tabCar[0] = 'U';
16
17 /* transposition de matrice */
18
19 for(i=0, j=0; ...)
20 {
21 matrice[i][j] = matrice[j][i];
22 }
23
24 /* Permutation de deux elements : Utiliser une variable temporaire
25 pour ne pas ecraser l'ancienne valeur */
26 sauveElt = tabEnt[5];
27 tabEnt[5] = tabEnt[8];
28 tabEnt[8] = sauveElt;
Exemple
explTableau.c
1 #include<stdio . h>
2 #include<s t r in g . h > / Manipulation des chaines de caracter e s /
3 #define N10
4 int main( void )
5 f
6 int tabEnt [N] , i ;
7 char message [ 8 ] ; / t a i l l e du message + 1 pour le ' n 0 ' /
8 for ( i=0 ; i <N; i ++) f
9 p r in t f ( " Entrez tabEnt[%d]= n n" , i ) ;
10 scanf ("%d",&tabEnt [ i ]) ;
11 g
12 / Copier une chaine constante dans le tableau message /
13 strcpy ( message , " Valeurs " ) ;
14 for ( i=N 1 ; i >=0 ; i ) f
15 p r in t f ("%s n n" , message ) ;
16 p r in t f ( " tabEnt[%d] = %d" , i , tabEnt [ i ] ) ;
17 g
18 g
2.3.2 Pointeurs
Dénition
Un pointeur est une variable qui permet de désigner (pointer sur) d'autres objets d'un type donné.
Un pointeur permet d'avoir un accès indirect à la variable pointée. Le type du pointeur est pointeur
sur le type de la variable pointée .
Syntaxe
type *nom;
Dénit un pointeur de type pointeur sur type .
Exemples
1 /* Defintions */
2 int ent1 = 4, ent2=123;
3 char car1='z', car2='w';
4
5 /* 2 pointeurs sur entier */
6 int *ptrEnt1, *ptrEnt2;
7
8 /* Def. et init. de 2 pointeurs sur caractere */
9 char *ptrCar1=&car1,
10 *ptrCar2=&car2;
11
12 /* Instructions */
13 /* ptrEnt1 pointe sur ent1 */
14 ptrEnt1 = &ent1;
15
16 /* ptrEnt2 pointe sur ent2 */
17 ptrEnt2 = &ent2;
18
19 /* ent2 prend la valeur de la variable pointee par ptrEnt1 (donc de ent1) */
20 ent2 = *ptrEnt1;
21
IMPORTANT : ne pas confondre les opérations sur les pointeurs avec les opérations sur les objets
pointés.
explPointeur.c
1 #include<stdio . h>
2 #include<s t r in g . h> / Manipulation des chaines de caracte r es /
3 #include<malloc . h> / Manipulation de la memoire dynamique /
4 #define TAILLE_CHAINE
5 int main( void )
6 f
7 char typeEtst [TAILLE_CHAINE], complement , nomComplet ;
8 int tailleNom ;
9
10 p r in t f ( "Type d ' etablissement ? n n" ) ;
11 scanf ("%s " , typeEtst ) ; / Pas besoin de l ' operateur & /
12 complement = c a llo c (TAILLE_CHAINE, sizeof ( char ) ) ;
13 p r in t f ( "Complement du nom ? n n" ) ;
14 scanf ("%s " , complement ) ; / Pas besoin de l ' operateur & /
15 tailleType = s t r le n ( typeEtst )
16 tailleNom = tailleType + s t r le n ( complement ) + 2 ;
17 nomComplet= c a llo c ( tailleNom , sizeof ( char ) ) ;
18 strcpy ( nomComplet , typeEtst ) ;
19 ( nomComplet+ tailleType ) = ' ' ; / ecrase le ' n 0 ' /
20 ( nomComplet+ tailleType + 1 ) = ' n 0 ' ; / pour le r e t a b l i r ! /
21 s t r c a t ( nomComplet , complement ) ;
22 p r in t f ( "Le nom complet est : %s n n" , nomComplet ) ;
23 g
Trace de l'exécution
typeEtst complement nomComplet
Instruction taille valeur taille valeur taille valeur
all/e all/e all/e
déclaration 11(Cst)/0 ? 0/0 NULL 0/0 NULL
scanf (1 ) 11/11 "Universite" 0/0 NULL 0/0 NULL
calloc (1) 11/11 "Universite" 11/0 ? 0/0 NULL
scanf (2) 11/11 "Universite" 11/9 "du Maine" 0/0 NULL
calloc (2) 11/11 "Universite" 11/9 "du Maine" 22/0 ?
strcpy 11/11 "Universite" 11/9 "du Maine" 22/11 "Universite"
aectations 11/11 "Universite" 11/9 "du Maine" 22/12 "Universite "
strcat 11/11 "Universite" 11/9 "du Maine" 22/22 "Universite du Maine"
Remarques
complement et nomComplet sont des pointeurs, il faut leur allouer de la mémoire dynamiquement ;
la valeur par défaut d'un pointeur est NULL, elle signie que le pointeur ne pointe sur rien ;
pour ces trois variables il n'y a pas besoin de l'opérateur & lors des appels à scanf (cf. 2.3.2) ;
la taille de nomComplet est calculée en fonction des tailles eectives des deux sous chaines (fonction
strlen) ;
dans de la dénition de la fonction : au lieu d'utiliser une variable de type T, utiliser une variable
de type pointeur de T , et modier le corps de la fonction pour utiliser l'accès indirect à la
variable ;
à l'appel de la fonction utiliser l'adresse de (ou un pointeur sur) le paramètre à modier. Toute
modication sur le pointeur restera locale, mais, grâce à l'accès indirect, la modication de la
variable pointée sera récupérée par l'appelant.
Introduction à l'algorithmique en C/Unix
c M. Alissali, Université du Maine 1998
2.3 Structure de données en C 41
Exemple
explMdfParFct.c
1 #include<stdio . h>
2
3 void ajouter5 ( int ptrEnt )
4 f
5 ptrEnt += 5 ;
6 g
7
8 int main( void )
9 f
10 int i = 10 , ptrEnt = & i ;
11
12 ajouter5 ( ptrEnt ) ;
13 p r in t f ( " Nouvelle valeur : %d ( acces d ir e c t ), %d ( acces in d ir e c t ) n n" , i , ptrEnt ) ;
14
15 / plus simple : u t i l i s e r l ' adresse de i ( pointeur sur i ) /
16 ajouter5 (& i ) ;
17 p r in t f ( " Nouvelle valeur : %d n n" , i ) ;
18 g
2.3.3 Enregistrements
Dénition
Un enregistrement est un regroupement de variables de types simples (int, char, float, etc.) ou
composés (tableau, enregistrement, etc.).
Syntaxe
struct {
type1 nom1;
type2 nom2;
...
typen nomn;} nomEnr;
Dénit un enregistrement nomEnr dont chaque membre (ou champ ) nomi est de type typei.
L'opérateur `.' permet d'accéder à un membre de l'enregistrement :
nomEnr.nomi
désigne le membre nomi (de type typei) de l'enregistrement nomEnr.
1 struct f
2 char nom[ 30 ] ; / imbrication de types composés /
3 int age ;
4 float poids ;
5 g personne1 ;
6
7 struct f
8 char nom[ 30 ] ;
9 int age ;
10 float poids ;
11 g personne2 ;
12 ...
13 strcpy ( personne1 . nom , ` ` Dupont ' ' ) ; / a t t e n t i o n au type de la données manipulée : /
14 / nom de type char [ 30 ] /
15 personne1 . age = 42 ;
Introduction à l'algorithmique en C/Unix
c M. Alissali, Université du Maine 1998
42 Chapitre2. Le langage C
struct modeleEnr {
type1 nom1;
...
typen nomn;};
dénit un modèle modeleEnr qui sert à dénir/déclarer des instances (variables) de l'enregistrement
avec une syntaxe semblable à celle de la dénition/déclaration d'une variable de type simple :
Syntaxe
typedef defType nomType
Permet de dénir un nouveau type nomType dont le domaine de valeurs est celui de defType qui
peut être simple ou composé. On peut distinguer deux cas de gure :
defType est un type simple : le nouveau type possède les mêmes opérations, mais c'est au déve-
loppeur de s'assurer de leurs bon usage. Par exemple après la déclaration :
typedef int Bool;
on peut additionner des booléens, ce qui ne correspond pas à la dénition habituelle de ce type.
defType est un type composé : on dispose des opérations sur les membres (si elles sont dénies)
mais pas nécessairement sur le nouveau type. Par exemple on peut eectuer des aectations d'enre-
gistrements mais pas de comparaisons. Dans le cas des tableaux ni l'aectation ni les comparaisons
ne sont autorisées. C'est donc au programmeur de dénir ses propres opérations.
Introduction à l'algorithmique en C/Unix
c M. Alissali, Université du Maine 1998
2.3 Structure de données en C 43
Exemple
1 / D é f i n i t i o n s /
2 / D é f i n it io n du type ( enregistrement ) pour l e s nombres complexes /
3 typedef struct f
4 float r e e l , imag ;
5 g complexe ;
6
7 / D é f i n it io n et i n i t i a l i s a t i o n de deux complexes /
8 complexe z , z1 = f 1 . , 1 . g , z2 = f 1 ., 1 . g ;
9
10 / Pour additionner deux complexes i l faut opérer sur l e s deux
11 membres de la s t r u c t u r e /
12
13 / I n s t r u c t i o n s /
14 / Addition d i r e c t e /
15 z . r e e l = z1 . r e e l + z2 . r e e l ;
16 z . imag = z1 . imag + z2 . imag ;
17
18 / opération d ' addition d é f i n i r une fonction /
19
20
21 complexe addition ( complexe z1 , complexe z2 )
22 f
23 complexe res ;
24
25 res . r e e l = z1 . r e e l + z2 . r e e l ;
26 res . imag = z1 . imag + z2 . imag ;
27
28 return ( res ) ;
29 g
30 ...
31 z = addition ( z1 , z2 ) ;
2.3.5 Structures autoréférentielles
Une structure autoréférentielle (ou structure récursive ) est une structure de données 2 qui permet
de faire référence à une autre structure de même type (voir gures 2.1 et 2.2).
Comme les tableaux d'enregistrements, ce type de structure est utilisé pour l'organisation de données
de même nature. Il présente des avantages dont la facilité d'insertion et de supression d'éléments (cf.
gure 2.3) ainsi que la possibilité de référencer plus d'un élément, comme dans le cas des arbres bi-
naires (cf. 3.10)). Par contre, toujours comparativement aux tableaux, il ne supporte pas de mécanisme
équivalent à l'indexage .
11111
00000 11111
00000
00000
11111 00000
11111
00000
11111 00000
11111
00000
11111 00000
11111
00000
11111 00000
11111
00000
11111 00000
11111
T 00000
11111 00000
11111
00000
11111 00000
11111
00000
11111 00000
11111
00000
11111
&enr2 00000
11111
NULL
00000
11111 00000
11111
T* 00000
11111 00000
11111
T enr1 T enr2
Fig. 2.1 Représentation schématique d'une
Fig. 2.2 Enchaînement de structures autoré-
structure autoréférentielle férentielles
Le programme suivant dénit la structure autoréférentielle struct compteMot qui, en plus du poin-
teur, contient deux chmaps mot et cpt. Un tel enregistrement peut servir par exemple au comptage des
mots dans un texte non connu à l'avance : un nouvel enregistrement est créé, initialisé et inséré dans la
liste. Sinon il sut d'incrémenter le compteur de l'enregistrement correspondant au mot.
L'exemple utilise une fonction d'achage et montre les opérations d'enchaînement et les deux modes
d'accès (équivalents !), direct et indirect, à un enregistrement de la liste. Des manipulations plus avancées
2. Les enregistrements, appelés structures en C, sont le meilleur (le seul?) moyen pour implanter les structures
autoréférentielle, mais il ne faut pas les confondre avec la notion générale de structure de données qui désigne toute
entités permettant une organisation particulière des données, comme les tableaux d'enregistrements et les structures
autoréférentielles.
Avant Après
sont présentées dans les sections sur les listes chaînées (cf. 3.8) et les arbres binaires (cf. 3.10).
strucRcrsv.c
Comme on l'a déjà vu dans d'autres cas, une dénition de type permet de simplier l'écriture. Une
version améliorée du programme précédent peut par exemple avoir le chier d'en-tête suivant.
Introduction à l'algorithmique en C/Unix
c M. Alissali, Université du Maine 1998
2.4 Accès aux chiers 45
strucRcrsvType.h
2.3.6 Unions
2.4 Accès aux chiers
2.4.1 Introduction
À l'exécution d'un programme le noyau d'Unix associe au processus (cf. 1.7) correspondant deux
chiers : l'entrée standard (par défaut le clavier) et la sortie standard (par défaut l'écran). Les ltres
(cf. 1.5.3) sont l'illustration typique de l'utilisation de ces deux chiers.
Tous les programmes C présentés jusqu'à présent n'utilisent que ces deux chiers. Cette section
présente la manipulation des chiers normaux (cf. 1.4.2).
2.4.2 Fonctions de manipulation de chiers
Un chier normal (ou ordinaire) est l'unité minimal de stockage sur un support permanent. Les
chiers sont gérés par le système d'exploitation (cf. 1.4) auquel il faut faire appel pour manipuler les
chiers dans un programme.
Pour accéder à un chier un programme doit d'abord l'ouvrir à l'aide de la fonction de la bibliothèque
standard fopen :
#include <stdio.h>
FILE *fopen(const char *nomFichier, const char *modAcces);
Un appel à cette fonction établit une liaison entre le nom externe (nomFichier, connu par le système)
et le programme grâce au pointeur de chier qu'elle retourne et qui doit être utilisé pour tout accès
au chier. En eet le pointeur pointe une structure de données (de type FILE, déni dans stdio.h),
dérivée de l'i-node (cf. 1.4.1) du chier, qui contient les informations nécessaires à sa manipulation.
modAcces est une chaîne de caractères qui indique les autorisations d'accès demandées : r pour la
lecture, w pour l'écriture et a pour l'ajout (écriture en n de chier sans écraser le début).
En cas d'erreur (chier inexistant, autorisations refusée, etc.) la fonction retourne NULL.
Il existe plusieurs groupes de fonctions de la bibliothèque standard pour lire ou écrire dans un chier
en C 3. Les deux fonctions fscanf et fprintf sont identiques à scanf et printf sauf qu'elles prennent
un pointeur de chier comme premier argument :
int fscanf(FILE *ptrFich, const char *format, ...);
En eet l'appel scanf("...", ...) est équivalent à fscanf(stdin, "...", ...), stdin étant un
pointeur constant de type FILE *, déni dans stdio.h et qui désigne le chier entrée standard . De
3. Toutes les fonctions décrites ici sont déclarées dans le chier stdio.h.
la même manière, printf("...", ...) est équivalent à fprintf(stdout, "...", ...), où stdout
désigne la sortie standard .
Les deux fonctions suivantes permettent respectivement de lire et d'écrire un seul caractère à la fois :
int getc(FILE *ptrFich);
getc retourne la valeur constante EOF, dénie dans stdio.h, lorsque la n de chier est atteinte ou en
cas d'erreur.
2.4.3 Exemple
Le programme suivant simule la commande cat qui ache à l'écran (stdout) les contenus des chiers
donnés en arguments (stdin par défaut).
mCat.c
Édition des liens (link ): assemblages des morceaux du programme (les codes objets), y compris les
fonctions utilisées de la bibliothèque standard , et résolution des références.
stdio.h prog.c
#define ... #include<stdio.h>
.... #include"mesFoncs.h"
#define N 10
....
i = N;
Précompilateur (cpp)
int printf(...);
....
i = 10;
Compilateur
"stdio"
Code Objet
Éditeur de liens
Exécutable
les déclarations des variables et fonctions exportées (utilisables par d'autres modules) dans un
chier d'en-tête (.h).
Dans la pratique :
un module utilisant les fonctions et/ou les variables d'un autre module doit inclure le chier
d'en-tête de celui-ci (pour avoir les déclarations) ;
les modules peuvent être compilés séparément. L'option -c du compilateur permet de s'arrêter
après la phase de compilation en générant le chier objet (.o) correspondant ;
pour obtenir l'exécutable il faut assembler l'ensembles des chiers objets (.o) par l'édition des
liens ;
Introduction à l'algorithmique en C/Unix
c M. Alissali, Université du Maine 1998
48 Chapitre2. Le langage C
2.5.3 Exemple
identite.c
1 / Fichier : i d e n t i t e . c
2 Description : contient une seule fonction , a f f i c h a g e d ' un message d ' i d e n t i f i c a t i o n .
3 Auteur : Mamoun ALISSALI
4 Historique :
5 Creation : Tue Feb 08 15 : 48 : 43 WET 1994
6 /
7 #include < stdio . h>
8 #include " id e n t it e . h"
9
10 int a f f ic h e Id ( const char message , int id )
11 /
12 afficheIdimprimeMessage : imprime ses deux argument sur la s o r t i e standard .
13 Entree : l e s arguments a imprimer : un tableau de char , et un int .
14 Retour : TRUE si OK, FALSE sinon .
15 /
16 f
17 if ( f p r i n t f ( stdout , "%s : %dn n" , message , id ) < 0 ) return (FALSE) ;
18 return (TRUE) ;
19 g / END a f f i c h e I d /
20
1 / Fichier : p r i n c i p a l . c
2 Description : module p r i n i c i p a l de progC qui a f f i c h e d i v e r s e s
3 i d e n t i f i c a t i o n s . progC sert d ' exemple a la creation d ' un programme C
4 sous Unix .
5 Auteur : Mamoun ALISSALI
6 Historique : Creation . Tue Feb 08 15 : 40 : 52 WET 1994
7 A FAIRE : Completer traitement des erreurs .
8 /
9 #include < unistd . h>
10 #include < sys / types . h>
11 #include < s t d lib . h>
12 #include < stdio . h>
13 #include " id e n t it e . h"
14 #include " p r in c ip a l . h"
15
16 int main( int argc , char argv [ ])
17 /
18 main : a p p e l l e a f f i c h e I d pour i d e n t i f i e r l ' u t i l i s a t e u r , son groupe ,
19 le processus courant et son pere .
20 Retour : 0 si OK, 1 si erreur d ' imperssion , 10 argc i n c o r r e c t .
21 /
22 f
23 if ( argc != 1)
24 f
25 f p r i n t f ( stderr , " U t ilis a t io n : %s n n" , argv [ 0 ] ) ;
26 exit ( 10 ) ;
27 g / END IF /
28
29 f p r i n t f ( stdout , "Programme : %s n n" , argv [ 0 ]) ;
30
31 if ( ! a f f ic h e Id ( " je suis l ' u t i l i s a t e u r " , getuid ( ) ) ) exit ( 1 ) ;
32 if ( ! a f f ic h e Id ( "mon groupe est " , getgid ( ) ) ) exit ( 1 ) ;
33 if ( ! a f f ic h e Id ( " je suis le processus " , getpid ( ) ) ) exit ( 1 ) ;
34 if ( ! a f f ic h e Id ( "mon pere est le processus " , getppid ( ) ) ) exit ( 1 ) ;
35
36 exit ( 0 ) ;
37 g / END main /
38
39 / __EOF__ /
40
identite.h
1 #ifndef IDENTITE_H
2 #define IDENTITE_H
3
4 #define FALSE 0
5 #define TRUE 1
6
7 extern int a f f ic h e Id ( const char msg, int id ) ;
8
9 #endif / IDENTITE_H /
10
principal.h
1 #ifndef PRINCIPAL_H
2 #define PRINCIPAL_H
3
4 #endif / PRINCIPAL_H /
5
Production de l'exécutable
Précompilation
1 {61} : cpp src/identite.c > src/tmp.c
2 cpp: Error: src/identite.c: 8: Cannot open file identite.h for #include
3 {62} : cpp -Iinclude src/identite.c > src/tmp.c
4 {63} : ls -l src
5 total 10
6 -rw-r--r-- 1 alissali thesardls 617 Feb 11 11:41 identite.c
7 -rw-r--r-- 1 alissali thesardls 1112 Feb 11 11:39 principal.c
8 -rw-r----- 1 alissali thesardls 6990 Feb 11 11:49 tmp.c
9
10 {64} : cat src/tmp.c
11 [...]
12 extern FILE _iob[8];
13 [...]
14 extern int fprintf();
15 [...]
16 # 8 "src/identite.c"
17 # 1 "include/identite.h"
18 [...]
19 extern int afficheId(const char *msg, int id);
20 [...]
21 # 9 "src/identite.c"
22
23 int
24 afficheId(const char *message, int id)
25
26 {
27 if(fprintf((&_iob[1]),"%s : %d\n", message,id) < 0 ) return(0);
28 return(1)
29 }
30
31 {65} : grep stdout /usr/include/stdio.h
32 #define stdout (&_iob[1])
33 [...]
34 {66} : !cpp:p
35 cpp -Iinclude src/identite.c > src/tmp.c
36 {67} : !!:s/-I/-D__STDC__ -D_POSIX_SOURCE -I/
37 cpp -D__STDC__ -D_POSIX_SOURCE -Iinclude src/identite.c > src/tmp.c
38 {68} : ls -l src
39 total 11
40 -rw-r--r-- 1 alissali thesardls 617 Feb 11 11:41 identite.c
41 -rw-r--r-- 1 alissali thesardls 1112 Feb 11 11:39 principal.c
42 -rw-r----- 1 alissali thesardls 4881 Feb 11 12:20 tmp.c
43
44 {69} : cat src/tmp.c
45 [...]
46 extern int fprintf(FILE *, const char *, ...);
47 [...]
Compilation
A. Vérication de la syntaxe
principal.h identite.h
#ifndef ... #ifndef ...
#define #define
#endif #endif
principal.c identite.c
#include"identite.h" #include"identite.h"
#include"principal.h"
src
int main(...)
{ int afficheId(...)
... {
afficheId(...); ...
... }
}
Précompilation
17 total 49
18 -rwxr-xr-x 1 alissali thesardls 26720 Feb 08 16:51 a.out
19 -rw-r--r-- 1 alissali thesardls 618 Feb 08 16:44 identite.c
20 -rw-r--r-- 1 alissali thesardls 1011 Feb 08 16:26 principal.c
21 -rw-r--r-- 1 alissali thesardls 6008 Feb 08 16:50 principal.o
22 -rw-r--r-- 1 alissali thesardls 7272 Feb 08 16:46 tmp.c
23 -rw-r--r-- 1 alissali thesardls 2768 Feb 08 16:46 tmp.o
Exemples d'exécution
1 {97} bin/progC
2 Programme : bin/progC
3 je suis l'utilisateur : 10396
4 mon groupe est : 1010
5 je suis le processus : 1633
6 mon pere est le processus : 1210
7 {98} !!
8 bin/progC
9 Programme : bin/progC
10 je suis l'utilisateur : 10396
11 mon groupe est : 1010
12 je suis le processus : 1634
13 mon pere est le processus : 1210
14 {99} bin/progC1
15 Programme : bin/progC1
16 je suis l'utilisateur : 10396
17 mon groupe est : 1010
18 je suis le processus : 1635
19 mon pere est le processus : 1210
Compilation
Vérification correction
de la syntaxe des erreurs
Exécutable
Fig. 2.6 Compilation et d'édition des lien des deux modules de l'exemple.
3.2.2 Conception
1 entier N, i , somme
2 écrire " Donner N"
3 lire somme
4
5 si N $ n leq$ 0 alors ERREUR
6 pour i < 1 à N faire
7 somme < somme + i
8 écrire " La somme est :a : " somme
9
3.2.3 Implantation
sommeNentiers.c
1 #include<stdio . h>
2
3 int main( void )
4 f
5 int n , i , somme = 0 ;
6
7 p r in t f ( "Donner n : n n" ) ;
8 scanf ("%d" , & n ) ;
9 if ( n <= 0 )
10 f
11 p r in t f ( " n doit etre > 0 ! n n" ) ;
12 return ( 1 ) ; / code erreur /
13 g
14 for ( i = 1 ; i <=n ; i ++)
15 somme += i ;
16 p r in t f ( "Somme : %d n n" , somme) ;
17 return ( 0 ) ; / bon fonctionnement /
18 g
19
3.2.4 Test
<harpo.alissali: 63>sommeNentiers
Donner n :
-1
n doit etre > 0 !
<harpo.alissali: 64>!!
sommeNentiers
Donner n :
7845
Somme : 30775935
<harpo.alissali: 65>!!
sommeNentiers
Donner n :
1
Somme : 1
Implantation et tests
Exercice
Complexité
données : un tableau de N entiers + 3 entiers
nombre d'opérations : dépend des données (on considère uniquement les comparaisons)
meilleur cas : 1 comparaison (complexité O(1)) ;
pire cas : N comparaisons (complexité O(N)) ;
moyenne : (1 + 2 + .... + N)/N = N(N+1)/2N = (N+1)/2 (complexite O(N/2)).
Ranement :
condition d'arrêt : taille du tableau 1, deux cas :
taille = 0 ) non trouve, taille = 1 ) tester ;
les limites du tableau (deb et n) sont des arguments de la fonction.
Conception
1 entier rechDicho ( entier valRech , entier tabEnt [ ] , entier deb , entier fin )
2 entier mil = ( deb+fin )/ 2
3 début
4 si deb > fin alors
5 retour NONn_TROUVE
6 si valRech = tabEnt [ mil ] alors
7 retour mil
8 si valRech < tabEnt [ mil ] alors
9 retour rechDicho ( valRech , tabEnt , deb , mil 1 )
10 sinon
11 retour rechDicho ( valRech , tabEnt , mil+1 , fin )
12 fin
Complexité
À chaque étape :
1 addition, 1 division, entre 1 et 3 tests et un appel récursif.
division par 2 de de la taille du tableau ) log2 (n) étapes.
Ordre de complexité : O(log2 (n)).
Coût en nombre d'opérations
taille recherche séquentielle recherhce dichotomique
16 8 4
1024 512 10
220 219 20
Implantation
rechDichRec.c
1 int rechDichRec ( int valRech , int tabEnt [ ] , int deb , int fin )
2 f
3 int mil = ( deb+fin )/ 2 ;
4
5 if ( deb > fin ) return ( 1 ) ;
6 if ( valRech == tabEnt [ mil ] ) return mil ;
7 if ( valRech < tabEnt [ mil ])
8 return rechDichRec ( valRech , tabEnt , deb , mil 1 ) ;
9 return rechDichRec ( valRech , tabEnt , mil+1 , fin ) ;
10 g
rechDichRec.h
1 int rechDichRec ( int valRech , int tabEnt [ ] , int deb , int fin ) ;
rechDichIt.h
Tests
rechDichTest.c
1 #include<stdio . h>
2 #include " rechDichRec . h"
3 #include " rechDichIt . h"
4 int main( void )
5 f
6 int valRech , mil , indice , tabEnt []= f 2 , 6 , 8 , 11 , 17 , 18 , 22 , 45 , 102 g ;
7 do f
8 p r in t f ( " valeur recherhcee ? n n" ) ;
9 scanf ("%d" , & valRech ) ;
10 if ( valRech != 1 ) f
11 p r in t f ( " rechDichRec :" ) ;
12 indice = rechDichRec ( valRech , tabEnt , 0 , 8 ) ;
13 if ( indice == 1 ) p r in t f ( " valeur non trouvee n n" ) ;
14 else p r in t f ( " valeur trouvee a l ' indice %d n n" , indice ) ;
15 p r in t f ( " rechDichIt :" ) ;
16 indice = rechDichIt ( valRech , tabEnt , 9 ) ;
17 if ( indice == 1 ) p r in t f ( " valeur non trouvee n n" ) ;
18 else p r in t f ( " valeur trouvee a l ' indice %d n n" , indice ) ;
19
20 g
21 g while ( valRech != 1 ) ;
22 g
Pour une validation minimale de l'algorithme, on peut par exemple eectuer le test dans les cas
suivants :
limites du tableau : 2 et 102 ;
milieu du tableau : 11, 17, 18 ;
Introduction à l'algorithmique en C/Unix
c M. Alissali, Université du Maine 1998
3.6 Algorithmes de tri de tableau 61
Principe
1 Ranger le premier élément
2 Trier le r e s t e du tableau
Analyse
1.1. Rechercher le plus petit élément : ppe
1.2. Permuter avec le premier élément du tableau
2. Trier le tableau à partir de l'élément suivant Données:
1 Entrées :a : tableau à t r i e r tabEnt ,
2 t a il l e du tableau ,
3 indice du premier élément à t r a i t e r .
4 S o r t ie s :a : tableau t r ié
5 Locales :a : plus p e t it élément et son indice
Traitement:
1 1 . 1 . boucle :a : trouver ppe
2 1 . 2 . permuter ppe et 1er élément
3 2 . t r i e r à p a r t ir de l ' élément suivant
Conception
1 n la b e l f :a a l g :triDirect :triDirectg
2 entier t r iDir e c t ( entier tabEnt [ ] , entier t a i l l e , entier indDeb )
3 entier ppe , indppe , ;a i ; ;
4 début
5 ppe < tabEnt [ indDeb ]
6 pour i < indDeb+1 à t a i l l e 1 faire
7 début
8 si ppe > tabEnt [ i ] alors
9 début
10 indppe < i
11 ppe < tabEnt [ i ]
12 fin
13 fin
14
15 si ppe $ n neq$ tabEnt [ i ] alors
16 permuter ( indppe , i )
17
18 si indDeb < t a i l l e 1 alors
19 t r iDir e c t ( tabEnt , t a i l l e , indDeb + 1)
20 fin
Introduction à l'algorithmique en C/Unix
c M. Alissali, Université du Maine 1998
62 Chapitre3. Algorithmique et structures de données avancées
Implantation
permuter.c
permuter.h
triDirectRec.c
triDirectRec.h
Test
triDirectTest.c
1 #include<stdio . h>
2 #include<s t d lib . h>
3 #include " s a i s i e I n v e r s e . h"
4 #include " a f f ic h e . h"
5 #include " triDirect Re c . h"
6 #define TAILLE 10000
7
8 int main( void )
9 f
10
11 int tab [ TAILLE ] , i , maxRand = TAILLE 10 ;
12 / = f 22 , 25 , 21 , 10 , 45 , 12 , 53 g ; /
13
14 for ( i = 0 ; i < TAILLE ; i ++)
15 tab [ i ] = rand () % maxRand;
16
17 / a f f i c h e ( tab , TAILLE);
18 /
19
20 triDirect Rec ( tab , TAILLE, 0 ) ;
21
22 / a f f i c h e ( tab , TAILLE);
23 /
24 g
10 23 5 75 37 49 53
10 23 5 37 75 49 53
10 23 5 37 49 75 53
10 23 5 37 49 53 75
10 5 23 37 49 75 53
5 10 23 37 49 75 53
Analyse
1 entier tabEnt [ N] , i , bSup
2 bSup < N 1
3 1 . boucle tant qu ' i l y a des permutations
4 2 . 1 . boucle sur tabEnt [ 0 . . bSup ]
5 2 . 2 . si tabEnt [ i ] > tabEnt [ i+1 ] alors
6 permuter ( tabEnt [ i ] , tabEnt [ i+1 ])
7 décrément bSup
Introduction à l'algorithmique en C/Unix
c M. Alissali, Université du Maine 1998
64 Chapitre3. Algorithmique et structures de données avancées
Ranement
Après le premier passage (la 1re itération de la boucle extérieure) le plus grand élément se trouve
rangé en haut du tableau, on peut donc l'exclure dans l'itération suivante. Le même raisonnement
s'applique sur les itérations suivantes : à chaque itération on diminue de 1 la taille de la partie à
traiter du tableau.
Plusieurs élément du haut du tableau peuvent se trouver rangés dans l'ordre ; on peut donc
optimiser le traitement en s'arrêtant au dernier élément déplacé de l'itération précédente, ce qui
peut être fait en ajoutant l'instruction :
bSup < indice de la dernière permutation.
Conception
1 Entrées :a : tableau d ' entier tabEnt , entier t a i l l e
2 S o r t ie s :a : tableau t r ié
3 Locales :a : entier i , booléen echange
4
5 bSup < N2
6 boucle
7 début
8 si bSup < 2 alors retour
9 echange < faux
10 pour i < 0 jusqu ' à bSup faire
11 si tabEnt [ i ] > tabEnt [ i+1 ] alors
12 début
13 permuter ( tabEnt [ i ] , tabEnt [ i+1 ])
14 indEchange < i
15 echange < vrai
16 fin
17 fin
18 si non echange alors retour
19 bSup < indEchange
Amélioration
Pour le tableau 10 92 35 50 69 90 95 il sut d'une seule passe (pour faire remonter
92 ) pour obtenir le résultat :
10 35 50 69 90 92 95
Par contre dans la conguration symétrique à la précédente :
10 50 69 90 92 35 95
il faut plusieurs passes :
95 92 35 90 69 50 10
95 92 90 35 69 50 10
95 92 90 69 35 50 10
95 92 90 69 50 35 10
dans le deuxième tableau il faut faire descendre l'élément mal placé ;
par symétrie au premier cas ceci peut se faire en une seule passe si le parcours s'eectue de haut
en bas .
Une solution consiste à alterner le sens du parcours. On appelle ce nouvel algorithme Tri-shaker .
1 determiner debut et fin de parcours
2 1 . boucle tant qu ' i l y a des permutations
3 2 . 1 . boucle echange debut . . fin
4 2 . 2 . mise à jour fin
5 3 . 1 . boucle echange fin . . debut
6 3 . 2 . mise à jour debut
Modication de l'analyse Ranement et conception : exercice.
Introduction à l'algorithmique en C/Unix
c M. Alissali, Université du Maine 1998
3.6 Algorithmes de tri de tableau 65
triBulTab.c
triBulTab.h
1 #include<stdio . h>
2 #include<s t d lib . h>
3 #include " s a i s i e I n v e r s e . h"
4 #include " a f f ic h e . h"
5 #include " triBulTab . h"
6
7 #define TAILLE 10000
8
9 int main( void )
10 f
11
12 int tab [ TAILLE ] , i , maxRand = TAILLE 10 ;
13 / = f 22 , 25 , 21 , 10 , 45 , 12 , 53 g ; /
14
15 for ( i = 0 ; i < TAILLE ; i ++)
16 tab [ i ] = rand () % maxRand;
17
18 / a f f i c h e ( tab , TAILLE);
19 /
20
21 triBulTab ( tab , TAILLE) ;
22
23 / a f f i c h e ( tab , TAILLE);
24 /
25 g
7 trouver x > el
8 permuter ( x , el )
9 3 . 1 . separer partie gauche
10 3 . 2 . separer partie droite
Ranement
Dans 2.1. et 2.2. la recherche de l'élément à permuter se fait à partir de l'emplacement de la
dernière permutation.
Exemple
22 25 21 10 45 12 53
12 25 21 10 45 22 53
12 22 21 10 45 25 53
12 10 21 22 45 25 53
10 12 21 22 45 25 53
10 12 21 22 25 45 53
Conception
Données
1 Entrées
2 tabEnt:a : tableau à t r i e r
3 deb , fin :a : lim it e s du t r i
4 Sortie
5 tabEnt:a : tableau t r ié
6 Locales
7 g , d:a : indice de parcours
8
Traitement
1 début
2 g < ;adeb;; d < fin
3 tant que d > g faire
4 début
5 tant que tabEnt [ d ] < tabEbt [ g ] faire decrementer d
6
7 si d <= g alors retour
8
9 permuter ( tabEnt [ g ] , tabEnt [ d;a ]) ; incrementer g
10
11 tant que tabEnt [ g ] < tabEbt [ d ] faire incrementer g
12
13 si d <= g alors retour
14
15 permuter ( tabEnt [ g ] , tabEnt [ d;a ]) ; decrementer d
16 fin
17
18 si deb < g 1 alors triSep ( deb , g 1 )
19 si fin > g+1 alors triSep ( g+1 , fin )
20 fin
Implanataion
triSep.c
triSep.h
Test
triSepTest.c
Performances
Le tableau 3.2 montre une comparaison des performances réelles des diérents algorithmes
présentés ici. Les essais ont eu lieu sur une machine Sun Sparcstation 20.
3.8.1 Introduction
Une liste chaînée est une structure de données composée de blocs de même nature (appelés élément s
ou n÷ud s) chaînés de telle manière que chaque n÷ud, à l'exception du dernier, désigne son successeur.
Ce type de liste est aussi appelé liste séquentielle ou encore liste séquentielle chaînée . Lorsqu'il n'y
a pas d'ambiguïté on parlera simplement de liste .
Par la suite on suppose que les listes sont implantées à l'aide des structures autoréférentielles
(cf. 2.3.5), le mécanisme de désignation est alors celui du pointeur.
Le premier n÷ud d'une liste est souvent appelé tête de liste et le dernier queue de liste . Un pointeur
(teteListe dans le cas de la gure 3.1) est normalement utilisé pour désigner la tête de la liste. Il est
indispensable lorsqu'une liste peut être vide ce qui s'exprime par la valeur NULL du pointeur.
29 52 10 32 24
Dans la pratique les listes sont généralement utilisées lorsque les données ne sont pas connues à
l'avance, les n÷ud s sont donc créés dynamiquement et ne font pas partie de la structure de la liste
initialement. Les n÷uds sont normalement implantés à l'aide des structures autoréférentielles (cf. 2.3.5).
Le code suivant contient les déclarations du type et de la fonction de création d'un n÷ud qui seront
utilisés par la suite.
noeud.h
Ce choix implique qu'une liste est vide si teteListe ne pointe sur rien (fait exprimé par la valeur
empruntée au langage C) :
NULL
1 booléen lis t e V id e ( typeNoeud t e t e L is t e )
2 début
3 retour ( t e t e L is t e = NULL)
4 fin
La fonction suivant retourne un pointeur sur l'élément (successeur ) désigné par un élément donné
(prédécesseur ) dans une liste donnée :
1 typeNoeud suivant ( typeNoeud courant )
2 début
3 retour ( courant >suiv )
4 fin
Le test de n de liste revient à tester si l'élément manipulé n'a pas de successeur (donc ne pointe
sur rien) :
1 booléen f in L is t e ( typeNoeud courant )
2 début
3 retour ( courant >suiv = NULL)
4 fin
Le parcours d'une liste chaînée ne peut se faire que de manière séquentielle ; en faisant avancer un
pointeur donné d'un seul n÷ud à la fois :
1 parcours ( typeNoeud t e t e L is t e )
2 début
3 ptrParcours < t e t e L is t e
4 tant que non f in L is t e ( ptrParcours )
5 début
6 Operation ( s ) sur l ' élément courant , p. e . a f f ic h e ( ptrParcours )
7 ptrParcours < ptrParcours >suivant
8 fin
9 fin
Par exemple, ayant à disposition une fonction afficheContenus pour l'achage des champs d'un
n÷ud, l'achage d'une lsite peut se faire de la manière suivante :
1 a f f i c h e r ( typeNoeud t e t e L is t e )
2 début
3 ptrParcours < t e t e L is t e
4 tant que non f in L is t e ( ptrParcours )
5 début
6 afficheContenus ( ptrParcours >cont )
7 ptrParcours < ptrParcours >suivant
8 fin
9 fin
10
Pour rechercher un élément il faut faire un parcours en comparant l'élément pointé avec l'élément
recherché. En cas d'égalité on retourne la valeur courante du pointeur de parcours, si la boucle se termine
l'élément n'a pas été trouvé, ce qui doit être indiquée par la valeur retournée, NULL dans notre cas :
1 typeNoeud trouver ( typeNoeud t e t e L is t e , typeContenus cont )
2 début
3 ptrParcours < t e t e L is t e
4 tant que non f in L is t e ( ptrParcours )
5 début
6 si cont = ptrParcours >cont retour ( ptrParcours )
7 ptrParcours < ptrParcours >suivant
8 fin
Introduction à l'algorithmique en C/Unix
c M. Alissali, Université du Maine 1998
74 Chapitre3. Algorithmique et structures de données avancées
9
10 retour (NULL)
11 fin
Il s'agit ici du cas général et la comparaison (ligne 6) est notée simplement =. Dans une réalisa-
tion/utilisation concrète cette opération doit être remplacée par la comparaison adéquate éventuellement
implantée à l'aide d'une fonction, de la même manière que l'adaptation du tri rapide dans l'application
BDU (cf. 4.6) par exemple.
Pour l'insertion et la suppression on traite d'abord le cas le plus simple : l'opération s'eectue à
un emplacement donné de la liste. À première vue un nouvel élément ne peut être inséré qu'après
l'emplacement spécié. Le passage des paramètres doit se faire par référence (c.à.d. adresse ou pointeur
qui désigne l'élément) puisqu'ils seront modiés, ce qui en même temps simplie l'écriture :
1 in s e r e r Dir e c t ( typeNoeud noeudAinserer , typeNoeud emplacement)
2 début
3 noeudAinserer >suiv < emplacement >suiv
4 emplacement >suiv < noeudAinserer
5 fin
Cette fonction peut par exemple être utilisée pour la saisie en insérant l'élément saisi en tête de liste.
En supposant l'existence d'une fonction de saisie du contenu, lireContenus, qui retourne vrai en cas
de saisie valide, faux sinon (pour signier la n de la saisie), on peut eectuer la saisie par l'algorithme
suivant :
1 début
2 répéter
3 si ( ( nouveauNoeud < creerNoeud ()) == NULL)
4 SortieErr e ur
5 sinon
6 début
7 saisieOK < lireContenus ( nouveauNoeud >cont )
8 si saisieOK
9 in s e r e r Dir ( nouveauNoeud , t e t e L is t e )
10 fin
11 jusqu ' à non saisieOK
12 l i b e r e r ( nouveauNoeud )
13 fin
Dans le cas de l'insertion on duplique d'abord le n÷ud emplacement avant lequel on désire insérer
(lignes 3 et 5), puis on remplace ses contenus par ceux de noeudAinserer et on chaîne ce dernier à la
copie (qui désormais remplace l'original). Dans le cas particulier où l'emplacement désigne la sentinelle
il sut que la copie remplace celle-ci, ce qui revient à la faire désigner par dernier (ligne 4) :
1 insererAvant ( typeListe l i s t e , typeNoeud nouedAinserer , typeNoued emplacement )
2 début
3 typeNoeud copie < creerNoeud ()
4
5 si emplacement = l i s t e . dernier l is t e . dernier < copie
6 sinon copie < emplacement
7 emplacement >cont < noeudAinserer >cont
8 emplacement >suiv < copie
9 l i b e r e r ( noeudAinserer )
10 fin
La suppression d'un n÷ud peut se faire en écrasant ses contenus avec ceux de son successeur, c'est
celui-ci qui est eectivement supprimé ensuite :
1 supprimerNoeud ( typeListe l i s t e , typeNoeud nouedAsupprimer )
2 début
3 typeNoeud succes s eu r < nouedAsupprimer >suiv
4 si succes se u r = l i s t e . dernier l is t e . dernier < noeudAsupprimer
5 sinon noeudAsupprimer < succe ss e ur
6 l i b e r e r ( succe ss e ur ) / suppression e f f e c t i v e /
7 fin
Si le n÷ud à supprimer est la queue de la liste, il devient tout simplement la sentinelle (ligne 4) ;
il n'est pas nécessaire de copier les contenus dans ce cas-là car ceux de la sentinelle n'ont aucune
importance.
Certains des algorithmes de l'implantation simple restent valables ici, comme l'insertion directe
(algorithme 3.8.3) utilisé dans l'algorithme 3.8.5 ci-après. Il s'agit des algorithmes indépendants de la
structure de la liste elle-même. Les autres algorithmes doivent être modiés pour s'adapter à cette
nouvelle structure, par exemple la n de liste est atteinte lorsque le pointeur courant pointe sur la
sentinelle (c.à.d. lorsqu'il a la même valeur que dernier) :
1 booléen f in L is t e ( typeNoeud courant )
2 début
3 retour ( courant = dernier )
4 fin
3.8.5 Listes triées
Une liste triée est une liste dont les éléments sont toujours rangés dans un ordre déni par une clef
(un champ ou une combinaison de champs des contenus du n÷ud). Ceci implique qu'il faut toujours
rechercher la position de l'élément manipulé quelle que soit l'opération à eectuer, ce qui nécessite de
parcourir la liste dans tous les cas.
Par la suite, on suppose une implantation avec sentinelle, mais, comme on l'a mentionné au pa-
ragraphe précédent, il reste possible d'utiliser certains des algorithmes de l'implantation simple. On
suppose aussi que la liste est triée dans l'ordre croissant et on attribue à l'opératuer de comparaison <
un sens générique dans le même esprit que précédemment.
Comparé au cas des listes non triées, la saisie devient moins performante mais on a un gain considé-
rable sur le coût la recherche, qui normalement est l'opération la plus fréquente.
1 n la b e l f :a a l g :LTsaisie :LTsaisieg%
2 s a i s i e L i s t e T r ie e ( typeListe liste )
3 début
4 tant que non f i n S a i s i e / condition a r a f f i n e r /
5 début
6 si ( ( nouveauNoeud < creerNoeud ()) == NULL)
Introduction à l'algorithmique en C/Unix
c M. Alissali, Université du Maine 1998
76 Chapitre3. Algorithmique et structures de données avancées
7 SortieErr e ur
8 sinon
9 début
10 lireContenus ( nouveauNoeud >cont )
11 ptrParcours < premier
12 tant que ptrParcours != NULL et ptrParcours >cont < nouveauNoeud >cont
13 ptrParcours < ptrParcours >suiv
14
15 insererAvant ( ptrParcours , nouveauNoeud )
16 fin
17 fin
18 fin
1 n la b e l f :a a l g :LTrech :LTrech g
2 typeNoeud trouverDansListeTriee ( typeListe l i s t e , typeContenus cont )
3 début
4 ptrParcours < l i s t e . premier
5 tant que non f in L is t e ( ptrParcours ) et cont < ptrParcours >cont
6 ptrParcours < ptrParcours >suivant
7
8 si cont = ptrParcours >cont retour ( ptrParcours )
9 retour (NULL)
10 fin
L'insertion dans une liste triée doit s'eectuer à la place correpondant à l'ordre de l'élément, un
parcours est donc nécessaire. On montre ici deux possibilités : un parcours avec deux pointeurs qui
utilise l'insertion directe, et un parcours avec un seul pointeur en utilisant l'algorithme d'insertion par
copie (3.8.4).
L'algorithme suivant eectue l'insertion en utilisant un parcours avec deux pointeurs (pour en donner
un exemple). Il montre aussi un cas de réutilisation d'algorithme : celui de l'insertion directe (3.8.3)
développé pour l'implantation simple, mais utilisable ici puisqu'indépendant de la structure de la liste :
1 in s e r e r Dir L is t e Tr ie e ( typeListe li s t e , typeNoeud noeudAinserer )
2 début
3 ptrPrec < l is t e . premier / non nul ( s e n t i n e l l e ) /
4 ptrParcours < l i s t e . premier > suiv
5 tant que non f in L is t e ( ptrParcours ) et
6 noeudAinserer >cont < ptrParcours >cont
7 début
8 ptrPrec < ptrParcours
9 ptrParcours < ptrParcours >suivant
10 fin
11 in s e r e r Dir e c t ( noeudAinserer , ptrPrec )
12 fin
L'algorithme suivant montre l'autre possibilité :
1 in s e r e r Co p ie L is t e Tr ie e ( typeListe l i s t e , typeNoeud noeudAinserer )
2 début
3 ptrParcours < l i s t e . premier
4 tant que non f in L is t e ( ptrParcours ) et
5 noeudAinserer >cont < ptrParcours >cont
6 ptrParcours < ptrParcours >suivant
7
8 insererAvant ( li s t e , noeudAinserer , ptrParcours )
9 fin
Il est possible d'améliorer la performance du dernier algorithme en modiant la condition d'arrêt de
la boucle. Dans le cas général les contenus de la sentinelle ne sont pas utilisées : si on y copie ceux de
l'élément cherché la condition sur les contenus (ligne 5) sera vériée en n de boucle et on n'a donc plus
besoin d'eectuer le test correspondant :
1 n la b e l f :a a l g :LTinsCpieAmel :LTinsCpieAmel g
2 in s e r e r Co p ie L is t e Tr ie e ( typeListe l i s t e , typeNoeud noeudAinserer )
3 début
4 l i s t e . dernier >cont < noeudAinserer >cont
Introduction à l'algorithmique en C/Unix
c M. Alissali, Université du Maine 1998
3.9 Piles et queues 77
premier
courant dernier
Fig. 3.2 Représentation schématique d'une liste circulaire doublement chaînée avec sentinelle.
4.2 Analyse
Données
Tables d'informations sur les étudiants et les enseignants.
Il faut deux types d'enregistrements, mais on remarque qu'une partie de l'inforamtion est partagée par
les deux types. Cette partie donnera lieu à la déntion d'un troisième type regroupant des informations
sur une personne.
Traitement
Saisie, achage, recherche, suppression, modication. Interaction avec l'utilisateur.
Pour une performance optimale il faudra aussi trier les données.
4.2.1 Ranement
Données
Types
enregistrement personne
nom, prenom, date de naissance
enregistrement etudiant
personne, identiant
enregistrement enseignant
personne, grade
date de naissance : jours, mois, annee (enr.)
identiant : entier
grade : code (chaine de caracteres ou entier)
Variables
tableau de etudiant
tableau de enseignant
Traitement
1. Sur les enregistrements
saisie : retourne un enr. du type approprié
) une fonction par type d'enregistrement.
79
80 Chapitre4. Application : Base de Données Universitaire
personne saisiePersonne(void)
etudiant saisieEtudiant(void)
enseignant saisieEnseignant(void)
date saisieDate(void);
achage : ache un enr. du type approprié
) une fonction par type d'enregistrement.
Exercice : description des fonctions
2. Sur les tableaux
aectation d'un élément (enr.) : autorisée en C.
recherche d'un élément : retour indice/erreur.
modication d'un élément :
Entrées : élément à modier, modications.
Sortie : élément modié.
Exercice : description des autres fonctions.
3. Interaction avec l'utilisateur: exercice
4.3 Conception
4.3.1 Types et données
denir type : typeDate
entier jour, mois, annee
denir type : typePersonne
chaine_de_caracteres nom
chaine_de_caracteres prenom
typeDate date
denir type : typeEtudiant
typePersonne etudiant
entier identiant
denir type : typeEnseignant
Exercice : dénir les membres
typeEtudiant tabEtudiants[MAX_ETD]
typeEnseignant tabEnseignant[MAX_ENS]
Traitements
Il s'agit de types composés à plusieurs niveaux , il faut donc distinguer plusieurs niveaux
de traitement : d'abord sur les enregistrements date, etudiant et enseignant, ensuite sur les
tableaux d'enregistrement.
Les données complexes ont été dénies par composition progressive de types simples. Il faut procé-
der de la même façon pour les traitements : construire progressivement les opérations sur les types
complexes à partir des opérations sur les types simples.
Exemple
typeDate est composé de trois entiers : jour, mois, annee , pour saisir/acher une date il sut
de saisir/acher ses trois membres ;
typeEtudiant est composé de typePersonne et de typeDate : pour saisir/acher une variable de
ce type il faut utiliser saisir/acher de ces deux type.
Attention: une opération sur un type complexe n'est pas nécessairement la répétition de la même
opération sur ses composants : la diérence entre deux dates ne s'obtient pas par simples soustractions
des membres respectifs.
Introduction à l'algorithmique en C/Unix
c M. Alissali, Université du Maine 1998
4.3 Conception 81
pour simplier on peut dire qu'un module regroupe les données de natures cohérentes et leurs
opérations ;
un ensemble de modules (un logiciel ou un assemblage de modules) doit être bien organisé en
architecture robuste. En particulier il faut bien préciser les relations entre modules : qui utilise
qui et comment , à l'aide de pseudo-langage, de schémas, etc. ;
un module doit être utilisable sans avoir à se soucier des détails de son implantation. Seule son
interface (les fonctions, constantes et variables) qu'il exporte doivent être visibles par les modules
qui l'utilise ;
en langage C on convient de dénir pour chaque module deux chiers : l'implantation (.c) et
l'interface (.h).
Saisie Saisie
Afficher typeDate typePersonne Afficher
Modifier Modifier
Saisie Saisie
Afficher typeEtudiant typeEnsiegnant Afficher
Modifier Modifier
Rechercher Rechercher
Trier tabEtudiants tabEnseignants Trier
... ...
Module
Données/Type Principal
Opérations/Traitement
Pour maintenir la clarte, ce schéma est
Utiliser (composition de types) un peu simplifié.
Par exemple le module principal utilise
Utiliser (opérer sur) Interface tous les types, ce qui ne figure pas
Utilisateur explicitement ici.
Module
1 / Fichier : dates . c
2 Description : module de manipulation d ' un enregistremnt de type date .
3 dans l ' état actuel f o u r n i t uniquement l e s opérations de s a i s i e et
4 d ' a f f i c h a g e ( r e c q u i s e s par l ' a p p l i c a t i o n BDU).
5 Auteur : Mamoun A l i s s a l i
6 Historique : 28 . 11 . 96 création
7 29 . 11 . 96 t e s t OK
8 A FAIRE : modifiaction de date
9 opérations arithmétiqu es
10 /
11 #include < stdio . h>
12 #include " dates . h"
13
14 void
15 afficheDate ( typeDate dt )
16 /
17 afficheDat e : Affiche à l ' écran un enregistrement de type typeDate
18 Entree : dt , enregistrement à a f f i c h e r
19 /
20 f
21 p r in t f (" %d/%d/%d n n" , dt . jour , dt . mois , dt . annee ) ;
22 g
23
24 void
25 s a is ie Dat e ( typeDate ptrDate )
26 /
27 permet la s a i s i e au c l a v i e r d ' un enregistrement de type typeDate
28 Entree : ptrDate , pointeur sur l ' enregistrement à s a i s i r
29 Remarque : i l faut u t i l i s e r un pointeur pour récupérer l e s modificatio n s
30 /
31 f
32 p r in t f ( " Jour ? n n" ) ;
33 scanf ("%d" , &(( ptrDate ) . jour ) ) ;
34 p r in t f ( " Mois ? n n" ) ;
35 scanf ("%d" , &(( ptrDate ) . mois ) ) ;
36 p r in t f ( " Annee ? n n" ) ;
37 scanf ("%d" , &(( ptrDate ) . annee ) ) ;
38 g
39
dates.h
1 typedef struct f
2 int jour , mois , annee ;
3 g typeDate ;
4
5 void afficheDate ( typeDate dt ) ;
6 void s a is ie Dat e ( typeDate ptrDate ) ;
1 / typePersonne doit être inclu pour être reconnu des modules qui
2 u t i l i s a t e u r s : i l s s ' en servent pour la composition des types
3 ( p . e . etudiant et pour l ' appel des fonction s du present module
4 /
5 #include " dates . h"
6 #define TAILLE 32
7 typedef struct f
8 char nom[ TAILLE ] , prenom [ TAILLE ] ;
9 typeDate dateNaissance ;
10 g typePersonne ;
11
12 void affichePer s onn e ( typePersonne prs ) ;
13 void s a is ie P e r s on ne ( typePersonne ptrPrs ) ;
personne.c
personneTest.c
1 / Fichier : etudiant . c
2 Description : module de traitement d ' un enregistrement typeEtudiant .
3 Fournit l e s opérations d ' a f f i c h a g e et de s a i s i e .
4 Auteur : Mamoun A l i s s a l i
5 Historique : 10 . 12 . 96 : r é a l i s a t i o n , t e s t OK.
6 /
7 #include<stdio . h>
8 #include " etudiant . h" / i n c l u t personne . h /
9
10 void
11 a f f ic h e Et u dia nt ( typeEtudiant etd )
12 /
13 a f f i c h e E t u d i a n t : a f f i c h a g e d ' un ernregistr emen t de typeEtudiant .
14 Entree : etd , ernregistre ment à a f f i c h e r .
15 U t i l i s e : affichePersonne .
16 /
17 f
18 affichePe rs onn e ( etd . pers ) ;
19 p r in t f ( " Id e n t if ia n t : %d n n" , etd . id ) ;
20 g
21
22 void
23 s a is ie E t u d ian t ( typeEtudiant ptrEt )
24 /
25 s a i s i e E t u d i a n t : sasie d ' un ernregistreme nt de typeEtudiant .
26 Entree : ptrEtd , pointeur sur l ' ernregistr emen t à s a i s i r .
27 U t i l i s e : saisiePersonne .
28 /
29 f
30 s a is ie P e r s on n e (&(( ptrEt ) . pers ) ) ;
31 p r in t f ( " Id e n t if ia n t ? n n" ) ;
32 scanf ("%d" , &(( ptrEt ) . id ) ) ;
33 g
1 #ifndef ETUDIANT_H
2 #define ETUDIANT_H
3
4 #include " personne . h"
5
6 typedef struct f
7 typePersonne pers ;
8 int id ;
9 g typeEtudiant ;
10
11 void a f f ic he E t ud ian t ( typeEtudiant etd ) ;
12 void s a is ie E t u d ia nt ( typeEtudiant ptrEt ) ;
13
14 #endif / ETUDIANT_H /
etudiantTest.c
tabEtudiantsV1.c
tabEtudiantsV1.h
4.6 Améliorations
4.6.1 Version 2 : tri du tableau adaptation du tri rapide
La méthode de tri rapide (tri par séparation peut être appliquée à un tableau d'enregistrements (en
l'occurence tabEtudiants) avec les deux modications suivantes :
Remplacer le type entier par le type typeEtudiant ;
fournir une opération de comparaison (qui exprime la relation d'ordre).
1 void triTabEtudiant ( typeEtudiant tabEtudiants [ ] g n , int deb , int fin )
2
3 g < ;adeb;; d < fin
4 tant que d > g f a ir e
5 DEBUT
6 tant que i n f e r i e u r ( tabEtudiant [ d ] , tabEtudiants [ g ] ) f a ir e decrementer d
7
8 si d <= g a lo r s retour
9
10 permuter ( tabEtudiant [ g ] , tabEtudiant [ d;a ]) ; incrementer g
11
12 tant que i n f e r i e u r ( tabEtudiant [ g ] , tabEtudiant [ d ] ) f a ir e incrementer g
13
14 si d <= g a lo r s retour
15
16 permuter ( tabEtudiant [ g ] , tabEtudiant [ d;a ]) ; decrementer d
17 FIN
18
19 si deb < g 1 a lo r s triSep ( tabEtudiants , deb , g 1)
20 si fin > g+1 a lo r s triSep ( tabEtudiants , g+1 , fin )
Fonction de comparaison
Pour pouvoir comparer des enregistrements de type typeEtudiant (comme inferieur ci-dessus) il
faut dénir une relation d'ordre et fournir les opérations appropriées :
on choisit de trier par nom, le champ nom est la clef du tri ;
pour la relation d'ordre on peut envisager deux solutions :
dénir une fonction pour chaque opérateur de comparaison (<, >, =) ;
à l'instar de la fonction de comparaison de chaînes de caractères (strcmp), dénir une seule
fonction qui retourne une valeur inférieure (resp. égale, resp. supérieure) à zéro si le pre-
mier paramètre est inférieur (resp. égale, resp. supérieur) au deuxième. C'est cette deuxième
solution qui sera retenue.
int compEtudiant(typeEtudiant etd1, typeEtudiant etd2)
f
return(strcmp(etd1.pers.nom, etd2.pers.nom));
g
Ce même principe peut être étendu à d'autres clefs pour trier le tableau par prénom ou par identiant
par exemple. Dans ce cas-là, pour ne pas dupliquer les données, il faut utiliser des tableaux d'indices
auxiliaire (cf. 4.6.2).
Pour parcourir (pour acher par exemple) le tableau trié par prénom :
1 boucle pour i < 0 jusqu ' à t a i l l e
2 a f f ic h e r ( tabPers [ indP [ i ] ])
Un tableau auxiliaire d'indices est particulièrement utile (voire indispensable) dans les situations
suivantes :
1 n item le coût du déplacement des éléments du tableau indexé est
2 élevé à cause de leur t a i l l e ;a ;
3 n item on d é s ir e t r i e r le tableau selon p lu s ie u r s c l e f s ou p lu s ie u r s
4 c r i t è r e s en même temps .
Une autre manière de construire un tableau d'indices est de faire ses éléments pointer sur les éléments
du tableau indexé : le tableau d'indices est alors un tableau de pointeurs.
Introduction à l'algorithmique en C/Unix
c M. Alissali, Université du Maine 1998
90 Chapitre4. Application : Base de Données Universitaire
Les modules dates, personne et etudiant ne sont pas modiés (grâce à la modularité !).
Il en est de même pour une partie du module tabEtudiants (saisie, achage, ...). Pour éviter la
duplication, cette partie se trouve désormais dans le sous-module tabEtudiantV0.
La mention Vn est ajoutée au nom d'un module modié pour indiquer qu'il s'agit de la version
no n.
Les versions 2 et 3 implantent en deux étapes les améliorations (cf. 4.6) présentées précédemment.
Introduction à l'algorithmique en C/Unix
c M. Alissali, Université du Maine 1998
4.7 Implantation des améliorations 91
tabEtudiantsV2.c
tabEtudiantsV2Test.c
1 #include<stdio . h>
2 #include " tabEtudiants . h"
3 #define TAILLE_TAB_ET 4
4
5 int main( void )
6 f
7 typeEtudiant tabEtudiants [TAILLE_TAB_ET] , etd ;
8 int i ;
9
10 p r in t f ( " n tVersion 2 n n n n" ) ;
11 saisieTabEtudiants ( tabEtudiants , TAILLE_TAB_ET) ;
12 afficheTabEtudiants ( tabEtudiants , TAILLE_TAB_ET) ;
13 triTabEtudiants ( tabEtudiants , 0 , TAILLE_TAB_ET 1 ) ;
14 afficheTabEtudiants ( tabEtudiants , TAILLE_TAB_ET) ;
15 while ( 1 )
16 f
17 p r in t f ( " Nom ? n n" ) ; scanf ("%s " , etd . pers . nom) ;
18 if ( strcmp ( etd . pers . nom, "0") == 0 ) return ( 0 ) ;
19 if (( i = rechercheEtudiant ( etd , tabEtudiants , TAILLE_TAB_ET) ) != NON_TROUVE)
20 a f f ic h e Et u dia nt ( tabEtudiants [ i ] ) ;
21 else
22 p r in t f ( " Il n' y aucun etudiant au nom de %s n n" , etd . pers . nom) ;
23 g
24 g
4.8 Version 4
utilisation de la fonction générique de tri rapide, qsort, de la bibliothèque standard ;
utilisation d'un tableau de pointeurs pour l'indexage auxiliaire ;
gestion des inclusions multiples de chiers d'en-tête.
4.8.1 La fonction générique qsort
La fonction qsort permet de trier un tableau de n'importe quel type à condition de fournir une
fonction de comparaison (cf. 4.6) et certaines informations sur le type des éléments du tableau. Sa
déclaration est la suivante :
void qsort(void base, size_t nel, size_t width, int (compar) (const void , const void ));
base : pointeur (ou adresse) du début ;
nel : nombre d'éléments (taille) du tableau ;
width : taille (seule information nécessaire) de chaque élément;
compar : fonction de comparaison par accès indirect (pointeurs) de deux éléments ;
void * est un pointeur générique qui peut pointer sur n'importe quel type.
4.8.2 Gestion des inclusions multiples
Inclure des chiers d'en-tête dans d'autres chiers d'en-tête comporte le risque d'inclure plusieurs
fois un même chier. Par exemple, après intégration du module enseignant, on trouvera dans le module
principal :
#include "tabEtudiants.h"
#include "tabEnseignants.h"
Ces deux chiers incluent respectivement "etudiant.h" et "enseignant.h", qui à leur tour incluent
chacun "personne.h". Ce dernier sera donc inclus deux fois dans le module principal, ce qui provoquera
des erreurs (p.e. typePersonne sera déni deux fois).
Pour résoudre ce problème on fait appel au préprocesseur 1 pour eectuer de la compilation condi-
tionnelle : chaque chier d'en-tête, p.e. monModule.h, a alors la forme suivante :
#ifndef MONMODULE_H / constante symbolique quelconque /
#dene MONMODULE_H / sera denie a la premiere inclusion /
/ corps de monModule.h /
#endif
La première ligne indique qui la suite (jusqu'à la directive #endif) sera prise en compte si la constante
MONMODULE_H n'est pas dénie. Dans ce cas, celle-ci sera dénie dans la ligne suivante, ce qui ne peut se
produire qu'une seule fois.
Le test #ifndef (if not dened ) est un cas particulier des tests reconnus par le préprocessuer et dont
la forme générale est :
#if EXPRESSION
...
#else
1. On rappelle que le préprocesseur exécute les directives (comme #define et #include) avant la compilation. Pour être
distinguées des mots clefs, variables, etc., les directives commencent toutes par #.
...
#endif
les lignes qui suivent la directive #if seront traitées normalement si EXPRESSION est vériée, sinon
elles seront ignorées, et ce sont celles qui suivent la directive #else qui seront traitées.
EXPRESSION est une expression booléenne (qui peut utiliser les opératuers logiques du C) sur des
constatntes et des constantes symboliques.
i
ii ALGORITHMES
iii
iv PROGRAMMES
v
vi TABLE DES FIGURES
vii
viii LISTE DES TABLEAUX
adresse, 39 (
cd commande ), 6, 8, 13, 25, 26
aectation, 31 (
cdpath variable ), 26
algorithme, 55 chaîne
alias (commande ), 6 caractère de n de, 37
allocation de caractères, 37
dynamique, 39 champ, 41
statique, 39 char (type ), 31, 41, 42
appel chargement
de fonction, 35 de programme, 27
arguments d', 27, 28 chdir (commande ), 26
argc (variable ), 28 chemin
argument, 5 d'accès, 9, 15
arguments chgrp (commande ), 5
d'appel, 27, 28 chmod (commande ), 1214
argv (variable ), 28 chown (commande ), 5
arrêt classes de caractères, 17
condition d', 33 clef, 75
arrière plan, 5, 9 clonage, 27
ASCII cmp (commande ), 13
code, 32 code
table, 32 ASCII, 32
autoréférentielles objet, 47
structures, 71 comm (commande ), 13
awk (commande ), 4, 19 commande, 5
bash (commande ), 5
interpréteur de, 5
bibliothèque, 35 ligne de, 5
standard, 12, 35, 37, 39, 40, 47 rappel de, 22
alias, 6
boucle, 33 awk, 4, 19
break (commande ), 26
bash, 5
BUS (constante ), 30
break, 26
calloc (fonction ), 39, 40 cat, 8, 13, 20, 46
caractère cc, 4
de n de chaîne, 37 cd, 6, 8, 13, 25, 26
constant, 32 chdir, 26
normal, 16 chgrp, 5
spécial, 16 chmod, 1214
caractère spécial chown, 5
<, 7 cmp, 13
>, 7 comm, 13
' ', 8 continue, 26
(, 17 cp, 13
), 17 csh, 5, 6, 911
*, 7, 15, 17, 21 cut, 13, 27
[, 17 diff, 13
[...], 7, 21 echo, 6, 8, 27
$, 7, 9, 17 ed, 16
%, 9 egrep, 12, 17, 19, 21
%, 9 else, 25
%m, 9 emacs, 9
&, 9 end, 25, 26
, 17 exec, 26
D, 20 exit, 5, 2426
, 9, 21 find, 12, 13, 15
@, 9 foreach, 21, 2527
caractères getty, 28
chaîne de, 37 goto, 26
lignes de, 19 grep, 4, 19, 25
spéciaux, 5 history, 22
cat (commande ), 8, 13, 20, 46 id, 4
cc (commande ), 4 if, 6, 24, 26, 27
USER, 10
variables
d'environnement, 11, 27
internes, 11
VRAI (constante ), 31, 33
(
wc commande ), 13, 19
(
which commande ), 10
(
while commande ), 2426
(
while instruction ), 33, 34
(
who commande ), 6
xxi