Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

  1. #1
    Candidat au Club
    Retrouver son prompt après un exec() Shell simple personnalisé
    Bonjour,

    Je dois créer un Shell simple. Un prompt affiche et je peux écrire des commandes qui n'existent pas dans le vrais Bash mais qui simule leur action.

    Mais mon problème est que je sors de mon programme et j'aimerai que mon prompt s'affiche à nouveau
    je ne réussi pas à comprendre comment faire pour que suite à un execl() je puisse retrouver mon pompt
    et sortir seulement quand on écrit exit!

    j'utilise un while(1)
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <signal.h>
    #include <string.h>
    #include <sys/wait.h>
     
    int main(int argc, char *argv[]){
     
        int status;
        char buffer[500];
     
        printf("\nDans MON SHELL  écrire \"exit\" pour quitter\n");
     
        int pid = fork();
     
        if(pid == -1){
            perror("Une erreur c'est produite : création du  fork");
            return EXIT_FAILURE;
     
        }
     
        if(pid == 0){
     
            while(1){
                printf("MONSHELL > : ");
     
                scanf("%s", buffer);
     
     
                if (strcmp("exit", buffer) == 0){
                    exit(0);
     
                }else if(strcmp("dir", buffer) == 0){       //je ne retrouve pas mon promp pour entrer par exemple ctr...
     
                    execl("/bin/ls", "ls", "-l", NULL);
     
                }else if(strcmp("ctr", buffer) == 0){  
     
    	    	    execl("/usr/bin/clear", "clear", NULL);
     
    	        }else if(strcmp("environ", buffer) == 0){  
     
    	    	    execl("/usr/bin/env", "env", NULL);
     
    	        }else if(strcmp("echo", buffer) == 0){    //ici si j'écris echo cmd j'aimerai que seulement le cmd affiche....
                    printf("%s\n", &buffer);
     
                }else{
     
                    printf("Commande inconnue ...\n");
                }
            }//fin wihile
     
        }//fin if =0
     
        if(pid > 0){
     
            if(wait(NULL) == -1){
                perror("erreur de wait cote parent\n");
                exit(EXIT_FAILURE);
            }
     
        }//fin if > 0
          return EXIT_SUCCESS;
    }//fin main



    merci beaucoup

  2. #2
    Membre habitué
    es-tu certain qu'on atteigne à un moment le exit() ?
    j'ai l'impression que l'usage de strcmp() ne soit pas approprié dans ce contexte...

  3. #3
    Membre habitué
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <signal.h>
    #include <string.h>
    #include <sys/wait.h>
     
    // Commentaires très bien ^ ^
     
    // Eviter les else if, essayer de concevoir son application pour éviter les branchements conditionnels
    // imbriqués et surtout ne pas utiliser le "else if/elsif"
     
    int main(int argc, char *argv[])
    {
        int status;
        char buffer[500];
     
    		memset(buffer,0,500);
        printf("\nDans MON SHELL  écrire \"exit\" pour quitter\n");
        int pid = fork();
     
        if(pid == -1)
    		{
            perror("Une erreur c'est produite : création du  fork");
            return EXIT_FAILURE;
        }
        if(pid == 0) // processus père
    		{
    			printf("[PERE]");		
             while(1)
    				 {
                printf("MONSHELL > : ");
                //scanf("%s", buffer);   
    						fgets(buffer,25,stdin);
    						printf("\n[DEBUG] -> %s (%s) %d\n",buffer,strcmp("exit",buffer)?"true":"false",strcmp("exit",buffer));
     
    						// Comme on ne sait pas (il y a des fonctions qui permettent de gérer plus facilement les "interpréteurs de commande"
    						// mais ici c'est "à la bonne franquette" ^^
     
    						// on teste les commandes "prises en charge" les unes après les autres, pas la peine
    						// de s'emmerder avec des else et des elsif/else if
     
                if (strstr(buffer,"exit") != NULL)
    						{
    							exit(0);
                }
                if(strstr(buffer,"dir") != NULL)
    						{       //je ne retrouve pas mon promp pour entrer par exemple ctr...
                   execl("/bin/ls", "ls", "-l", NULL);
    						}
    						if(strstr(buffer,"ctr") != NULL)
    						{  
    							execl("/usr/bin/clear", "clear", NULL);
    						}
    						if(strstr(buffer,"environ") != NULL)
    						{  
    							execl("/usr/bin/env", "env", NULL);
    						}
    						if(strstr(buffer,"echo") !=NULL)
    						{    //ici si j'écris echo cmd j'aimerai que seulement le cmd affiche....
    							printf("%s\n", &buffer);
     						}
    					}//fin wihile
     
        }//fin if =0
     
        if(pid > 0) // processus fils
    		{
    				printf("[FILS] ...");
            if(wait(NULL) == -1)
    				{
                perror("erreur de wait cote parent\n");
                exit(EXIT_FAILURE);
            }
            printf("[FILS] end...\n");
        }//fin if > 0
     
    		return EXIT_SUCCESS;
    }//fin main

    Les commentaires ont été ajoutés, j'ai utilisé strstr (qui cherche une sous-chaine dans une chaine)...
    ici strcmp() retourne toujours autre chose que 0... il est tard il fait chaud et je ne saurais pas te dire pourquoi strcmp retourne autre chose que 0.
    strstr() retourne un pointeur sur la première occurence de la chaîne trouvée dans le chaîne, ou NULL si elle n'est fait pas partie.

    Je ne sais pas pourquoi tu fais un fork(), vu que le fils attends le père et que le père lance un execl() qui remplace le code du père par le programme que tu appelles
    du coup automatiquement ton code dans le père (le while(1)...) sera remplacé par le programme exécuté par execl...

  4. #4
    Membre habitué
    Bon !!
    Ton projet m'a tellement passionné que j'ai fait ceci, il faudra que tu l'adaptes, je n'ai tellement plus l'habitude des fork() que je me suis un peu emmelé les pinceaux ^ ^

    Le fork retourne 0 pour indiquer qu'il s'agit du processus "fils" et fourni le pid du processus "enfant" au processus père, je pensais que c'était l'inverse enfin bon...

    (man 3p fork: fork() shall return 0 to the child process and shall return the process ID of the child process to the parent process)

    J'avais complètement oublié que dans le "contexte des forks" il valait mieux "oublier" les fonctions "bufferisées" comme fgets() que je conseille d'utiliser, en général, pour tout ce qui concerne les "saisies au clavier".
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <signal.h>
    #include <string.h>
    #include <stdbool.h>
    #include <sys/wait.h>
     
    int main(void)
    {
    	int 	pidfils,returncode;
    	bool  bCommand=true;
    	char 	*pStrCommand;
    	char 	binarypath[255]="/usr/bin/";
     
    	pStrCommand=calloc(255,sizeof(char));
     
    	fprintf(stdout,	"Programme de test pour vérifier qu'il soit possible d'exécuter des commandes"
    									" tant que \"exit\" ne soit pas encodé...\n");
     
    	while(bCommand==true)
    	{
    		fprintf(stderr,"[SHELL]>\t");
    		//fgets(pStrCommand,255,stdin); avec l'usage de execl il vaut mieux ne pas utiliser des fonctions de type "bufferisé"... 
    		int octetslus=read(STDIN_FILENO,pStrCommand,255);
    		octetslus--;
    		*(pStrCommand+octetslus)='\0'; // on remplace le '\n' par '\0' sinon ça ne marchera pas avec execl... 		
     
    		pidfils=fork();
    		if(!pidfils) // FILS
    		{
    			fprintf(stderr,"\n[DEBUG] FILS...\n");
    			fprintf(stderr,"[FILS] pid %05d\n",pidfils);
    			fprintf(stderr,"[FILS] pStrCommand %s\n",pStrCommand);
     
    			if(strstr(pStrCommand,"exit")!=NULL) exit(1);			
    			strcat(binarypath,pStrCommand);
    			fprintf(stderr,"[FILS] binarypath %s\n",binarypath);
    			// A partir d'ici le Code Segment du processus sera remplacé par "ps" (ou n'importe quel autre programme exécutable)
    			execl(binarypath,pStrCommand,NULL);	 
     
    		}
     
    		// PERE
     
    		int code;
     
    		returncode=wait(&code);
    		if(returncode!=-1) 
    		{
    			fprintf(stderr,"[PROC DEATH] id: %05d\n",returncode);
    			if(code==256) bCommand=false; // exit(1) -> 256, exit(2) -> 512, ... exit(-1) -> 255, ... c'est un peu bizarre ^^  
    		}
    		fprintf(stderr,"[PERE] bCommand %s\n",bCommand?"true":"false");
    		strcpy(binarypath,"/usr/bin/");
    	}
    }


    Concernant la partie...

    int octetslus=read(STDIN_FILENO,pStrCommand,255);
    octetslus--;
    *(pStrCommand+octetslus)='\0'; // on remplace le '\n' par '\0' sinon ça ne marchera pas avec execl...
    ...comme nous sommes en "non bufferisé", la fonction read() incorpore le '\n' qui correspond à l'appui de la touche <RETURN> et ce caractère va nous empêcher d'exécuter execl...
    Du coup: via l'arithmétique des pointeurs je m'arrange pour placer le '\0', fin de chaîne, à la place du '\n'.

    *(pStrCommand+octetslus) <-> à partir du pointeur pStrCommand (une adresse en mémoire) tu te déplaces de octetslus * la taille du type de pStrCommand (ici 1 octet) puis à cet endroit tu y places '\0'.
    pStrCommand est l'adresse de base et octetslus est l'offset (le déplacement en mémoire) dépendant de la taille du type de pStrCommand.
    La petite étoile (dé-référencement) indique que c'est à l'adresse spécifiée qu'il faut placer le '\0'.

    C'était bien cool, j'ai pu renouer avec la fonction fork(), me rappeller que récupérer le "exit code" c'était un peu bizarre comme mécanisme, ...

  5. #5
    Expert éminent sénior
    Bonjour
    Citation Envoyé par hurukan Voir le message
    Le fork retourne 0 pour indiquer qu'il s'agit du processus "fils" et fourni le pid du processus "enfant" au processus père, je pensais que c'était l'inverse enfin bon...
    Pourtant ça me semble à moi inoubliable ce genre de truc. Parce que si c'était l'inverse, le fils aurait un numéro qui lui sert à que dalle (son propre pid qu'il connait déjà via getpid()) et le père aurait un 0 qui lui sert aussi à que dalle et en plus ne pourrait pas connaitre le pid de son fils.

    Citation Envoyé par hurukan Voir le message
    if(code==256) bCommand=false; // exit(1) -> 256, exit(2) -> 512, ... exit(-1) -> 255, ... c'est un peu bizarre ^^
    C'est pas bizarre, c'est "construit mathématiquement" pour stocker plusieurs informatiions sur un int.

    Un processus peut s'arrêter de deux façons
    • via exit(n), n compris entre 0 et 255
    • via kill(m), m compris entre 1 et 255

    Chaque nombre "n" et "m" n'occupant qu'un octet, les deux informations accolées tiennent sur un int. Ainsi wait(&status) va attendre la fin d'un fils, et si ce fils s'est terminé par exit(n) va positionner le "n" dans les 8 premiers bits de "status" et si ce fils a été tué via kill(m), va positionner ce "m" dans les 8 derniers bits de "status".
    Ensuite il suffit de regarder. Si les 8 derniers bits sont à 0 c'est que c'était exit(n) et pour retrouver "n" suffit de faire un décalage, sinon c'était kill(m) (m ne pouvant pas être égal à 0) et pour retrouver "m" suffit là de faire un masque. Et en plus comme il est malsain d'écrire un code basé sur des conventions qui peuvent évoluer, il existe 4 macros qu'on peut utiliser pour gérer le truc et qui, elles, suivront l'évolution éventuelle
    • WIFEXITED(status) qui renvoie vrai si c'était exit(n)
    • WEXITSTATUS(status) qui renvoie la valeur du "n"
    • WIFSIGNALED(status) qui renvoie vrai si c'était kill(m)
    • WTERMSIG(status) qui renvoie la valeur du "m"

    Evidemment afficher directement "status" sans connaitre ces détails rend évidemment la chose bizarre (toutefois tu remarqueras que toutes les valeurs que tu montres dans ton exemple sont toutes égales à n * 256, ce qui équivaut à n décalé à gauche de 8 bits).
    Ne reste que le exit(-1) que tu dis valoir 255 à expliquer mais ça ça reste effectivement inexplicable parce que faux. -1 sur un bit vaut 0xff (255) et 0xff placé sur les 8 bits de gauche d'un int donnent 0xff00 soit 255 * 256 = 65280.

    Citation Envoyé par hurukan Voir le message
    Du coup: via l'arithmétique des pointeurs je m'arrange pour placer le '\0', fin de chaîne, à la place du '\n'.
    Dangereux. Parce que si l'utilisateur entre une commande qui fait pile poil 255 caractères, ta variable pStrCommand ne contient pas de '\n' et tu effaces donc le dernier caractère de la commande. Ok, peu probable je l'admets. Mais s'il existe une solution alternative qui évite cette probabilité qui, bien que très faible, n'en est pas moins concrète, alors autant l'utiliser. Et elle existe.
    Accessoirement j'en profite car c'est sur la même variable, pourquoi "calloc" pour une zone de taille fixe ??? D'autant plus que t'as oublié le free().
    Code c :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    #define SZ_COMMAND				(255)
    ...
    	char pStrCommand[SZ_COMMAND + 1];
    	char *pt;
    	pStrCommand[SZ_COMMAND]='\0';				// Oui, je n'ai besoin que d'un seul '\0' pour avoir une string... mais l'important est qu'il soit judicieusement positionné !!!
    	...
    	int octetslus=read(STDIN_FILENO, pStrCommand, SZ_COMMAND);	// Je t'ai conservé octetslus bien que peu utile...
    	if ((pt=strchr(pStrCommand, '\n')) != NULL) *pt='\0';

    Voilà. Désolé de te casser ta belle arithmétique des pointeurs qui en fait n'est pas vraiment utile. Toutefois elle était inutile aussi dans ton code originel. En effet, on utilise l'arithmétique des pointeurs dans des boucles de traitement, pour éviter de faire "n" fois le décalage. Mais ici, sans boucle, entre *(pStrCommand+octetslus)='\0' où tu fais une fois un décalage explicite, et pStrCommand[octetslus]='\0' qui fait exactement la même chose avec un décalage implicite là aussi qui n'est fait qu'une fois, je préfère la seconde écriture. Ben oui, à deux écritures équivalentes, mieux vaut privilégier la plus lisible...

    Citation Envoyé par hurukan Voir le message
    strcpy(binarypath,"/usr/bin/");
    Inutile. La variable n'a pas changé. Toutefois puisque j'y suis, tu concatènes une chaine pouvant aller jusqu'à 255 caractères à une chaine non nulle et tout ça dans une variable ne faisant, elle aussi, que 255 caractères...?
    Code c :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    #define SZ_PATH				(255)
    	char binarypath[SZ_PATH + SZ_COMMAND + 1]="/usr/bin/";

    Ok, ça fait riche d'utiliser 255 caractères pour en stocker 15 mais c'est pas la mort, on peut gaspiller 240 octets (et puis si tu veux définir à moins...). Sinon l'autre solution est de faire là de l'allocation dynamique.
    Code c :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    #define PATH				("/usr/bin/")
    	char *binarypath=malloc(strlen(PATH) + SZ_COMMAND + 1) * sizeof(*binarypath));
    	...
    	sprintf(binarypath, "%s%s", PATH, pStrCommand);

    plus rajouter le test de réussite du malloc (ça reste impératif dans un projet réel) et ne pas oublier le free(binarypath) à la fin. Je préfère gaspiller 240 octets.
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site

  6. #6
    Candidat au Club
    Merci beaucoup pour toutes les réponses!!!

    je vais prendre le temps de tout lire, de comprendre et d'essayer de coder le mieux que je peux et je vous reviens le plus vite possible!

    avec toutes les lectures et ce que vous me dites, je commence à comprendre un peu mieux ... j'ai de petites lumières qui allument

    merci encore!

  7. #7
    Candidat au Club
    Bonsoir !
    Bon je dois me décider à choisir parmi tout le code 1-ce que je comprend et 2-ce qui fonctionne le mieux

    Donc, j'ai opté pour ce qui suit... par contre toujours pas réussi à sortir avec mon exit(0)...
    des qu'il y a un wait() je ne sors pas... c'est peut-être une piste... je vais essayer
    j'ai essayé avec les signaux mais je n'ai pas très bien compris leurs comportements...
    c'est le père qui lance un signal quand le fils lui transmet son état?
    Une fois attrapé ou un message de l'état du fils est rendu au père on fait quoi pour sortir du programme?
    j'ai essayé et essayé lu et lu mais je ne vois pas exactement quoi faire...

    en attendant voici le code

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
     
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <signal.h>
    #include <string.h>
    #include <sys/wait.h>
     
     
    void sortie(int n, int status);
     
    void sortie(int n, int status){
        int w = n;
        printf("dans sortie\n");
     
                    do{
                       if (WIFEXITED(status)) {
                           printf("terminé, code=%d\n", WEXITSTATUS(status));
                       } else if (WIFSIGNALED(status)) {
                           printf("tué par le signal %d\n", WTERMSIG(status));
                       } else if (WIFSTOPPED(status)) {
                           printf("arrêté par le signal %d\n", WSTOPSIG(status));
                       } else if (WIFCONTINUED(status)) {
                           printf("relancé\n");
                       }
                   } while (!WIFEXITED(status) && !WIFSIGNALED(status));
     
    }
     
    int main(int argc, char *argv[])
    {
        int status;
        char buffer[500];
     
     
        memset(buffer,0,500);   
        if( argc == 2){
        printf("argc =2 argv[1] = nom du fichier a traiter %s\n");
     
     
        }
        printf("\nDans MON SHELL écrire \"exit\" pour quitter\n");
     
            if (strstr(buffer,"exit") != NULL)
            {
                exit(0);
            }     
     
     
        while(1){
     
            printf("MONSHELL > : ");
     
            fgets(buffer,25,stdin);
            int w;
             int pid = fork();
     
            if(pid == 0){
     
                if (strstr(buffer,"exit") != NULL)
                {
                    printf("PID : %d PPID : %d\n", getpid(), getppid());
                    w = waitpid(pid, &status, WUNTRACED | WCONTINUED);
                    sortie(w, status);
     
                }
                if(strstr(buffer,"dir") != NULL)
                {
                    execl("/bin/ls", "ls", "-l", NULL);
                }
     
                if(strstr(buffer,"ctr") != NULL)
                {
                    execl("/usr/bin/clear", "clear", NULL);
                }
     
                if(strstr(buffer,"environ") != NULL)
                {
                    execl("/usr/bin/env", "env", NULL);
                }
     
                if(strstr(buffer,"echo") !=NULL)
                {
                    printf("%s\n", &buffer[5]);
                }
                else
                {
                    printf("Commande inconnue ...\n");
                }
     
            }//fin if==0
     
     
        if(pid > 0){
            wait(&pid);
        }
     
     
        if(pid == -1)
        {
            perror("erreur fork -1");     
            exit(EXIT_FAILURE);
        }
     } //fin while
     
        return EXIT_SUCCESS;
    }//fin main

  8. ###raw>post.musername###
    Candidat au Club
    Bonsoir,

    Bon, voici ce que j'ai réussi à faire... pas grand chose mais je dois me fixer sur un code que je comprends le plus possible pour pouvoir avancer
    Ce n 'est pas par manque de lectures et tests et casse tête et tout ce que vous voulez

    j'ai trouvé comment sortir avec un exit pour 3 appels sur 4 ! quand je fais le echo titu par exemple, ça affiche bien juste titu mais il faut que je fasse 2 exit pour sortir
    je sais ... je pense... que la cause et encore le \n à la fin de titu
    pensez-vous qu'il est possible de retirer le \n et afficher avec execl(cut ou expr) ?

    En plus, je ne saisie pas trop le fonctionnement des signaux

    Désolée, je me perds un peu dans les explications qu'on m'a données
    merci pour votre aide!

    voici le code

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
     
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <signal.h>
    #include <string.h>
    #include <sys/wait.h>
     
     
    int status;
     
     
    int main(int argc, char *argv[])
    {
        char buffer[500];
     
     
        memset(buffer,0,500);   
        if( argc == 2){
        printf("argc =2 argv[1] = nom du fichier a traiter %s\n", argv[1]);
     
     
        }
        printf("\nDans MON SHELL écrire \"exit\" pour quitter\n");
     
     
        while(1){
     
            printf("MONSHELL > : ");
     
            fgets(buffer,25,stdin);
     
             int pid = fork();
     
            if(pid == 0){
        		if (strstr(buffer,"exit") != NULL)
                	{
                    	printf("PID : %d PPID : %d\n", getpid(), getppid());
     
             		exit(0);
                }
     
                if(strstr(buffer,"dir") != NULL)
                {
                    execl("/bin/ls", "ls", "-l", NULL);
                }
     
                if(strstr(buffer,"ctr") != NULL)
                {
                    execl("/usr/bin/clear", "clear", NULL);
                }
     
                if(strstr(buffer,"environ") != NULL)
                {
                    execl("/usr/bin/env", "env", NULL);
                }
     
                if(strstr(buffer,"echo") !=NULL)
                {
     
    		//char *argsexpr = "\\(.*\\).$";
     
    		//execl("/usr/bin/expr", "&buffer[5]", ":", "argsexpr", NULL);
     
    		//expr "bonjour" : "\(.*\).$"
    		//execl("/usr/bin/cut", "cut", "-f3-6", "&buffer[5]", NULL);
                   printf("%s\n", &buffer[5]);
                }
                else
                {
                    printf("Commande inconnue ...\n");
                }
     
            }//fin if==0
     
     
     
    	int ret = waitpid(pid, &status, 0);
    	printf("ret = %d\n", ret);    
    	if(ret == -1){
            	perror("echec waitpid du prc enfant 1");
        	}else{
     
            	printf("le execl() est termine prc = %d\n", getpid());
    		if (strstr(buffer,"exit") != NULL){
    			exit(0);
    		}
    	}
     
     
        if(pid == -1)
        {
            perror("erreur fork -1");     
            exit(EXIT_FAILURE);
        }
     } //fin while
     
        return EXIT_SUCCESS;
    }//fin main
      0  0

  9. #9
    Membre habitué
    Quand je suis tombé sur ton projet il était tard et mon cerveau n'était plus très fonctionnel, j'ai tout de même pris un peu de temps pour risquer quelque chose.
    Alors je ne prétend pas du tout avoir la maîtrise du sujet, je suis enseignant et non professionnel en programmation "système" en C.

    Le fait que tu développes sur Linux je trouve ça bien cool et cela m'a motivé ^^

    Comme la dernière fois que j'avais fait un fork() ça remontait en 2015/2016 je n'avais plus trop de "souvenirs" clairs quant à "comment cela fonctionne".
    J'ai tenté de proposer "quelque chose qui marche" fait à la "va vite", avec quelques "libertés" quant à la manière d'aborder la problématique.

    Je ne pense pas qu'un shell soit programmé de la sorte, il faudrait que j'observe le code source du bash... du coup ton projet est surtout intéressant pour
    l'aspect "processus" (création de processus), pour l'aspect "recouvrement de processus" (utilisation des fonctions exeXXX())...

    Je n'ai pas encore regardé tes essais... je me suis arrêté sur ta demande liée "aux explications".

    Cependant d'entrée de jeu "il est conseillé de ne pas utiliser fgets() quand on "joue" avec les fonctions execXXX et le fork()", je ne retrouve pas la source mais je pense
    que cela vient d'un bouquin sur "la programmation système sous Linux". Je vais retrouver cela.
    Du coup il vaut mieux utiliser la fonction read()... qui fait la même chose que fgets() sauf qu'il faille enlever le '\n'.

    Tu peux le faire simplement grâce à la fonction read() qui va retourner le nombre de caractères lus, le '\n' se trouvera toujours à la dernière position...

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    octetslus=read(...);
    buffer[octetslus-1]='\0'; // remplace le '\n' par '\0'


    oups !! sve@r va crier...
    Il est préférable d'utiliser strchr() qui va chercher la première occurence du '\n' dans ta chaîne... elle retourne un pointeur sur la position dans la chaîne de celui-ci

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    char *pChercher=strchr(buffer,'\n');
    if(pChercher!=NULL)
    {
       *pChercher='\0'; // ...remplace le '\n' par le '\0'. 
    }


    si octetslus, dans la "mauvaise" approche, est à -1, par exemple, cela risque de poser des soucis lors de l'exécution du programme.
    buffer[-2] <-- en général ça ne se passe pas bien (il y a de fortes chances que cela ne se passe pas bien par rapport aux résultats escomptés) surtout si read() indique une erreur avec -1.

    ...essaye déjà en remplaçant la fonction fgets() par read()...

    Concernant les signaux (moi je pense à SIGXXXXXX), je ne sais pas si Sve@r sera de mon avis: je ne suis pas certain du tout qu'ils soient nécessaires... ou qu'ils puissent apporter quoi que ce soit
    comme amélioration du comportement de ton programme par rapport à ce que tu veux faire, si j'ai bien compris: simuler un shell.

    Maintenant si tu parles de la récupération du code de sortie d'un exit() ou d'un kill() sve@r a été sympa de nous indiquer des macros de sys/wait.h, que je ne connaissais pas, qui permettent de
    s'en sortir un peu mieux pour récupérer le "code de sortie" d'un processus.

    Bon courage ^^

  10. #10
    Expert éminent sénior
    Citation Envoyé par billy333 Voir le message
    Bon, voici ce que j'ai réussi à faire... pas grand chose mais je dois me fixer sur un code que je comprends le plus possible pour pouvoir avancer
    Au-moins ton code est maintenant lisible. Parce que ce matin je suis venu et je suis reparti direct.

    Citation Envoyé par billy333 Voir le message
    En plus, je ne saisie pas trop le fonctionnement des signaux
    Ca on s'en bat le steak. Un signal est un évènement extérieur à un processus, un truc qu'on lui envoie dans la tronche via kill() et où le processus receveur, s'il a de la chance, peut arriver à le traiter sinon il meurt (enfin ça dépend du signal car il y a quelques signaux qui ne le font pas mourir). Ici ça n'a aucune utilité. Peut-être plus tard, tu pourras protéger ton shell contre des signaux mais c'est pas la priorité.

    Citation Envoyé par billy333 Voir le message
    voici le code
    Alors déjà tu commences par tester si buffer contient "exit" alors que 3 lignes avant tu l'as mis à 0. Fatalement il ne contient pas exit. Ensuite ta fonction "sortie()" fait une boucle en fonction de l'état d'une variable qui ne change pas. Ca donne au choix une boucle infinie ou alors pas de boucle (ou ici une seule itération vu que tu fais un do...while). Bref la boucle est totalement inutile.

    Et enfin si tu veux sortir via "exit", il te faut tester "exit" avant de faire le fork. C'est le père qui s'arrête, pas le fils.

    PS: au fait, quand tu génères un fils via fork(), il faut penser à le faire s'arrêter via exit(). Surtout si la génération se fait dans une boucle !!!

    Citation Envoyé par hurukan Voir le message
    Quand je suis tombé sur ton projet il était tard et mon cerveau n'était plus très fonctionnel, j'ai tout de même pris un peu de temps pour risquer quelque chose.
    Très bien. Je ne t'ai absolument rien reproché, juste montré tes erreurs (ou disons les maladresses et les dangers que ça amène) et la façon de les corriger.

    Citation Envoyé par hurukan Voir le message
    Du coup il vaut mieux utiliser la fonction read()... qui fait la même chose que fgets() sauf qu'il faille enlever le '\n'.
    fgets() lui aussi récupère le '\n'.

    Citation Envoyé par hurukan Voir le message
    Tu peux le faire simplement grâce à la fonction read() qui va retourner le nombre de caractères lus, le '\n' se trouvera toujours à la dernière position...
    S'il s'y trouve ce qui n'est pas garanti à 100% !!!

    Citation Envoyé par hurukan Voir le message
    Il est préférable d'utiliser strchr() qui va chercher la première occurence du '\n' dans ta chaîne... elle retourne un pointeur sur la position dans la chaîne de celui-ci
    Yes. A condition impérative que ta zone de stockage soit une chaine, donc qu'elle contienne un '\0'. D'où ma méthode où je la taille à un caractère de plus que la taille "utile" et où je mets un '\0' dans ce caractère en plus (qui ne sera, lui, jamais modifié).

    Citation Envoyé par hurukan Voir le message
    Concernant les signaux (moi je pense à SIGXXXXXX), je ne sais pas si Sve@r sera de mon avis: je ne suis pas certain du tout qu'ils soient nécessaires... ou qu'ils puissent apporter quoi que ce soit
    Totalement d'accord.
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site

  11. #11
    Membre habitué
    Très bien. Je ne t'ai absolument rien reproché, juste montré tes erreurs (ou disons les maladresses et les dangers que ça amène) et la façon de les corriger.
    J'avais bien compris, j'aime bien sortir de ma "zone de confort" ^^
    Il y a plein de choses auxquelles on ne pense pas et puis on est fâché sur soi-même parce que on "oublie" la plupart des cas de figure qui se révêlent quand ton programme bugge ^^

    On apprend ^^

    EDIT:

    fgets() lui aussi récupère le '\n'.
    My bad -> je m'en servais pour introduire des nombres convertis la plupart du temps, et pour les chaînes de caractères je passe par des fonctions de mon cru qui l'enlèvent automatiquement :{
    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    while(nbreCar<maximum)
    	{
    		*pTmp=getch();								// c'est complexe je sais...
     
    		if(*pTmp==10 || *pTmp==27) break;  // enlève le '\n', ou le caractère ESC de la chaîne...

  12. #12
    Expert éminent sénior
    Ok, j'ai fait ma propre version.

    Code c :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
     
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <signal.h>
    #include <string.h>
    #include <sys/wait.h>
     
    void sortie(int);
    void saisie(const char* const, char* const, unsigned short);
     
    void sortie(int status) {
    	printf("dans sortie\n");
    	if (WIFEXITED(status))
    		printf("terminé, code=%d\n", WEXITSTATUS(status));
    	else if (WIFSIGNALED(status))
    		printf("tué par le signal %d\n", WTERMSIG(status));
    	else if (WIFSTOPPED(status))
    		printf("arrêté par le signal %d\n", WSTOPSIG(status));
    	else if (WIFCONTINUED(status))
    		printf("relancé\n");
    }
     
    void saisie(const char* const prompt, char* const buffer, unsigned short size) {
    	fputs(prompt, stdout);
    	fflush(stdout);
    	int n=read(STDIN_FILENO, buffer, size);
    	if (buffer[n-1] == '\n') buffer[n-1]='\0';
    }
     
    #define SZ_BUF		(500)
    int main(int argc, char *argv[]) {
    	printf("\nDans MON SHELL écrire \"exit\" pour quitter\n");
     
    	char buffer[SZ_BUF + 1];
    	buffer[SZ_BUF + 1]='\0';
     
    	while(1) {
    		saisie("MON SHELL >", buffer, SZ_BUF);
    		if (strstr(buffer, "exit") != NULL) break;
     
    		int pid=fork();
     
    		if (pid == 0) {
    			// Fils
    			if (strstr(buffer,"dir") != NULL)
    				execl("/bin/ls", "ls", "-l", NULL);
     
    			if (strstr(buffer,"ctr") != NULL)
    				execl("/usr/bin/clear", "clear", NULL);
     
    			if (strstr(buffer,"environ") != NULL)
    				execl("/usr/bin/env", "env", NULL);
     
    			if (strstr(buffer,"echo") != NULL)
    				printf("%s\n", &buffer[strlen("echo") + 1]);
    			else
    				printf("Commande inconnue ...\n");
    			exit(0);
    		}
     
    		// Père
     		int status;
    		wait(&status);
    		sortie(status);
    	}
     
    	return EXIT_SUCCESS;
    }
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site

  13. #13
    Candidat au Club
    Bonjour!

    Et oui, j'avais remarqué que les signaux ne me servaient pas à grand choses pour l'instant...
    encore merci pour le code!
    je suis en train de regarder attentivement pour bien comprendre
    finalement toutes les routes mènent à Rome.. il y en a des plus courtes que d'autres!

    j'ai encore pleins de questions...

    je continue et je vous reviens
    merciiii

  14. #14
    Candidat au Club
    Bon... je pense que je vais pleurer! ma parole! je tourne en rond comme un chien qui se mord la queue! je m'impressionne moi-même
    alors... après avoir passé toute la journée sur le même problème...
    j'ai besoin de votre aide!
    vraiment je ne saisi pas les signaux ni le vrais comportement d'un fork ... je pense bien que mon problème c'est ça!
    alors
    voici mon code qui ne fonctionne pas
    j'ai deux fois mon prompt qui apparaît...au moins cette fois-ci ça ne fait pas planter mon ordi...
    même avec le code ici-bas parfois il y a des processus qui restent vivant ... je le vois avec $ps
    je kill..

    une autre question:
    j'ai fait un makefile super simple pour que mon script puisse se lancer peu importe l'emplacement dans le dossier home de l'usager
    pourriez-vous SVP, me dire s'il fonctionne? je pense que oui mais bon ... on ne sait jamais
    MERCI!

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    .PHONY: all
     
    	export PATH=$PATH:/tp1/
    VPATH=~/.batshrc
     
    monshell: monshell.c
    	gcc -Wall -o  monshell monshell.c
     
     
    clean:
    	rm -rf monshell


    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
     
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <signal.h>
    #include <string.h>
    #include <sys/wait.h>
     
    void sortie(int);
    void saisie(const char* const, char* const, unsigned short);
     
    void sortie(int status) {
    	printf("dans sortie\n");
    	if (WIFEXITED(status))
    		printf("terminé, code=%d\n", WEXITSTATUS(status));
    	else if (WIFSIGNALED(status))
    		printf("tué par le signal %d\n", WTERMSIG(status));
    	else if (WIFSTOPPED(status))
    		printf("arrêté par le signal %d\n", WSTOPSIG(status));
    	else if (WIFCONTINUED(status))
    		printf("relancé\n");
    }
     
    void saisie(const char* const prompt, char* const buffer, unsigned short size) {
    	fputs(prompt, stdout);
    	fflush(stdout);
    	int n=read(STDIN_FILENO, buffer, size);
    	if (buffer[n-1] == '\n') buffer[n-1]='\0';
    }
     
    #define SZ_BUF		(500)
     
    int main(int argc, char *argv[]) {
     
        printf("\nDans MON SHELL écrire \"exit\" pour quitter\n");
     
        int status; 
    	char buffer[SZ_BUF + 1];
    	buffer[SZ_BUF + 1]='\0';
     
        int pid1=fork();
    	while(1){
     
            saisie("MON SHELL >", buffer, SZ_BUF);
     
            if (strstr(buffer, "exit") != NULL) break;
     
            int pid=fork();
     		if (pid == 0) {
     
    /*---------------------------------------------------*/
     
                if (strstr(buffer,"pause") != NULL){
                    if(pid1 == -1){
                        perror("Echec du premier fork()");
                    }
     
                    if(pid1 == 0){  
                        printf("Pour continuer taper 'ENTER' au clavier.\n");
            	    }
                    pause();
    			    printf("status status %d - PID %d - ppid %d \n", status, getpid(), getppid());
                }//fin pause
    /*------------------------------------------------------------------*/
     
                // Fils
                if (strstr(buffer,"dir") != NULL)
    			    execl("/bin/ls", "ls", "-l", NULL);
     
    			if (strstr(buffer,"ctr") != NULL)
    				execl("/usr/bin/clear", "clear", NULL);
     
    			if (strstr(buffer,"environ") != NULL)
    				execl("/usr/bin/env", "env", NULL);
     
    			if (strstr(buffer,"echo") != NULL)
    				printf("%s\n", &buffer[strlen("echo") + 1]);
    			else
    				printf("Commande inconnue ...\n");
    			exit(0);
    		}//fin PID
     
            // Père
    		wait(&status);
    	//	sortie(status);
    	}
     
    	kill(pid1, SIGTERM);
    	return EXIT_SUCCESS;
    }

  15. #15
    Expert éminent sénior
    Citation Envoyé par billy333 Voir le message
    vraiment je ne saisi pas les signaux ni le vrais comportement d'un fork ... je pense bien que mon problème c'est ça!
    Exact. En dehors d'être devenu imbitable (tu fais un second fork puis ensuite tu vas tester le premier) ton code est devenu explosif. Tu fais un fork() dans une boucle !!!

    Le fork crée un nouveau procesuss qui récupère et exécute tout le code qui se trouve placé après. Exemple: fork(); printf("Hello %d\n", getpid()) et tu auras 2 fois "hello" mais avec un numéro différent (celui de chaque processus).

    Grâce à sa valeur renvoyée, tu peux savoir si tu es dans le père ou dans le fils et tu peux programmer alors des traitements différents dans un ou l'autre cas. Mais il ne faut pas oublier que même si tu détectes qui est qui, tout le code reste quand-même visible des deux processus
    Code c :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    if ((pid=fork()) == 0) {
    	// Ici on est dans le fils. Le père voit ce bloc mais lui il n'y entre pas car pour lui pid ne vaut pas 0
    	printf("Fils %d de %d\n", getpid(), getppid());
    } else {
    	// Ici on est dans le père. Le fils voit ce bloc mais lui n'y entre pas car pour lui pid vaut 0
    	printf("Père %d de %d\n", getpid(), pid);
    }
     
    // Ici les deux processus voient ce code. Et comme il n'y a aucune discrimination, les deux vont l'excuter
    printf("Hello %d\n", getpid());


    Donc si tu mets ça dans une boucle, première itération 2 processus, puis le fils recommence la boucle (et son père aussi) donc seconde itération 4 processus etc etc etc. C'est ce qu'on appelle un "fork bomb".

    Donc avec un fork, tu n'as plus le droit ensuite de quitter les alternatives if/else sous peine de voir tout code situé en dehors être exécuté 2 fois... sauf si tu prends soin d'arrêter l'un des deux via exit() (et généralement c'est moins con d'arrêter le fils)
    Code c :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    if ((pid=fork()) == 0) {
    	// Ici on est dans le fils. Le père voit ce bloc mais lui il n'y entre pas car pour lui pid ne vaut pas 0
    	printf("Fils %d de %d\n", getpid(), getppid());
    	exit(0);			// Le fils s'arrête
    }
    // Le "else" devient inutile
    // Ici on est dans le père. Le fils est mort et donc ne voit même pas ce code
    printf("Père %d de %d\n", getpid(), pid);		// Même si le fils est mort, pid conserve sa mémoire
    printf("Hello %d\n", getpid());

    Dans ce cas seulement (si le fils s'arrête) alors t'as le droit de mettre fork() dans une boucle.

    Pour les signaux là t'as bien compris. kill(pid, sig) enverra le signal "sig" au processus "pid". Ca ça fonctionne. Ensuite ce qui est plus chaud, c'est programmer ledit processus pour qu'il réagisse de façon particulière s'il reçoit ledit signal (une fois je me suis amusé à faire un morse entre 2 processus) mais on n'en est pas encore là.

    Sinon je ne pige absolument rien à ce que tu veux faire. Pourquoi 2 fork() ???

    Et accessoirement si tu l'indentais proprement, cela serait quand-même plus lisible. Ecrire un code c'est d'abord l'écrire pour qu'il soit facilement lu dionc facilement compris. Si je dois réfléchir 2h sur une ligne à savoir à quel bloc elle appartient parce que t'as eu la flemme d'écrire propre, ben je préfère autant ne pas avoir à réfléchir et te laisser te démerder tout seul.

    Citation Envoyé par billy333 Voir le message
    j'ai fait un makefile super simple pour que mon script puisse se lancer peu importe l'emplacement dans le dossier home de l'usager
    pourriez-vous SVP, me dire s'il fonctionne? je pense que oui mais bon ... on ne sait jamais
    Je crois que tu n'as pas saisi le but d'un Makefile. Son but n'est pas de positionner un PATH. Le but d'un Makefile est de décrire les règles de dépendances entre les extensions (comment faire pour passer d'un .i à un .q puis d'un .q à un .tutu puis d'un .tutu à un .titi puis d'un .titi à un .c puis d'un .c à un exécutable).
    Donc tu décris dans le Makefile les règles puis les actions à faire entre chaque transition. Ensuite il te suffit de demander une cible (ex make xxx.titi) et si la règle "comment faire le ".titi" est décrite et que le fichier source (ici xxx.tutu puisque la règle est de faire un .titi à partir d'un .tutu) est présent, alors l'action sera faite. S'il n'est pas présent alors make regardera d'abord s'il peut faire le "xxx.tutu" à partir du xxx.q et etc.

    Pour pouvoir appeler un exécutable situé dans un dossier "XXX" depuis n'importe où dans l'arborescence, il suffit d'intégrer le dossier "XXX" dans la variable d'environneemnt PATH et ça ça se fait soit depuis le fichier .profile (qui est lu quand tu te connectes) soit depuis le fichier .bashrc (qui est lu à chaque fois que tu ouvres une fenêtre shell)

    Sinon oui pour ce qu'il est censé faire (compiler ton code) oui il est bon.
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site