web-dev-qa-db-fra.com

comment contrôler popen stdin, stdout, stderr redirection?

Je suis confus sur la façon dont popen () redirige stdin, stdout et stderr du processus enfant sous unix. La page de manuel sur popen () n'est pas très claire à cet égard. L'appel

FILE *p = popen("/usr/bin/foo", "w");

bifurque un processus enfant et exécute un Shell avec les arguments "-c", "/ usr/bin/foo", et redirige stdin de ce Shell (qui est redirigé stdin de foo), stdout vers p. Mais que se passe-t-il avec stderr? Quel est le principe général derrière cela?

J'ai remarqué que si j'ouvre un fichier dans foo (en utilisant fopen, socket, accept etc.), et que le processus parent n'a pas de sortie standard, il reçoit le prochain numéro de fichier disponible, qui est 1 et ainsi de suite. Cela donne des résultats inattendus d'appels comme fprintf (stderr, ...).

Il peut être évité en écrivant

FILE *p = popen("/usr/bin/foo 2>/dev/null", "w");

dans le programme des parents, mais sont leurs meilleurs moyens?

28
Dong Hoon

popen(3) n'est qu'une fonction de bibliothèque, qui s'appuie sur fork(2) et pipe(2) pour faire le vrai travail.

Cependant, pipe(2) ne peut créer que des canaux unidirectionnels. Pour envoyer l'entrée de processus enfant et capturer également la sortie, vous devez ouvrir deux canaux.

Si vous souhaitez également capturer le stderr, c'est possible, mais vous aurez besoin de trois tuyaux et d'un select boucle pour arbitrer les lectures entre les flux stdout et stderr.

Il y a un exemple ici pour la version à deux tubes.

35
Alnitak

idée simple: pourquoi ne pas ajouter "2> & 1" à la chaîne de commande pour forcer le bash à rediriger stderr vers stdout (OK, écrire dans stdin n'est toujours pas possible mais au moins nous obtenons stderr et stdout dans notre programme C).

34
martin

La valeur de retour de popen () est un flux d'E/S standard normal à tous égards, sauf qu'il doit être fermé avec pclose () plutôt que fclose (3). L'écriture dans un tel flux écrit sur l'entrée standard de la commande; la sortie standard de la commande est la même que celle du processus qui a appelé popen (), sauf si cela est modifié par la commande elle-même. Inversement, la lecture d'un flux "ouvert" lit la sortie standard de la commande, et l'entrée standard de la commande est la même que celle du processus qui a appelé popen ().

À partir de sa page de manuel, il vous permet donc de lire la sortie standard des commandes ou d'écrire dans son entrée standard. Cela ne dit rien sur stderr. Ainsi, cela n'est pas redirigé.

Si vous fournissez "w", vous enverrez vos trucs au stdin du Shell qui est exécuté. Ainsi, faire

FILE * file = popen("/bin/cat", "w");
fwrite("hello", 5, file);
pclose(file);

Fera exécuter le shell/bin/cat et lui passera la chaîne "hello" comme flux d'entrée standard. Si vous souhaitez rediriger, par exemple stderr vers le fichier "foo" faites ceci avant d'exécuter le code ci-dessus:

FILE * error_file = fopen("foo", "w+");
if(error_file) {
    dup2(fileno(error_file), 2);
    fclose(error_file);
}

Il ouvrira le fichier et dupliquera son descripteur de fichier à 2, fermant ensuite le descripteur de fichier d'origine.

Maintenant, si votre stdout est fermée dans votre parent, alors si l'enfant appelle open, il obtiendra 1, car c'est (si stdin est déjà ouvert) le prochain descripteur de fichier gratuit. La seule solution que je vois est d'utiliser simplement dup2 et de dupliquer quelque chose dans le parent, comme le code ci-dessus. Notez que si l'enfant ouvre stdout, il pas fera aussi ouvrir stdout dans le parent. Il y reste fermé.

7

Découvrez popenRWE par Bart Trojanowski. Manière propre de faire tous les 3 tuyaux.

5
neoneye

si vous voulez simplement obtenir STDERR, essayez ceci:

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <malloc.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>

/*
 * Pointer to array allocated at run-time.
 */
static pid_t    *childpid = NULL;

/*
 * From our open_max(), {Prog openmax}.
 */
static int      maxfd;

FILE *
mypopen(const char *cmdstring, const char *type)
{
    int     i;
    int     pfd[2];
    pid_t   pid;
    FILE    *fp;

    /* only allow "r" "e" or "w" */
    if ((type[0] != 'r' && type[0] != 'w' && type[0] != 'e') || type[1] != 0) {
        errno = EINVAL;     /* required by POSIX */
        return(NULL);
    }

    if (childpid == NULL) {     /* first time through */
        /* allocate zeroed out array for child pids */
        maxfd = 256;
        if ((childpid = calloc(maxfd, sizeof(pid_t))) == NULL)
            return(NULL);
    }

    if (pipe(pfd) < 0)
        return(NULL);   /* errno set by pipe() */

    if ((pid = fork()) < 0) {
        return(NULL);   /* errno set by fork() */
    } else if (pid == 0) {                          /* child */
        if (*type == 'e') {
            close(pfd[0]);
            if (pfd[1] != STDERR_FILENO) {
                dup2(pfd[1], STDERR_FILENO);
                close(pfd[1]);
            }
        } else if (*type == 'r') {
            close(pfd[0]);
            if (pfd[1] != STDOUT_FILENO) {
                dup2(pfd[1], STDOUT_FILENO);
                close(pfd[1]);
            }
        } else {
            close(pfd[1]);
            if (pfd[0] != STDIN_FILENO) {
                dup2(pfd[0], STDIN_FILENO);
                close(pfd[0]);
            }
        }

        /* close all descriptors in childpid[] */
        for (i = 0; i < maxfd; i++)
            if (childpid[i] > 0)
                close(i);

        execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
        _exit(127);
    }

    /* parent continues... */
    if (*type == 'e') {
        close(pfd[1]);
        if ((fp = fdopen(pfd[0], "r")) == NULL)
            return(NULL);
    } else if (*type == 'r') {
        close(pfd[1]);
        if ((fp = fdopen(pfd[0], type)) == NULL)
            return(NULL);

    } else {
        close(pfd[0]);
        if ((fp = fdopen(pfd[1], type)) == NULL)
            return(NULL);
    }

    childpid[fileno(fp)] = pid; /* remember child pid for this fd */
    return(fp);
}

int
mypclose(FILE *fp)
{
    int     fd, stat;
    pid_t   pid;

    if (childpid == NULL) {
        errno = EINVAL;
        return(-1);     /* popen() has never been called */
    }

    fd = fileno(fp);
    if ((pid = childpid[fd]) == 0) {
        errno = EINVAL;
        return(-1);     /* fp wasn't opened by popen() */
    }

    childpid[fd] = 0;
    if (fclose(fp) == EOF)
        return(-1);

    while (waitpid(pid, &stat, 0) < 0)
        if (errno != EINTR)
            return(-1); /* error other than EINTR from waitpid() */

    return(stat);   /* return child's termination status */
}

int shellcmd(char *cmd){
    FILE *fp;
    char buf[1024];
    fp = mypopen(cmd,"e");
    if (fp==NULL) return -1;

    while(fgets(buf,1024,fp)!=NULL)
    {
        printf("shellcmd:%s", buf);
    }

    pclose(fp);
    return 0;
}

int main()
{
    shellcmd("ls kangear");
}

et vous obtiendrez ceci:

shellcmd:ls: cannot access kangear: No such file or directory
5
kangear