TP Intéraction entre le C et le Shell
Filière Eurecom
Philippe Dax
Le but de ce TP est d'utiliser les facilités offertes par l'interpréteur
Shell et les commandes accessibles à partir du Shell à travers des
programmes écrits en langage C.
Un programme C interagit avec le Shell par 2 moyens, interaction avec la
ligne de commande et interaction pendant le traitement propre.
-
Un programme C récupère les informations tapées sur la ligne commande
du Shell par l'intermédiaire des variables prédéfinies argc
et argv. Ces variables doivent être déclarées en argument
de la fonction main() qui sera le point d'entrée initial
du programme C. L'analyse syntaxique de ligne de commande étant alors
à la charge du programme C.
-
Au niveau de l'intéraction des données entre le programme C et le Shell
il existe 2 fonctions de la bibliothèque C standard qui permettent
d'interagir avec les commandes du Shell, à savoir system()
et popen().
Les 2 arguments argc et argv sont transmis du
Shell à la fonction main() du programme utilisateur.
La syntaxe de la fonction main() est la suivante:
main(int argc, char ** argv)
où
- argc est un entier qui est égal au nombre de chaînes
de caractères, séparées par un ou plusieurs blanc (espace ou tabulation),
dans la ligne de commande. Le nom de la commande est donc inclu
dans le compte de la variable argc. Une commande shell
sans argument aura donc un argc = 1.
- argv est un pointeur sur un tableau de pointeurs sur des chaînes
de caractères, celles-ci correspondant à celles tapées sur le clavier.
Chaque pointeur du tableau pointe sur le début des chaînes respectives.
Le tableau est terminé par un pointeur nul indiquant qu'il n'y a plus
d'arguments.
Le schéma ci-dessous illustre le tableau argv:
$ commande argument1 argument2 argument3
^ ^ ^ ^
| | | |
------------ | | | |
argv -->| argv[0] |------- | | |
|----------| | | |
| argv[1] |---------------- | |
|----------| | |
| argv[2] |-------------------------- |
|----------| |
| argv[3] |------------------------------------
|----------|
| null |
------------
La grande majorité des commandes Unix utilisent la convention -lettre
pour désigner une option ( -a, -b, -c,... ). La fonction getopt()
s'appuit sur cette convention pour faciliter la phase de l'analyse
syntaxique de la ligne de commande.
La syntaxe de la fonction getopt est la suivante:
int getopt(int argc, char ** argv, char * optstring)
extern char *optarg;
extern int optind, opterr;
getopt() retourne la lettre de l'option courante de la ligne de
commande qui correspond à l'une des lettres d'option présente dans la chaîne
optstring. La chaîne de référence optstring doit
contenir l'ensemble des lettres d'option possibles.
Si une lettre d'option est suivie d'un caractère ':', cette option annonce un
ou plusieurs arguments séparés par un espace.
La variable optarg est alors pré-initialisée pour pointer sur
le début de l'argument de l'option.
optind est l'index de la chaîne courante, il est compris
entre 0 et argc.
Exemple:
main (int argc, char **argv)
{
int c;
extern char *optarg;
extern int optind;
extern int opterr;
extern int optopt;
int aflg = 0;
int bflg = 0;
int errflg = 0;
char *ofile = NULL;
opterr=0;
while ((c = getopt(argc, argv, "abo:")) != EOF)
{
switch (c) {
case 'a':
if (bflg)
errflg++;
else
aflg++;
break;
case 'b':
if (aflg)
errflg++;
else
bflg++;
break;
case 'o':
ofile = optarg;
(void)printf("ofile = %s\n", ofile);
break;
case '?':
printf("OPTOPT= %c\n",optopt);
errflg++;
}
}
if (errflg) {
(void)fprintf(stderr,
"usage: cmd [-a|-b] [-o ] files...\n");
exit (2);
}
printf("Autres arguments:\n");
for ( ; optind < argc; optind++)
(void)printf("%s\n", argv[optind]);
}
La syntaxe de la fonction system() est la suivante:
system(char *commande)
où commande est une chaîne de caractères qui contient le nom
d'une commande Unix et éventuellement ses paramètres séparés par un
ou plusieurs caractères blanc (espace ou tabulation).
Exemple:
system("grep \":ppp-stud\" /infres/admin/ppp/ppp.map");
Cet appel de fonction permet de lister toutes les lignes du fichier
/infres/admin/ppp/ppp.map dans lequel apparait les sous-chaînes
de caractères :ppp-stud:. Cette sous-chaîne est quotée à
l'aide du caractère '"'. Il est nécessaire d'anti-quoter ce
caractère dans la fonction system() car celle-ci utilise
en argument une chaîne de caractères C déjà quotée par '"',
d'où la présence du \".
La commande system() n'accepte qu'une seule chaîne de caractères
en argument et ne permet donc pas d'introduire plusieurs arguments
dynamiques comme le fait la fonction printf(). Si tel est
le cas, il faut alors coupler la fonction system() avec
la fonction sprintf(). Cette dernière permettra de construire
à la convenance du programmeur la chaîne finale qui sera utilisée
par la fonction system().
Exemple:
char commande[BUFSIZ];
sprintf(commande, "%s %s", argv[1], argv[2]);
system(commande);
où argv[1] et argv[2] sont respectivement les chaînes
de caractères :ppp-stud: et /infres/admin/ppp/ppp.map
mises en premier et second argument du nom du programme utilisateur.
L'inconvénient majeur de la fonction system() est que le
programme appelant ne contrôle ni en entrée ni en sortie, les données
traîtées par la commande. La fonction popen() permet de
pallier cette insuffisance. De manière transparente, popen()
ouvre un pipe avant d'appeler la commande, permettant ainsi
au programme appelant de communiquer avec le programme appelé grâce
à ce pipe.
La syntaxe est la suivante:
FILE * popen(char * commande, char * mode)
-
où FILE * indique que la fonction popen() retourne un
pointeur sur une structure de type FILE, comme le fait la
fonction fopen().
-
où commande représente la chaîne de caractères associée
au nom de la commande Unix, éventuellement suivie de ses arguments,
exactement comme pour la fonction system().
-
où mode correspond au mode d'entrée/sortie choisi:
Ce mode a la même signification que celui qui est généralement utilisé
dans la fonction fopen(filename, mode.
Exemple de lecture de données d'une commande restituées sur l'écran:
FILE *pp;
if ((pp = popen("ps aux", "r") == NULL) {
perror("popen");
exit(1);
}
while (fgets(buf, sizeof(buf), pp))
fputs(buf, stdout);
pclose(pp);
Exemple d'écriture de données vers une commande à partir d'une saisie clavier:
FILE *pp;
if ((pp = popen("/usr/ucb/mail", "w") == NULL) {
perror("popen");
exit(1);
}
while (fgets(buf, sizeof(buf), stdin))
fputs(buf, pp);
pclose(pp);
La fonction pclose() permet de fermer le pipe, avec en argument
le pointeur de fichier rendu initialement par popen().
- Exo 1:
Faire l'analyse syntaxique d'une ligne de commande, en manipulant manuellement les variables
argc argv, de la commande suivante:
$ cmd1 [-h -d] [-f nom_de_fichier]
où -h signifie help, -d debug, -f annonce un nom de fichier qui suit.
Les crochets inquiquent que les arguments sont facultatifs. Si le
nom du fichier est absent l'entrée stdin sera prise par
défaut.
Cliquez ici pour consulter exo1.c
- Exo 2:
A partir de la même commnande décrite ci-dessus, faire l'analyse
de la ligne de commande à l'aide de la fonction getopt()
en appelant la commande cmd2.
- Exo 3:
Utiliser la fonction system() dans un programme C pour
afficher la liste des processus à l'aide de la commande Unix ps.
- Exo 4:
Utiliser la fonction system() dans une boucle pour afficher
des images au format GIF du répertoire /infres/images/gif
à l'aide de la commande Unix xv. On utilisera la fonction
chdir() pour se positionner dans le répertoire.
#define GIFDIR "/infres/images/gif"
...
chdir(GIFDIR);
system("ls *.gif");
while (1) {
printf("Nom de l'image (ou q pour quitter): ");
scanf("%s", &nom);
...
}
- Exo 4-Bis:
La fonction system() devra être réservée pour l'éxecution
de commandes n'ayant pas d'appel système équivalent.
Pour les commandes simples comme cd, ls, rm on utilisera
les appels correspondants:
Créer un répertoire ainsi que quelques fichiers dans celui ci.
Ecrire un programme C qui, à l'aide des appels système précédents,
vous positionnera dans ce nouveau répertoire,
listera son contenu, et demandera la destruction éventuelle de ces fichiers.
Utiliser la commande man ou sman pour l'emploi
de ces appels système.
- Exo 5:
Créer un script-shell, à partir du modèle suivant, qui fait globalement la même chose que le programme C
de l'Exo 4: utilisation de la boucle for.
#!/bin/sh
#
# exo5
#
GIFDIR="/infres/images/gif"
prog=`basename $0`
cd ${GIFDIR}
for file << à compléter >>
do
echo -n "image ${file} (o|n|q): "
read reponse
case "${reponse}" in
o|oui|y|yes)
<< à compléter >>
n|non|no)
<< à compléter >>
q|quit)
<< à compléter >>
*)
echo "réponse incorrecte"
continue
;;
esac
done
- Exo 6:
Modifier le script de l'Exo 5 pour qu'il accepte des noms de fichiers
image sur la ligne de commande.
Utiliser la fonction popen() pour trier le fichier de référence
/infres/admin/ppp/ppp.map à l'aide de l'utilitaire Unix
sort, faire "man sort".
- Exo 7:
sort aura en argument le nom du fichier défini par un #define dans
le programme C comme suit:
#define F "/infres/admin/ppp/ppp.map"
- Exo 8:
sort n'aura pas d'argument. Le fichier à trier sera
ouvert par le programme C, puis sera indroduit dans le pipe.
© (Copyright) Philippe Dax - 1996-2000
Last updated: Jan 23, 1996