2008-11-11 19 views
24

Estoy confundido acerca de cómo popen() redirige stdin, stdout y stderr del proceso secundario en Unix. La página man en popen() no es muy clara en este sentido. La llamadacómo controlar popen stdin, stdout, stderr redirection?

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

horquillas un proceso hijo y ejecuta un shell con argumentos "-c", "/ usr/bin/foo", y redirige la entrada estándar de esta cáscara (que se redirige la entrada estándar de foo), stdout para pag. ¿Pero qué pasa con stderr? ¿Cuál es el principio general detrás de esto?

Me di cuenta de que si abro un archivo en foo (utilizando fopen, socket, accept etc.) y el proceso principal no tiene stdout, se le asigna el siguiente número de archivo disponible, que es 1 y así sucesivamente. Esto ofrece resultados inesperados de llamadas como fprintf (stderr, ...).

puede evitarse escribiendo

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

en el programa de los padres, pero son sus mejores formas?

+0

En cuanto a la pregunta original, que menciona el cierre de la salida estándar: No vuelvas a hacer esto . Los programas C en Unix suponen que 0,1,2 son descripciones de archivos sensibles, y todo invoca C en algún momento. Regla general: * siempre * reserve 0,1,2 para usar como stdio. Si quiere cerrar uno de ellos, redirigirlo a '/ dev/null' en su lugar para que el fd esté todavía ocupado y no se le asigne nada más. –

Respuesta

28

popen(3) es solo una función de biblioteca, que se basa en fork(2) y pipe(2) para hacer el trabajo real.

Sin embargo, pipe(2) solo puede crear tubos unidireccionales.Para enviar la entrada de proceso secundario, y también capturar la salida, debe abrir dos tubos.

Si desea capturar la stderr también, eso es posible, pero entonces tendrá que tres tuberías, y un bucle select para arbitrar lee entre stdout y stderr corrientes.

Hay un ejemplo here para la versión de dos tubos.

7

El valor de retorno de popen() es un estándar normal de I corriente/O en todos los aspectos guardar que debe estar cerrada con pclose() en lugar de fclose (3). Escribir en dicha secuencia escribe en la entrada estándar de el comando; la salida estándar del comando es la misma que la del proceso que llamó a popen(), a menos que esto sea alterado por el comando mismo. Por el contrario, la lectura de un flujo "abierto" lee la salida estándar del comando , y la entrada estándar del comando es la igual que la del proceso que llamó a popen().

Desde su página de manual, por lo que le permite leer los comandos de salida estándar o escribir en su entrada estándar. No dice nada sobre stderr. Por lo tanto, eso no se redirige.

Si proporciona "w", enviará sus cosas al stdin del shell que se ejecuta. Por lo tanto, haciendo

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

hará que la ejecución en shell/bin/cat, y pasarlo la cadena "hello" como su flujo de entrada estándar. Si desea redirigir, por ejemplo stderr al archivo "foo" hacer esto primero, antes de ejecutar el código anterior:

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

Se abrirá el archivo, y duplicar su archivo descriptor a 2, cerrando el archivo original descriptor después.

Ahora, si tiene su stdout cerrada en su padre, entonces si el hijo llama al open obtendrá 1, ya que eso es (si stdin ya está abierto) el siguiente descriptor de archivo libre. La única solución que veo es usar dup2 y duplicar algo en el padre, como el código anterior. Tenga en cuenta que si el hijo abre stdout, se no haga que stdout también se abra en el elemento primario. Permanece cerrado allí.

+0

gracias. lo siento por mi error con respecto a stdout (debo haber estado dormido). de hecho, ahora veo que debería reformular mi pregunta: ¿qué sucede si el proceso principal en sí mismo no tiene una extensión estándar en el primer caso, o no está en el último, o no está en ambos casos? –

+0

Si está escribiendo en popen, y si stdout se cerró en el elemento primario, entonces en realidad si el niño intenta escribir en stdout, se comportará como si intentara escribir en el archivo descriptor 4211: no funcionaría –

+0

gracias. Concluyo que no es seguro confiar en stdin, stdout, stderr en un programa que otro programa puede usar en un conducto de comando. es decir, a menos que lo controle usted mismo a través de la redirección manual (con dup2() por ejemplo). –

28

idea simple: ¿por qué no agregar "2> & 1" a la cadena de comandos para forzar al bash a redirigir stderr a stdout? (OK, escribir en stdin todavía no es posible pero al menos obtenemos stderr y stdout en programa).

+0

¡Impresionante, gracias! Esto funciona muy bien para lectura popen de stderr. –

5

Echa un vistazo popenRWE de Bart Trojanowski. Manera limpia de hacer las 3 tuberías.

+3

Eso es útil. Para los curiosos es GPL, por lo que realmente no se puede tratar como un fragmento de código. –

5

si lo que desea es obtener STDERR, intente esto:

#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"); 
} 

y obtendrá esto:

shellcmd:ls: cannot access kangear: No such file or directory 
Cuestiones relacionadas