| PRECEDENT | SOMMAIRE |
Chaque commande UNIX est un programme qui, à l'origine, est écrit en C. Il s'agit des commandes externes correspondant à un fichier exécutable sous un répertoire bin, e.g. /bin/time, et non des commandes internes au shell, e.g. time, ou des fichiers scripts interprétés par shell. En outre, ce programme s'exécute dans un certain environnement défini par les variables shell. Nous allons en montrer le principe.
On sait que tout programme C contient une fonction main() sans paramètres et c'est la première fonction exécutée. Or, on a vu plus haut que c'est le shell qui lance l'exécution d'un programme, et qu'en plus il lui transmet une liste de paramètres éventuels fournis sur la même ligne commande. Ces paramètres sont transmis, après analyse et prétraitement, à la fonction main() du programme considéré. Comment les récupérer, sachant qu'en plus ils sont en nombre quelconque? Il suffit tout simplement, ce que fait le shell, d'en indiquer le nombre et d'en fournir la liste. Ce nombre est conventionnellement désigné argc (argument count) et la liste est un tableau de longueur variable designé argv[] (argument value). Il contient la liste de tous les paramètres de la commande, y compris le nom de celle-ci. La fin du tableau est marquée aussi par son contenu qui est la chaîne vide NULL ('\0'). Si par exemple on exécute un programme par:
% Prog -o toto titi
on aura:
La fonction main() devra alors être muni des deux arguments, argc et argv, ainsi
main (int argc, char* argv[]) {...}
afin de récupérer dans le programme, la liste des paramètres d'exécution: argv étant un tableau de argc chaînes de caractères. Ces paramètres, ramassés sur la ligne commande, sont fournis au programme par le shell de lancement (on verra plus loin comment une primitive exec() qui réalise cela).
D'autres informations sont éventuellement récupérables par main(). Ce sont les variables d'environnement comme PATH, TERM qu'on peut récupérer par l'intermédiaire d'un troisième argument arge (argument environment).
main (int argc, char* argv[], char* arge[]) {...}
où arge, comme argv, est un tableau de chaînes de caractères (dernier élément NULL) de forme
VARIABLE=valeur.
Exemples:
PATH=.:./bin:/bin:/usr/bin:/usr/local/bin
HOME=/usr/users/tounsi
SHELL=/bin/csh
etc...
Ainsi, un programme lancé par un utilisateur sous un shell peut s'exécuter dans l'environnement que l'utilisateur a défini. (La fonction getenv() de la bibliothèque stdlib.h permet aussi de récupérer les informations pour une variable environnement. Elle a un homologue putenv() qui permet de le modifier). Dans les exemples qui suivent, et pour simplifier, on ne va pas utiliser cet argument arge. Nous y reviendrons avec la primitive système exec().
Exemple_1: Voici un exemple de programme qui fait
juste imprimer le tableau argv[]. On va
l'exécuter avec différents paramètres pour
illustrer la convention entre un shell et la programme.
% cat maincv.c
main(int argc, char* argv[])
{ int i;
for (i=0; i<argc; i++)
printf("%s\n",argv[i]);
}% cc -o maCommande maincv.c
% maCommande
maCommande% maCommande toto titi
maCommande
toto
titi% maCommande -o toto <--- avec une chaîne de type option
maCommande
-o
toto% maCommande - o titi <--- une option doit être rattachée à son -
maCommande
-
o
titi% maCommande "- o" $user `hostname` '$user'
maCommande
- o
tounsi <--- paramètres analysés
gnaoui
$user
% ls afac
titi toto
La dernière illustration montre le besoin d'analyser, avec prétraitement éventuel, les paramètres avant transmission. Caractéristique très avantageuse des shells (Voir les manuels shell ou faire man csh).
Pour les commandes (e.g. copie de fichier) dont on connaît d'avance le nombre de paramètres à utiliser, il est d'usage de tester argc dès le début du programme. Par exemple, pour la copie d'un fichier, on peut écrire:
if (argc != 3)
printf(" Plait-il?... );
car on sait qu'il doit y avoir 3 paramètres: deux fichiers, origine et destination, plus le nom de la commande (supposée sans options). On doit aussi pouvoir connaître la liste des options présentes le cas échéant, et qui doivent suivre immédiatement --convention UNIX-- le nom de la commande. L'instruction
if ( strcmp(argv[1],"-o") == 0 )
teste si l'argument option est la valeur "-o". Une autre façon de faire est d'utiliser sscanf() qui permet, comme une lecture avec format, une analyse assez aisée d'une chaîne de caractères. Surtout pour récupérer des paramètres numériques.
Exemple_2:
A titre d'exemple voici, repris du précédent, un programme commande qui imprime les i premiers paramètres fournis (en dehors de la commande et de l'option). Justement, une option -nnombre indique ce nombre. Par exemple:
% commande -n2 toto titi tata
doit afficher toto et titi.
% cat maincv1.c
#include <stdio.h>
main(argc,argv)
int argc; char *argv[];
{ int i; int val_option;if (argc <2)
/* pas d'arguments ni d'options on ne fait rien */
exit(0);
else if (argc == 2){ /* une option seule */
printf("Usage: %s -nN arg1 arg2 ...\n", argv[0]);
exit(1);
}
else { /* ici argc >=2, il y a l'option qui doit etre -n
* et au moins un autre parametre */
(1) if (!(*argv[1]=='-' && *(argv[1]+1)=='n')){
printf("%s: syntaxe option\n",argv[0]);
exit(1);
}
(2) sscanf(argv[1],"-n%d",&val_option);
if ( val_option > argc-2 ){
/* pas assez de parametres */
printf("%s: pas assez d\'arguments\n",argv[0]);
exit(1);
}
for(i=0; i<val_option; i++)
printf("%s\n", argv[i+2]);
exit(0);
}}
On aura noté les lignes: (1) pour tester la justesse de l'option, et (2) pour lire la valeur numérique de cette option. Voici l'exécution:
% cc -o maCdeOpt maincv1.c
% maCdeOpt <--- aucun résultat prevu% maCdeOpt -n3
Usage: maCdeOpt -nN arg1 arg2 ...% maCdeOpt -n2 toto titi tutu
toto
titi% maCdeOpt -n4 titi tutu
maCdeOpt: pas assez d'arguments% maCdeOpt -p titi
maCdeOpt: syntaxe option
On aura noté que chaque message d'erreur est précédé du nom de la commande qu'on peut récupérer dans argv[0]. L'instruction exit(valeur) sera vue plus loin. Elle est comme return(valeur) mais vers le tout premier appel du programme (celui fait par shell de lancement ici, main() pouvant être récursif).
Autre Exemple
Il s'agit de programmer une commande qui affiche les n premières lignes d'un fichier (donné en paramètre). Par défaut, l'entrée standard est prise comme fichier. Si n est absent, on affiche tout le fichier.
#include <stdio.h>
#include <ctype.h>
main (argc, argv)
int argc;
char* argv[];
{
char c;
FILE* f;
int n;
switch (argc) {
case 1: /* affiche stdin sur stdout */
while ( (c=getchar()) != EOF)
putchar(c);
exit(0);
case 2: /* minicat f : affiche f */
if (freopen(argv[1],"r",stdin)==NULL){
fprintf(stderr,"%s inexistant\n",argv[1]);
exit(1);
}
while ( (c=getchar()) != EOF){
putchar(c);
}
exit(0);
case 3: /* minicat -n f : affiche n lignes de f*/
if (! isdigit(argv[1][1])){
fprintf(stderr,"Mauvais nombre\n");
exit(1);
}
if (freopen(argv[2],"r",stdin)==NULL){
fprintf(stderr,"%s inexistant\n",argv[2]);
exit(1);
}
sscanf(argv[1],"-%d",&n);
while ( (c=getchar()) != EOF && n >0){
putchar(c);
if(c=='\n') n--;
}
exit(0);
default: /* erreur */
fprintf(stderr,"usage: %s [[-n] fichier] \n",
argv[0]);
exit(1);
}
}
| PRECEDENT | SOMMAIRE |