Executer une commande

Signaler
Messages postés
26
Date d'inscription
dimanche 14 mars 2004
Statut
Membre
Dernière intervention
26 août 2006
-
Messages postés
364
Date d'inscription
mercredi 11 février 2004
Statut
Membre
Dernière intervention
5 octobre 2006
-
hello ;)

J'ai un programme ecrit en c++/gtkmm dans lequel je veut executer une commande et surtout recupérer son resultat au fur et a mesure.

J'ai une solution qui consite a
- executer la commande avec std::system() avec redirection du resultat dans un fichier
- en paralelle une boucle qui verifie si des nouvelles lignes sont presentes dans le fichier, si c'est le cas je les lis.

Je me demandais si il existait un autre solution peut etre plus propre, comme une librairie un peu plus puissant que std::system()

merci ;)

21 réponses

Messages postés
6535
Date d'inscription
lundi 16 décembre 2002
Statut
Modérateur
Dernière intervention
22 août 2010
7
Sous Linux il y a la fonction pipe pour faire ca

_____________________________________
Un éditeur de ressources gratuit pour Windows
Messages postés
26
Date d'inscription
dimanche 14 mars 2004
Statut
Membre
Dernière intervention
26 août 2006

soit tu ne comprend pas ma question, soit je ne comprend pas ta réponse :D

Je connait la commande pipe, je l'utilise souvent pour faire des grep sur un resultat.
Mais je ne voit pas comment je pourrait l'utiliser pour résoudre mon probleme.
Messages postés
71
Date d'inscription
mercredi 14 avril 2004
Statut
Membre
Dernière intervention
14 septembre 2007

Comme vecchio l'a si bien dit, il faut utiliser pipe ... enfin presque :)

Il faut en fait comprendre ce qui se passe quand on utilise un pipe dans un shell (C'est en fait une fonctionalité du shell). L'utilisation du pipe indique au shell qu'il doit rediriger la sortie du premier programme vers l'entrée du second. Redirection implique une modification des handles standard input et standard output (puisqu'on veut récupérer le résultat en sortie).

Pour ce faire, on utilise donc un pipe (ou un unix socket, rien à voir avec les sockets ip) qui agit comme un buffer entre les deux programmes.

J'ai déjà écrit ce genre de code pour un serveur web lors de l'exécution des cgi et de la récupération des résultats de l'exécution (si tu ne vois pas le lien, ne cherche pas ce n'est pas grave :p).

Je copie la partie du code implémente cette fonctionalité. Je n'ai pas mis toutes les fonctions utilisées mais leurs noms est assez explicite pour que tu puisses les recoder.

Argument de la fonction:
1) tableau de string formant la ligne de commande
2) tableau de string pour les variables d'environnement
3) pointeur vers un entier dans lequel sera stocké le "file descriptor" à utiliser pour lire le stdout et écrire dans le stdin du programme (simplement avec read et write comme s'il s'agissait d'un fichier).

Le principe de la fonction est de créer une paire de socket déjà connecté, de faire un fork (les file descriptors pour le deux bouts du tunnel sont toujours valides), de fermer un des deux bouts selon qu'on est dans le parent où dans le fils. Pour le processus parent le travail s'arrête là. Dans le processus fils, il faut encore dédoubler le bout du "tunnel" en stdin et stdout. Il faut enfin faire un exec pour lancer le programme à exécuter. La fonction exec ne réouvre pas le flux d'entrée et de sortie. De cette façon, nos modifications de stdin et stdout seront conservées. Assez de blabla, maintenant le code :P

int

io_popen(char *argv[], char *envp[], int *srv_child)

{

    int sockpair[2];

    int child_srv;

   

    if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair) == -1)

    {

        log_error("socketpair failed: %s", strerror(errno));

        return -1;

    }

   

    *srv_child = sockpair[0];

    child_srv = sockpair[1];

   

    int retval = fork();

    if(retval < 0)

    {

        /* parent - fork has failed */

        log_error("fork failed: %s", strerror(errno));

        io_close(child_srv);

        io_close(*srv_child);

        return -1;

    }

    else if(retval > 0)

    {

        /* parent - close the file descriptor used by the child */

        io_close(child_srv);

        return 0;

    }

   

    /*---------------*

     | Child process |

     *---------------*/

   

    /* close socket used by parent */

    io_close(*srv_child);

   

    /* logs shouldn't be accessed by cgi */

    log_close();

   

    /* close stdin and stdout */

    io_close(STDIN_FILENO);

    io_close(STDOUT_FILENO);

   

    /* duplicate input and output to stdin and stdout */

    if (dup2(child_srv, STDIN_FILENO) != STDIN_FILENO ||

        dup2(child_srv, STDOUT_FILENO) != STDOUT_FILENO)

    {

        io_close(child_srv);

        debug_msg(ERROR, "dup2 failed: %s", strerror(errno));

        _exit(EXIT_FAILURE);

    }

   

    execve(argv[0], argv, envp);

   

    /* execve doesn't return on success */
    if (errno ENOEXEC || errno EACCES)

        debug_msg(ERROR, "CGI file not executable");

    else

        debug_msg(ERROR, "execve failed: %s", strerror(errno));

   

    /* prevent the call to the function registered with atexit */

    _exit(EXIT_FAILURE);

}

Si tu as des questions sur le code ou sur mes explications (légèrement foireuse ?), n'hésite j'essayerais de faire mieux.

Belo
Messages postés
71
Date d'inscription
mercredi 14 avril 2004
Statut
Membre
Dernière intervention
14 septembre 2007

Mini question dans ce thread: est-ce normal que l'espace entre les lignes de code soit si grand (avec firefox et ie) ? J'ai utilisé le type "Formatte" dans
le "textbox évolué".
Messages postés
26
Date d'inscription
dimanche 14 mars 2004
Statut
Membre
Dernière intervention
26 août 2006

merci :D
J'ai essayé de comprendre l'aglo et je pense avoir réussi ^^
Le revoici avec des commentaires supplémentaires, si tu pouvais regarder ;)
int io_popen(char *argv[], char *envp[], int *srv_child)

{

    int sockpair[2];

    int child_srv;

    //on commence a créer socketpair.

    //Ceci va permettre d'avoir un tunelle entre srv_child et child_srv afin d'avoir un lien entre les 2 processus

    if(socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair) == -1)

    {

        log_error("socketpair failed: %s", strerror(errno));

        return -1;

    }

    *srv_child = sockpair[0];

    child_srv = sockpair[1];

   

    //on fait le, fork et on ferme le socketpair qui n'est pas utilisé par le processus (chaque processus en garde 1)

    int retval = fork();

    if(retval < 0)

    {

        /* parent - fork has failed */

        log_error("fork failed: %s", strerror(errno));

        io_close(child_srv);

        io_close(*srv_child);

        return -1;

    }

    else if(retval > 0)

    {

        /* parent - close the file descriptor used by the child */

        io_close(child_srv);

        return 0;

    }

    /*---------------*

     | Child process |

     *---------------*/

    /* close socket used by parent */

    io_close(*srv_child);

   

    //ca je comprend pas , c'est lié à ton utilisation du code je suppose.

    /* logs shouldn't be accessed by cgi */

    log_close();

   

    //on ferme les entrées/sorties standards car on les redirige apres ?

    /* close stdin and stdout */

    io_close(STDIN_FILENO);

    io_close(STDOUT_FILENO);

   

    //Les on redirige les entrées/sorties standard vers le socketpair child_srv

    //qui est lui même lié a srv_child du processus père

    /* duplicate input and output to stdin and stdout */

    if (dup2(child_srv, STDIN_FILENO) != STDIN_FILENO ||

        dup2(child_srv, STDOUT_FILENO) != STDOUT_FILENO)

    {

        io_close(child_srv);

        debug_msg(ERROR, "dup2 failed: %s", strerror(errno));

        _exit(EXIT_FAILURE);

    }

    //on execute la commande

    execve(argv[0], argv, envp);

        //gestion de la sortie de execve

    //je suppose que le fait que errno sorte d'on ne sais ou vient du cgi ...

    /* execve doesn't return on success */
    if (errno ENOEXEC || errno EACCES)

        debug_msg(ERROR, "CGI file not executable");

    else

        debug_msg(ERROR, "execve failed: %s", strerror(errno));

    /* prevent the call to the function registered with atexit */

    _exit(EXIT_FAILURE);

}
Messages postés
71
Date d'inscription
mercredi 14 avril 2004
Statut
Membre
Dernière intervention
14 septembre 2007

log_close(); -> En effet, rien à voir dans le cas qui nous occupe :P

//on ferme les entrées/sorties standards car on les redirige apres ?
/* close stdin and stdout */
io_close(STDIN_FILENO);
io_close(STDOUT_FILENO);
-> On doit dabord fermer les file descriptors (fd) car sinon dup2 échoue quand on lui demande de faire la duplication car les fd sont encore valides et utilisables.


//on execute la commande
execve(argv[0], argv, envp);
-> Dans le cas où tout se passe bien, execve ne retourne jamais puisque cette fonction écrase la section code en mémoire de notre application avec le code de la nouvelle application.

Toutefois, il se peut que cet appel échoue car, par exemple, l'exécutable est introuvable ou le droit actuel du programme ne permet pas de lire le fichier exécutable... Il faut donc faire une gestion des erreurs, d'où la suite du code.


//gestion de la sortie de execve
//je suppose que le fait que errno sorte d'on ne sais ou vient du cgi ...
/* execve doesn't return on success */
if (errno ENOEXEC || errno EACCES)
debug_msg(ERROR, "CGI file not executable");
else
debug_msg(ERROR, "execve failed: %s", strerror(errno));
-> pas tout à fait. errno est un grande classique dans la programmation système sous linux. C'est une variable de la librairie C qui contient le code d'erreur du dernier échec lors d'un appel. Cette variable indique donc la dernière erreur qui a eu lieu. man 2 execve pour avoir toutes les possibilités pour execve.

strerror permet de récupérer une description du code d'erreur dans une chaine de caractère.

Attention à l'utilisation de errno. Il me semble que errno n'est pas thread-safe
(il me semble qu'ils sont en train de résoudre le problème). Je te conseil donc de mettre tous tes appels de fonction utilisant errno dans une seule et meme thread (si tu en as plusieurs). De cette façon, tu éviteras d'avoir des codes d'erreurs bizarres.
Messages postés
26
Date d'inscription
dimanche 14 mars 2004
Statut
Membre
Dernière intervention
26 août 2006

Je peut aussi gérer le retour de execve d'une manière classic, pourquoi faire compliqué ?
res = execve()

Il faudrait aussi que a la fin je recré les "vrai" file descriptor STDIN_FILENO et STDOUT_FILENO non ?

Bon sinon j'ai cherché les fonctions nécessaires et les includes, voici la liste, ca pourra aider d'autres personnes:
#include <sys/types.h>
#include <sys/socket.h>
socketpair()
close()
dup2()
execve()

A part pour io_close() qui devient close(), il n'y a pas de différence avec le precedent code.
Messages postés
71
Date d'inscription
mercredi 14 avril 2004
Statut
Membre
Dernière intervention
14 septembre 2007

Deux choses concernant STDIN_FILENO et STDOUT_FILENO:
1° Il n'y a pas besoin de recréer les "vrais" puisque tu veux avoir tout l'output du programme et ensuite que celui-ci se terminera.
2° Il n'y a de toute façon pas moyen de le faire. Il faut bien comprendre que si l'exec réussit, ce n'est plus ton programme qui s'exécute mais l'autre que tu vien s de lancer. C'est pour ca qu'on fait d'abord un fork ... pour éviter que notre programme ne se fasse écraser. Etant donné que ce n'est plus ton programme qui s'exécute, tu ne pourras pas restaurer les deux fd.

Pour le programme que tu lances, il n'y a aucune différence. Si dans ce programme, on fait un printf, le résultat de ce printf sera écrit dans le socket unix et non pas sur la console. C'est pour ça qu'on dit que la sortie standard est redirigée.

Fait peut-être quelque essai avec fork/exec pour bien saisir leur comportement.
Messages postés
26
Date d'inscription
dimanche 14 mars 2004
Statut
Membre
Dernière intervention
26 août 2006

ah ok, ca m'oblige donc a executer execve dans un processus fils. Je prevoyais de le faire dans le processus principal etant donné que je ne ferait rien en parallèle. Je vais donc le lancer dans un processus fils et faire attendre le processus père.
Messages postés
71
Date d'inscription
mercredi 14 avril 2004
Statut
Membre
Dernière intervention
14 septembre 2007

Fait quand même attention en faisant attendre le processus père.

Je ne suis pas sur à 100% de ce que j'avance mais ca parait fort probable.

Les sockets unix (comme ceux utilisés dans notre cas) peuvent être vus comme des zones de mémoires partagées gérées par le système d'exploitation. Ces zones ont biensur une taille fixe et il est possible que le processus qui écrit dans le socket soit bloqué lors d'une écriture parce que les buffers systèmes sont pleins.

En fait, le processus fils est bloqué parce qu'il ne peut plus écrire et le processus père attend que le processus fils termine. On se trouve devant un magnifique deadlock :) (Pas évident à remarquer en plus)

La façon de remédier à ce problème est de lire au fur et à mesure les données dans le socket de façon à ne pas saturer les buffers.

Malheureusement, je ne peux pas te dire quelle est la taille de ce buffer ;-/

Une chose est sure, il vaut mieux être prudent et mettre en place un système qui lit au fur et à mesure.

PS: Si quelqu'un pouvait confirmer ou connaissait la taille des buffers, qu'il ne se gène pas pour poster ! :)
Messages postés
26
Date d'inscription
dimanche 14 mars 2004
Statut
Membre
Dernière intervention
26 août 2006

hehe en faite je vais avoir un processus principale qui va:
- créer les sockets
- créer un fils qui sera chargé de touujours écouter si il y a un nouveau message sur le socket
- ensuite le processus principal appellera quand nécessaire, une fonction qui créera un fils qui executera une commande.

Donc je viderai le buffer au fur et a mesure.
Messages postés
26
Date d'inscription
dimanche 14 mars 2004
Statut
Membre
Dernière intervention
26 août 2006

J'ai un pb de Xlib :D
voici le code: http://watchwolf.fr/public/Perso/redirection_commande.cc

Dans cette version du code le fork est mis en commentaire. Je n'ai pas d'erreur si ce n'est  le programme qui se ferme, ce qui montre qu'il arrive a executer la commande. Je n'ai bien sur rien d'afficher dans le terminal vu que je fait dup2().
Si j'enleve dup2() et  close(STDIN/OUT_FILENO ) j'ai le resultat qui s'affiche et qui est correcte.

Maintenant quand je fait le fork j'ai cette erreur:
Xlib: unexpected async reply (sequence 0x349)!

Ce n'est pas cool, je sais que ce genre d'erreur arrive quand on a des processus en parallelle :D
Messages postés
26
Date d'inscription
dimanche 14 mars 2004
Statut
Membre
Dernière intervention
26 août 2006

(on ne peut pas éditer ces messages?)

Je vient de tester le fork sans executer de commande. et j'ai aussi l'erreur, j'ai alors remplacé le derniere return par un exit et je n'ai plus l'erreur.
Mais avec la commande l'erreur persiste, ce qui est normal vu que le fils ne continue pas l'execution de mon code lorsque la commande est correcte et donc a sa propre methode.
Messages postés
26
Date d'inscription
dimanche 14 mars 2004
Statut
Membre
Dernière intervention
26 août 2006

hehe probleme résolue :D
il ne faut pas essayer de récupérer le resultat de execv() directement (int res=execv()). Voila pourquoi tu ne faisait pas comme ca :D
Messages postés
26
Date d'inscription
dimanche 14 mars 2004
Statut
Membre
Dernière intervention
26 août 2006

bon ben voila j'ai réussi à implementer le tout ;)

Je remerci tout le monde :D
Messages postés
71
Date d'inscription
mercredi 14 avril 2004
Statut
Membre
Dernière intervention
14 septembre 2007

Félicitations :P
Messages postés
364
Date d'inscription
mercredi 11 février 2004
Statut
Membre
Dernière intervention
5 octobre 2006
2
Hum,

popen  n'est pas plus simple ?
Messages postés
71
Date d'inscription
mercredi 14 avril 2004
Statut
Membre
Dernière intervention
14 septembre 2007

C'est une possibilité mais il n'y a pas moyen de changer l'environnement pour le nouveau processus. popen est sans doute plus facile si aucun changement ne doit être fait à l'environnement, l'autre méthode sinon.

De plus la fonction décrit les posts précédents montre comment fonctionne popen ce qui ne peut pas être mauvais en soi :P
Messages postés
364
Date d'inscription
mercredi 11 février 2004
Statut
Membre
Dernière intervention
5 octobre 2006
2
L'environnement...
je ne connais pas trop de cas utile où il faudrait changer l'environnement de l'appelé mais ça doit bien exister.
Sinon avec popen, c'est possible en ajoutant la mise en place de de l'environnement dans la commande "export VAR=value; env | sort "
mais je te l'accorde c'est pas très pratique...

L'important étant que l'OP est trouvé son bonheur ;)
Messages postés
26
Date d'inscription
dimanche 14 mars 2004
Statut
Membre
Dernière intervention
26 août 2006

OP ?

Sinon maintenant que ca marche je ne vais pas changer. Et le principe est le meme avec popen, on ne gagne pas grand chose ;)