web-dev-qa-db-fra.com

Capture optimale de stdout à partir d'une commande system ()

J'essaie de démarrer une application externe via system() - par exemple, system("ls"). Je voudrais capturer sa sortie au fur et à mesure que je puisse l'envoyer vers une autre fonction pour un traitement ultérieur. Quelle est la meilleure façon de le faire en C/C++?

52
SinisterDex

Du manuel popen:

#include <stdio.h>

FILE *popen(const char *command, const char *type);

int pclose(FILE *stream);
40
jkramer

Essayez la fonction popen (). Il exécute une commande, comme system (), mais dirige la sortie dans un nouveau fichier. Un pointeur vers le flux est renvoyé.

  FILE *lsofFile_p = popen("lsof", "r");

  if (!lsofFile_p)
  {
    return -1;
  }

  char buffer[1024];
  char *line_p = fgets(buffer, sizeof(buffer), lsofFile_p);
  pclose(lsofFile_p);
32
Andrew Langrick

EDIT: question mal lue comme voulant passer la sortie à un autre programme, pas à une autre fonction. popen () est presque certainement ce que vous voulez.

Le système vous donne un accès complet au Shell. Si vous souhaitez continuer à l'utiliser, vous pouvez rediriger sa sortie vers un fichier temporaire, par le système ("ls> tempfile.txt"), mais choisir un fichier temporaire sécurisé est pénible. Ou, vous pouvez même le rediriger via un autre programme: system ("ls | otherprogram");

Certains peuvent recommander la commande popen (). Voici ce que vous voulez si vous pouvez traiter vous-même la sortie:

FILE *output = popen("ls", "r");

qui vous donnera un pointeur FILE que vous pouvez lire avec la sortie de la commande dessus.

Vous pouvez également utiliser l'appel pipe () pour créer une connexion en combinaison avec fork () pour créer de nouveaux processus, dup2 () pour en modifier l'entrée et la sortie standard, exec () pour exécuter les nouveaux programmes et wait () dans le programme principal pour les attendre. Cela ne fait que mettre en place le pipeline comme le ferait Shell. Consultez la page de manuel pipe () pour plus de détails et un exemple.

7
wnoise

Les fonctions popen() et autres ne redirigent pas stderr et autres; J'ai écrit popen3() à cet effet.

Voici une version bowdlerised de mon popen3 ():

int popen3(int fd[3],const char **const cmd) {
        int i, e;
        int p[3][2];
        pid_t pid;
        // set all the FDs to invalid
        for(i=0; i<3; i++)
                p[i][0] = p[i][1] = -1;
        // create the pipes
        for(int i=0; i<3; i++)
                if(pipe(p[i]))
                        goto error;
        // and fork
        pid = fork();
        if(-1 == pid)
                goto error;
        // in the parent?
        if(pid) {
                // parent
                fd[STDIN_FILENO] = p[STDIN_FILENO][1];
                close(p[STDIN_FILENO][0]);
                fd[STDOUT_FILENO] = p[STDOUT_FILENO][0];
                close(p[STDOUT_FILENO][1]);
                fd[STDERR_FILENO] = p[STDERR_FILENO][0];
                close(p[STDERR_FILENO][1]);
                // success
                return 0;
        } else {
                // child
                dup2(p[STDIN_FILENO][0],STDIN_FILENO);
                close(p[STDIN_FILENO][1]);
                dup2(p[STDOUT_FILENO][1],STDOUT_FILENO);
                close(p[STDOUT_FILENO][0]);
                dup2(p[STDERR_FILENO][1],STDERR_FILENO);
                close(p[STDERR_FILENO][0]);
                // here we try and run it
                execv(*cmd,const_cast<char*const*>(cmd));
                // if we are there, then we failed to launch our program
                perror("Could not launch");
                fprintf(stderr," \"%s\"\n",*cmd);
                _exit(EXIT_FAILURE);
        }

        // preserve original error
        e = errno;
        for(i=0; i<3; i++) {
                close(p[i][0]);
                close(p[i][1]);
        }
        errno = e;
        return -1;
}
3
Will

Le moyen le plus efficace consiste à utiliser directement le descripteur de fichier stdout, en contournant le flux FILE:

pid_t popen2(const char *command, int * infp, int * outfp)
{
    int p_stdin[2], p_stdout[2];
    pid_t pid;

    if (pipe(p_stdin) == -1)
        return -1;

    if (pipe(p_stdout) == -1) {
        close(p_stdin[0]);
        close(p_stdin[1]);
        return -1;
    }

    pid = fork();

    if (pid < 0) {
        close(p_stdin[0]);
        close(p_stdin[1]);
        close(p_stdout[0]);
        close(p_stdout[1]);
        return pid;
    } else if (pid == 0) {
        close(p_stdin[1]);
        dup2(p_stdin[0], 0);
        close(p_stdout[0]);
        dup2(p_stdout[1], 1);
        dup2(::open("/dev/null", O_WRONLY), 2);

        /// Close all other descriptors for the safety sake.
        for (int i = 3; i < 4096; ++i) {
            ::close(i);
        }

        setsid();
        execl("/bin/sh", "sh", "-c", command, NULL);
        _exit(1);
    }

    close(p_stdin[0]);
    close(p_stdout[1]);

    if (infp == NULL) {
        close(p_stdin[1]);
    } else {
        *infp = p_stdin[1];
    }

    if (outfp == NULL) {
        close(p_stdout[0]);
    } else {
        *outfp = p_stdout[0];
    }

    return pid;
}

Pour lire la sortie de enfant utilisez popen2() comme ceci:

int child_stdout = -1;
pid_t child_pid = popen2("ls", 0, &child_stdout);

if (!child_pid) {
    handle_error();
}

char buff[128];
ssize_t bytes_read = read(child_stdout, buff, sizeof(buff));

Pour écrire et lire:

int child_stdin = -1;
int child_stdout = -1;
pid_t child_pid = popen2("grep 123", &child_stdin, &child_stdout);

if (!child_pid) {
    handle_error();
}

const char text = "1\n2\n123\n3";
ssize_t bytes_written = write(child_stdin, text, sizeof(text) - 1);

char buff[128];
ssize_t bytes_read = read(child_stdout, buff, sizeof(buff));
2
GreenScape

Sous Windows, au lieu d'utiliser system (), utilisez CreateProcess, redirigez la sortie vers un tuyau et connectez-vous au tuyau.

Je suppose que cela est également possible d'une manière POSIX?

1
shoosh

Les fonctions popen() et pclose() pourraient être ce que vous recherchez.

Jetez un œil au manuel de la glibc pour un exemple.

1
Andrew Edgecombe

Dans cette page: capture_the_output_of_a_child_process_in_c décrit les limites de l'utilisation de popen par rapport à l'utilisation de l'approche fork/exec/dup2/STDOUT_FILENO.

J'ai des problèmes capture tshark sortie avec popen .

Et je suppose que cette limitation pourrait être mon problème:

Il renvoie un flux stdio par opposition à un descripteur de fichier brut, qui ne convient pas pour gérer la sortie de manière asynchrone.

Je reviendrai sur cette réponse si j'ai une solution avec l'autre approche.

1
nephewtom

En fait, je viens de vérifier, et:

  1. popen est problématique, car le processus est fourchu. Donc, si vous devez attendre l'exécution de la commande Shell, vous risquez de la manquer. Dans mon cas, mon programme a fermé avant même que la pipe ne fasse son travail.

  2. J'ai fini par utiliser système appel avec la commande tar sur linux. La valeur de retour du système était le résultat de tar .

Donc: si vous avez besoin de la valeur de retour, non seulement il n'y a pas besoin d'utiliser popen, mais il ne fera probablement pas ce que vous voulez.

1
mousomer

Je ne suis pas entièrement certain que ce soit possible en C standard, car deux processus différents ne partagent généralement pas d'espace mémoire. La manière la plus simple à laquelle je peux penser serait que le deuxième programme redirige sa sortie vers un fichier texte (programname> textfile.txt), puis relise ce fichier texte pour le traitement. Cependant, ce n'est peut-être pas la meilleure façon.

0
Nicholas Flynt