Forum Programmation.shell Commande qui ne fonctionne pas

Posté par  . Licence CC By‑SA.
Étiquettes :
1
2
déc.
2015

Bonjour,
J'aimerais une explication à propos de la fonction moncd en C que je compile et j'exécute par la suite sous shell ou bash.La fonction moncd permet de changer de répertoire courant.
Malheureusement après compilation, ma commande ./moncd ne fonctionne ni sous le shell ni sous bash. Dès que je l'exécute, je n'ai aucun effet et directement c'est le prompt.
La fonction est la suivante:

    # include <stdio.h>
    # include <unistd.h>
    # include <stdlib.h>

    int
    main(int ac, char * av[]){
      char * dir;
      int t;                             
      if (ac < 2){
        dir = getenv("HOME");
        if (dir == 0)
          dir = "/tmp";
      } else if (ac > 2){
        fprintf(stderr, "usage: %s [dir]\n", av[0]);
        return 1;
      } else
        dir = av[1];

      t = chdir(dir);
      if (t < 0){
        perror(dir);
        return 1;
      }
      return 0;
    }

Merci de m'aider à comprendre.

  • # ça fonctionne

    Posté par  . Évalué à 5.

    Ton programme fonctionne très bien, d'ailleurs si tu rajoute un getcwd() et affiche le résultat tu verra que le répertoire courant a bien changé à l'intérieur de ton programme.

    Et si tu regarde sur ton système tu ne trouvera pas de commande /bin/cd car cette commande est interne à ton shell (bon /bin/cd existe dans la norme POSIX, mais fait la même chose que ton programme)

    • [^] # Re: ça fonctionne

      Posté par  . Évalué à 1. Dernière modification le 03 décembre 2015 à 23:30.

      Merci pour la réponse. Donc si j'ai bien compris en gros la commande cd est une commande interne à mon shell.
      Mais comment faire justement pour constater ce changement ?
      Moi je n'arrive malheureusement pas à le constater.

      Quand par exemple j'exécute j'ai ce résultat:

      roger@roger-1005PX:~/Bureau$ gcc -g -Wall cp-moncd.c -o moncd
      roger@roger-1005PX:~/Bureau$ ./moncd   #exécution de la commande
      roger@roger-1005PX:~/Bureau$_          #prompt

      Si elle fonctionne, comment dois-je faire pour constater ce fonctionnement ? Je dois modifier ma fonction en rajoutant la commande getcwd() à l'intérieur ? Si c'est bien cela, à quel niveau de la fonction je dois l'insérer. Si c'est possible d'avoir une illustration avec le code, ça faciliterait ma compréhension.

  • # ça marche

    Posté par  . Évalué à 3.

    Bah ça marche, mais le cwd est uniquement modifé pour ton programme (et ses fils, éventuellement).
    Immagine le bordel que ça serait si un processus pouvait modifier l'environement du processu qui l'a lancé ?
    Note bien que "cd" est une command interne au shell. Au contraire de ms-dos/windows, qui est donc un système qui ne fourni pas cette "imperméabilité" de l'environement d'exécution du parent par son fils ou sous-fils.

    Bref soit tu lance un nouveau shell depuis ton programme, soit tu demande au shell qui lance ton programme d'exécuter quelle que chose à la suite, genre output=`./monprog|tail -1`; ret=$?; if test $ret eq 42 then eval cd $output; fi En mieux tu peux utiliser un autre descripteur de sortie pour ne pas devoir réserver ton stdout à cette seule fin. IIRC, certaines distribution configurent un "alias" dans le shell pour midnight commander (mc) afin de pouvoir positionner le répertoire courant du shell sur le dernier visité par mc à la sortie de celui-ci. Tu peux t'inspirer de celà si cela est vraiment nécessaire. Perso, je trouve bizard d'attendre un tel comportement… Dans tout les cas tu devras trouver un système qui demande la coopération du shell appellant :-/

    • [^] # Re: ça marche

      Posté par  . Évalué à 1.

      Merci pour ta réponse.
      Donc l'une des solutions serait de faire une fonction C qui génère un nouveau shell pour l'exécuter avec le programme moncd ? C'est bien cela….

    • [^] # Commentaire supprimé

      Posté par  . Évalué à 1.

      Ce commentaire a été supprimé par l’équipe de modération.

      • [^] # Re: ça marche

        Posté par  . Évalué à 1. Dernière modification le 04 décembre 2015 à 16:57.

        D'une part, "cd" est aussi une commande interne au shell.

        Ok, accordé. Disons que la commande cd aurait très bien pu être externe, voir 2me point.

        Et d'autre part […] l'imperméabilité est aussi présente côté Windows.

        J'ai bien écris ms-dos/windows, donc implicitement de l'API DOS et des ses implémentations que l'on trouve dans dos >2.0 et windows.
        Et là, en dépit de te décevoir, cela n'est pas le cas!

        (Au mieux pourrais-tu encore critiquer la pertinence de comparer l'api UNIX à celle de DOS, une api qui n'est certainement plus promue par MS.
        J'argumenterais en disais qu'elle est arrivé déjà bien après celle d'UNIX et que cette société aurait pu au moins s'inspirer de l'état de l'art au lieu de torcher un produit dans la seule fin de truster un marcher en faisant fis du développement de l'informatique dans son sens le plus large, quoi que peuvent en dire les médias—encore au jour d'aujourd'hui --.
        Je conçois que le posteur de cette question n'a, plus que probablement, aucune expérience avec DOS et donc que cette comparaison s'apparente plus à une expression de type "de mon temps …" sans apporter de vrai valeur pédagogique, néanmoins je souligne ainsi par ce petit retour dans le passé que cette société ne s'est jamais démarquée par l'excellence technique. Car j'estime que sur ce site, il n'est jamais mauvais de faire un peu de prosélytisme, surtout parmi les futurs programmeurs.
        Bref, bonjour chez toi et bon week-end)

        Et l'âne s'en retourna brouter son herbe…

        • [^] # Commentaire supprimé

          Posté par  . Évalué à 1.

          Ce commentaire a été supprimé par l’équipe de modération.

  • # Fonction mini shell

    Posté par  . Évalué à 1. Dernière modification le 03 décembre 2015 à 23:31.

    J'ai finalement résolu mon problème pour le programme moncd précédent merci pour les explications, elles m'ont servi de bcp pour la compréhension.
    Par ailleurs, si j'ai un programme C qui a pour but d'exécuter le fonctionnement d'un mini shell comment pourrais je le modifier afin de rajouter la possibilité de lancer des processus en arrière-plan quand la ligne de commande se termine par un & ? L'indication est de ne pas faire le wait dans le parent.
    Le but c'est de pouvoir faire un mini shell complet à partir de celui ci voilà pourquoi j'avais besoin de créer un programme moncd pour simuler le fonctionnement de cd.

    Le programme est le suivant(il lit une ligne de commande, la découpe avec la fonction découper, fabrique un nouveau processus avec fork et tente d'y lancer la commande en allant chercher un fichier du même nom dans chacun des répertoires indiqué par PATH.):

            # 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);
    
            # define PROMPT "? "
    
            int main(int argc, char * argv[]){
              char ligne[MaxLigne];
              char pathname[MaxPathLength];
              char * mot[MaxMot];
              char * dirs[MaxDirs];
              int i, tmp;
    
              /* Decouper PATH en repertoires */
              decouper(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);
            for(i = 0; mot[i] != 0 ; i++) ;
                  if (mot[0] == 0)            // ligne vide
                    continue;
    
    
                  tmp = fork();         // lancer le processus enfant
    
                  if (tmp < 0){
                    perror("fork");
                    continue;
                  }
    
                if (tmp != 0){ // parent : attendre la fin de l'enfant
                     while(wait(0) != tmp); 
                    continue;
                  }
    
                                   // 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){
                  fprintf(stderr, "Erreur dans la fonction decouper: trop de mots\n");
                  mot[i - 1] = 0;
                }
                mot[i] = strtok(NULL, separ);
              }
            }

    J'ai pensé à introduire au niveau du parent l'instruction:

    if(*mot[i-1] != '&')

    Mais l'exécution me produit une erreur.

    roger@roger-1005PX:~/Bureau$ gcc -g -Wall shell.c -o shell
    roger@roger-1005PX:~/Bureau$ ./shell 
    ?                             #notre mini shell
    ? echo essai                  #test avec echo
    ? essai
    ? sleep 10 &                  #test arrière plan
    sleep: intervalle de temps «&» incorrect.Saisissez « sleep --help » pour plus d'informations.

    Merci d'avance …

    • [^] # Re: Fonction mini shell

      Posté par  . Évalué à 1.

      Réécrire un shell est un très bon exercice pour mieux comprendre les notions de processus et d'environnement sous linux.
      Pour le symbole "&", il faut penser à le supprimer de la ligne de commande avant d'appeler execv, le remplacer par un caractère espace est suffisant.

      Pour l'arrière plan, il ne faut pas utiliser wait mais faire plusieurs waitpid avec l'option NOHANG (non bloquant) et trouver un moyen de sauvegarder le pid du processus à attendre.
      À toi de choisir comment tu souhaites appeler waitpid et surtout à quelle fréquence, certains shell l'appelle après chaque traitement de ligne.

      Après si tu as vraiment envie de t'amuser, tu peux commencer à jouer avec la redirection d'entrées-sorties, les pipes, plusieurs processus en arrière plan …

      • [^] # Re: Fonction mini shell

        Posté par  . Évalué à 1.

        D'accord je comprends à peu près ce que tu veux dire.
        Mais est ce possible d'etre plus explicite de façon concrète avec quelques modifications apportées au niveau de mon code ? sur l'appel du waitpid pour l'arrière plan.
        Ca m'éclaircirait davantage. Merci

      • [^] # Re: Fonction mini shell

        Posté par  . Évalué à 1.

        " Pour le symbole "&", il faut penser à le supprimer de la ligne de commande avant d'appeler execv, le remplacer par un caractère espace est suffisant."

        Qu'est ce que cela voudrait dire ? Sur ma ligne de commande je dois remplacer & par un espace de quelle manière ?

        • [^] # Re: Fonction mini shell

          Posté par  . Évalué à 1. Dernière modification le 03 décembre 2015 à 10:20.

          Il faut capturer le symbole '&' dans le mot puis le remplacer par un espace (bien) ou un caractère nul (mieux) :

            if (*mot[i-1] == '&')
            {
              *mot[i-1] = '\0';
              // Mettre ici le code pour le traitement en arrière plan
            }

          Pour le waitpid, elle diffère de wait en ajoutant la possibilité d'attendre un processus particulier et de spécifier des options d'attente comme NOHANG qui permet de ne pas rester bloqué dans la fonction si le processus fils n'est pas terminé. wait(&status) est équivalent à waitpid(-1, &status, 0).
          En remplaçant le paramètre -1 de waitpid par le pid du processus fils, tu n'as plus besoin de faire une boucle while autour de wait.

          • [^] # Re: Fonction mini shell

            Posté par  . Évalué à 1. Dernière modification le 03 décembre 2015 à 23:29.

            Merci Corentin !
            En tenant compte de tes suggestions, j'ai finalement pu ajouter quelques instructions au niveau du parent:

                 if (tmp != 0){      // parent : attendre la fin de l'enfant
            
                         if (*mot[i-1] == '&')
                        {
                          *mot[i-1] = '\0';
            
                          pid_t pid, pid1; 
                          int etat; 
                          if ((pid1 = fork()) == 0) exit(1); 
                          else if ((tmp) == 0) sleep(2),exit(2); 
                          else { 
                            pid = waitpid(tmp,&etat,0); 
                           printf(" fils termine %d son etat %d \n",pid, 
                                   etat);}         
                        }         
                        continue;
                      }

            En essayant quelques exemples sur le mini shell, j'ai ces résultats:

            ? echo essai1 &
            essai1 &
             fils termine 21473 son etat 0 
            ? echo essai2 &
            essai2 &
             fils termine 21475 son etat 0 
            ? echo essai3 &
            essai3 &
             fils termine 21480 son etat 0

            Au vue des résultats, mes instructions sont-elles optimales surtout pour la gestion de plusieurs processus en arrière plan ? Aussi pourquoi en affichant l'echo, il affiche avec le symbole '&' à la fin ? C'est un peu désagréable, comment me débarrasser de cela ? Merci

            • [^] # Re: Fonction mini shell

              Posté par  . Évalué à 1.

              *mot[i-1] = '\0';

              dois être fait juste avant le execv, dans le fils pas dans le parent.
              Pourquoi avoir rajouté un deuxième fork qui fait juste un sleep(2) ?
              Pour le mode arrière plan, il faut utiliser :

              pid = waitpid(tmp, &etat, WNOHANG); 
              if (WIFEXITED(etat))
              {
                // fin de l'arrière plan
              }
              else
              {
                // toujours en arrière plan
              }

              N'hésite pas à lire le manuel de waitpid pour bien comprendre le fonctionnement.
              Le plus dur pour gérer l'arrière plan sera de trouver comment sauvegarder tous les 'tmp' qui représentent les fils en arrière plan. N'hésite pas non plus à renommer tes variables avec des noms compréhensibles, le 'tmp' ne devrait jamais exister dans un programme, tu t'apercevra très vite qu'un bon nom de variable est très pratique.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.