Bonjour,
Je bloque depuis un certain moment pour introduire dans un minishell des redirection > < >> << ,
Je ne comprend pas pourquoi mon code ne marche pas, je pense après plusieurs tentation que le problème vient de wait mais je ne vois pas comment le faire fonctionner.
# include <stdio.h>
# include <stdlib.h>
# include <unistd.h>
# include <sys/types.h>
# include <sys/wait.h>
# include <assert.h>
# include <string.h>
enum {
MaxLigne = 1024, // longueur max d'une ligne de commandes
MaxMot = MaxLigne / 2, // nbre max de mot dans la ligne
MaxDirs = 100, // nbre max de repertoire dans PATH
MaxPathLength = 512, // longueur max d'un nom de fichier
};
void decouper(char *, char *, char **, int);
void usage(char *);
# define PROMPT "? "
int
main(int argc, char * argv[]){
char ligne[MaxLigne];
char pathname[MaxPathLength];
char * mot[MaxMot];
char * dirs[MaxDirs];
int i, tmp,overrideOut,appendOut,overrideIn,appendIn;
int status;
/* Decouper UNE COPIE de PATH en repertoires */
decouper(strdup(getenv("PATH")), ":", dirs, MaxDirs);
/* Lire et traiter chaque ligne de commande */
for(printf(PROMPT); fgets(ligne, sizeof ligne, stdin) != 0; printf(PROMPT)){
decouper(ligne, " \t\n", mot, MaxMot);
if (mot[0] == 0) // ligne vide
continue;
tmp = fork(); // lancer le processus enfant
if (tmp < 0){
perror("fork");
continue;
}
if (tmp != 0){
printf("attendre l'enfant (%d)\n", tmp);
tmp = wait(&status);
printf("enfant (%d) fini\n", tmp);
exit(0);
if(ligne[strlen(ligne)-1] == '\n'){
ligne[strlen(ligne)-1] = '\0';
}else{
printf("\n");
return 0;
}
argv[0] = strtok(ligne, " \n\0");
for(argc=1; argv[argc-1]; argc++){
if(!strcmp(">", argv[argc-1])){
overrideOut = 1;
}
if(!strcmp(">>", argv[argc-1])){
appendOut = 1;
}
if(!strcmp("<", argv[argc-1])){
overrideIn = 1;
}
if(!strcmp("<<", argv[argc-1])){
appendIn = 1;
}
argv[argc] = strtok(NULL, " \n\0");
}
if(!strcmp(argv[0], "exit")) exit(0);
if(overrideOut == 1){
freopen(argv[argc-2], "w+", stdout);
argv[argc-2] = strtok(NULL, " \n\0");
argv[argc-3] = strtok(NULL, " \n\0");
}else if(appendOut == 1){
freopen(argv[argc-2], "a", stdout);
argv[argc-2] = strtok(NULL, " \n\0");
argv[argc-3] = strtok(NULL, " \n\0");
}else{
freopen("/dev/tty", "rw", stdin);
}
}
else{
if(execvp(argv[0], argv)){
printf("error");
exit(1);
}else{
exit(0);
}
}
// enfant : exec du programme
for(i = 0; dirs[i] != 0; i++){
snprintf(pathname, sizeof pathname, "%s/%s", dirs[i], mot[0]);
execv(pathname, mot);
}
// aucun exec n'a fonctionne
fprintf(stderr, "%s: not found\n", mot[0]);
exit(1);
}
printf("Bye\n");
return 0;
}
/* decouper -- decouper une chaine en mots */
void
decouper(char * ligne, char * separ, char * mot[], int maxmot){
int i;
mot[0] = strtok(ligne, separ);
for(i = 1; mot[i - 1] != 0; i++){
if (i == maxmot){
usage("Erreur dans la fonction decouper: trop de mots");
mot[i - 1] = 0;
break;
}
mot[i] = strtok(NULL, separ);
}
}
void usage(char * P) { printf("Usage : %s erreur", P);; exit(1) ;}
Merci de m'aider je bloque vraiment
# Quelques problèmes
Posté par benja . Évalué à 2.
au moment de l'exec par le fils, il "exec" le shell lui même (argv[0]) au lieu du programme entré.
exit(0) après le wait ne devrait pas être là.
les modifications sur stdin/stdout doivent se faire dans le fils (avant l'exec). Là tu le fais dans le père/shell et, en plus, après le fork, ce qui n'a aucune incidence sur le fils, ce n'est certainement pas ce que tu veux..
surcharger l'argv du main pour construire ton argv pour l'execv est, en plus d'être carrément dégueulasse et illisible, dangereux car rien ne t'indique qu'il est bien dimensionné. Construis en un autre à l'aide de malloc! (btw fgets ne devrait pas non plus être utilisé, je l'avais déja dit à ton collègue ou à toi.
conseil: continue de découper ton code en fonctions, par exemple en faire une pour le fork+exec+modification stdou/stdin serait déja pas mal, voire une autre pour construire l'argv ou encore pour "parser" la ligne d'entrée
conseil: utilise "strace -o /tmp/trace -f -t ./shell" afin de savoir quels syscalls sont effectués par ton programme. L'argument "-f" est important, il demande de tracer aussi les fils. Avec le code posté, tu obtiens qq chose du genre (j'ai retirés les lignes non pertinentes).
La première ligne indique le pid. On voit bien que le wait fonctionne bien, mais que le fils (pid 3063) fait une lecture sur stdin (fd=0) et ne quitte qu'au moment on on entre CTRL+D (read=0 => EOF). Note que le fork est en fait le "clone", c'est la primitive noyau ("syscall") que la fonction fork de la libc utilise. Tu peux aussi utiliser ltrace (avec un L pour librairies), qui normalement te donne les noms de fonctions appellées, mais en règle générale, c'est beaucoup plus lent et verbeux, et il faut avoir les infos de débuggages.
[^] # Re: Quelques problèmes
Posté par benja . Évalué à 2. Dernière modification le 06 mars 2016 à 23:04.
Au temps pour moi: évidemment (argument sizeof…) fgets ne souffre pas de ce problème, contrairement à gets.
[^] # Re: Quelques problèmes
Posté par benja . Évalué à 1.
J'oubliais : je pense que tu t'es quelques peu emmêlé les pinceaux au niveau des if et, ce qui n'aide pas, l'indentation n'est pas correcte. Le code après le commentaire "//enfant: exec du programme" n'a rien à faire là, il devrait être dans la clause "else" du if(tmp)… Bref en pseudo code ton programme devrait avoir cette structure:
Ensuite tu as 3 options pour passer tous les paramètres: soit tu utilises des globales, le plus simple dans un premier temps mais le moins facilement "maintenable"; soit tu passes tous les arguments (éventuellement par références), par exemple `pid_t do_exec(const char* bin, const char* stdin, enum redir_mode stdin_redir, const char* stdout, enum redir_mode stdout_redir), soit tu mets tout dans une structure genre "struct commande { const char* binary; …}". C'est cette dernière méthode que je te conseillerais. Tu pourrais avoir alors le corps de ta boucle qui ressemblerait à "struct commande* cmd = parse_commande(); if (cmd) { … //commande_valide… do_exec(cmd); wait(pid); free(cmd);}" ce qui me semble assez lisible. Enfin bon ça dépend un peu du style que l'on vous a conseillé au cours…
[^] # Re: Quelques problèmes
Posté par benja . Évalué à 1. Dernière modification le 07 mars 2016 à 17:07.
Note bien que ce code est buggé, lui aussi :p
Pourquoi ?
Parce que le print() n'a rien à faire dans le fils ! Le stdout/stderr est (potentiellement) déja modifé par set_stdio(). Tu n'as pas d'autre choix que de faire toi-même une recherche exhautive dans path à coups de fstat, au bien tu laisse tomber car c'est sujet à race condition (i.e. on supprimer le programme juste après que tu l'aie trouvé, je ne sais pas si posix prévois une sorte de exec avec le fd du binaire déja ouvert), ou bien tu fais un exit(valeur_magique) et tu regardes après dans le père si le résultat du wait() te renvois cette valeur, mais bon ce n'est pas infaillible car rien n'empêche le programme de renvoyer légitimement cette valeur (je crois que bash combine ces deux méthodes), merci POSIX… ;-)
# Conseils pour se débrouiller
Posté par freem . Évalué à 4. Dernière modification le 06 mars 2016 à 21:28.
Quand on programme et que l'on a un problème (ce qui arrive régulièrement) la meilleure solution reste de reproduire un programme minimal qui reproduise le problème: ça rend le problème plus simple à cerner, et donc à résoudre.
Quand on demande de l'aide sur un forum, il est très important de décrire le problème. Sinon les gens sont moins motivés pour t'aider, vu qu'ils se demandent si tu as réellement cherché (cf. point 1).
Bon, sinon, en vrac:
const char PROMPT[] = "? ";
encore une fois pour une question de typage. Les macros sont utiles, mais quand on peut s'en passer, c'est mieux, parce que quand tu as un problème causé par une macro, c'est une horreur à déboguer. Et en utilisant de véritables constantes, le compilateur est plus à même de t'indiquer des erreurs (via des warnings éventuellement).wait(&status);
fait qu'on se dit que la valeur de status sera utilisée, hors tu ne t'en sers pas. Autant utiliserwait(NULL);
au moins ton intention est claire. Et être clair est important en programmation.Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.