2012-02-23 16 views
8

En Linux 3.0/C++:Linux 3.0: Ejecutar proceso hijo con la entrada estándar entubada/salida estándar

me gustaría una función que hace lo siguiente:

string f(string s) 
{ 
    string r = system("foo < s"); 
    return r; 
} 

Obviamente lo anterior no funciona, pero entiendes la idea Tengo una cadena s que me gustaría pasar como la entrada estándar de una ejecución de proceso hijo de la aplicación "foo", y luego me gustaría grabar su salida estándar a la cadena r y luego devolverla.

¿Qué combinación de funciones syscalls de linux o posix debo usar?

+0

posible duplicado de [? ¿Cómo puedo ejecutar un programa externo de C y analizar su salida] (http://stackoverflow.com/questions/43116/how-can -i-run-an-external-program-from-c-and-parse-its-output) –

+1

¿Qué es "Linux 3.0"? – Joe

+0

@Joachim Pileborg: No, no es un duplicado, necesito stdin y stdout. popen es unidireccional (stdin o stdout, no ambos). –

Respuesta

32

El código proporcionado por eerpini no funciona como está escrito. Tenga en cuenta, por ejemplo, que los extremos de tubería que están cerrados en el elemento primario se usan posteriormente. Mire

close(wpipefd[1]); 

y la posterior escritura en ese descriptor cerrado. Esto es solo una transposición, pero muestra que este código nunca se ha usado. A continuación hay una versión que he probado. Desafortunadamente, cambié el estilo del código, por lo que no se aceptó como una edición del código de eerpini.

El único cambio estructural es que solo redirigí las E/S del niño (tenga en cuenta que las llamadas dup2 solo están en la ruta secundaria). Esto es muy importante porque las E/S del padre se estropean. Gracias a eerpini por la respuesta inicial, que utilicé para desarrollar esta.

#define PIPE_READ 0 
#define PIPE_WRITE 1 

int createChild(const char* szCommand, char* const aArguments[], char* const aEnvironment[], const char* szMessage) { 
    int aStdinPipe[2]; 
    int aStdoutPipe[2]; 
    int nChild; 
    char nChar; 
    int nResult; 

    if (pipe(aStdinPipe) < 0) { 
    perror("allocating pipe for child input redirect"); 
    return -1; 
    } 
    if (pipe(aStdoutPipe) < 0) { 
    close(aStdinPipe[PIPE_READ]); 
    close(aStdinPipe[PIPE_WRITE]); 
    perror("allocating pipe for child output redirect"); 
    return -1; 
    } 

    nChild = fork(); 
    if (0 == nChild) { 
    // child continues here 

    // redirect stdin 
    if (dup2(aStdinPipe[PIPE_READ], STDIN_FILENO) == -1) { 
     exit(errno); 
    } 

    // redirect stdout 
    if (dup2(aStdoutPipe[PIPE_WRITE], STDOUT_FILENO) == -1) { 
     exit(errno); 
    } 

    // redirect stderr 
    if (dup2(aStdoutPipe[PIPE_WRITE], STDERR_FILENO) == -1) { 
     exit(errno); 
    } 

    // all these are for use by parent only 
    close(aStdinPipe[PIPE_READ]); 
    close(aStdinPipe[PIPE_WRITE]); 
    close(aStdoutPipe[PIPE_READ]); 
    close(aStdoutPipe[PIPE_WRITE]); 

    // run child process image 
    // replace this with any exec* function find easier to use ("man exec") 
    nResult = execve(szCommand, aArguments, aEnvironment); 

    // if we get here at all, an error occurred, but we are in the child 
    // process, so just exit 
    exit(nResult); 
    } else if (nChild > 0) { 
    // parent continues here 

    // close unused file descriptors, these are for child only 
    close(aStdinPipe[PIPE_READ]); 
    close(aStdoutPipe[PIPE_WRITE]); 

    // Include error check here 
    if (NULL != szMessage) { 
     write(aStdinPipe[PIPE_WRITE], szMessage, strlen(szMessage)); 
    } 

    // Just a char by char read here, you can change it accordingly 
    while (read(aStdoutPipe[PIPE_READ], &nChar, 1) == 1) { 
     write(STDOUT_FILENO, &nChar, 1); 
    } 

    // done with these in this example program, you would normally keep these 
    // open of course as long as you want to talk to the child 
    close(aStdinPipe[PIPE_WRITE]); 
    close(aStdoutPipe[PIPE_READ]); 
    } else { 
    // failed to create child 
    close(aStdinPipe[PIPE_READ]); 
    close(aStdinPipe[PIPE_WRITE]); 
    close(aStdoutPipe[PIPE_READ]); 
    close(aStdoutPipe[PIPE_WRITE]); 
    } 
    return nChild; 
} 
+0

gracias, finalmente, una pieza de código completa y funcional. – minastaros

2

Como quiera tener acceso bidireccional al proceso, tendría que hacer lo que popen hace detrás de las escenas explícitamente con tuberías. No estoy seguro de si algo de esto va a cambiar en C++, pero aquí es un ejemplo puro C:

void piped(char *str){ 
    int wpipefd[2]; 
    int rpipefd[2]; 
    int defout, defin; 
    defout = dup(stdout); 
    defin = dup (stdin); 
    if(pipe(wpipefd) < 0){ 
      perror("Pipe"); 
      exit(EXIT_FAILURE); 
    } 
    if(pipe(rpipefd) < 0){ 
      perror("Pipe"); 
      exit(EXIT_FAILURE); 
    } 
    if(dup2(wpipefd[0], 0) == -1){ 
      perror("dup2"); 
      exit(EXIT_FAILURE); 
    } 
    if(dup2(rpipefd[1], 1) == -1){ 
      perror("dup2"); 
      exit(EXIT_FAILURE); 
    } 
    if(fork() == 0){ 
      close(defout); 
      close(defin); 
      close(wpipefd[0]); 
      close(wpipefd[1]); 
      close(rpipefd[0]); 
      close(rpipefd[1]); 
      //Call exec here. Use the exec* family of functions according to your need 
    } 
    else{ 
      if(dup2(defin, 0) == -1){ 
        perror("dup2"); 
        exit(EXIT_FAILURE); 
      } 
      if(dup2(defout, 1) == -1){ 
        perror("dup2"); 
        exit(EXIT_FAILURE); 
      } 
      close(defout); 
      close(defin); 
      close(wpipefd[1]); 
      close(rpipefd[0]); 
      //Include error check here 
      write(wpipefd[1], str, strlen(str)); 
      //Just a char by char read here, you can change it accordingly 
      while(read(rpipefd[0], &ch, 1) != -1){ 
        write(stdout, &ch, 1); 
      } 
    } 

} 

Eficacia con la que hacer esto:

  1. Crear tuberías y redirigir la salida estándar y la entrada estándar a los extremos de las dos tuberías (tenga en cuenta que en linux, pipe() crea tubos unidireccionales, por lo que necesita utilizar dos tubos para su propósito).
  2. Exec ahora comenzará un nuevo proceso que tiene los extremos de las tuberías para stdin y stdout.
  3. Cierre los descriptores no utilizados, escriba la cadena en la tubería y luego comience a leer lo que sea que el proceso se descargue a la otra tubería.

dup() se utiliza para crear una entrada duplicada en la tabla de descriptores de archivos. Mientras dup2() cambia a lo que apunta el descriptor.

Nota: Tal como lo mencionó Ammo @ en su solución, lo que proporcioné anteriormente es más o menos una plantilla, no se ejecutará si solo intentó ejecutar el código ya que claramente hay un exec * (familia de funciones) falta, por lo que el niño terminará casi inmediatamente después de la horquilla().

+0

¿Por qué mantiene una referencia a stdin/stdout con dup, cuando posteriormente están redirigiendo con dup2? dup2 cierra implícitamente su segundo parámetro. –

+1

Tienes razón, eso es innecesario. La respuesta de Ammo anterior es la forma correcta de hacerlo, por favor refiérase a eso. – eerpini

1

El código de Ammo tiene algunos errores al manejar errores. El proceso secundario vuelve después de la falla de dup en lugar de salir. Tal vez los DUP niño pueden ser sustituidos por:

if (dup2(aStdinPipe[PIPE_READ], STDIN_FILENO) == -1 || 
     dup2(aStdoutPipe[PIPE_WRITE], STDOUT_FILENO) == -1 || 
     dup2(aStdoutPipe[PIPE_WRITE], STDERR_FILENO) == -1 
     ) 
    { 
     exit(errno); 
    } 

    // all these are for use by parent only 
    close(aStdinPipe[PIPE_READ]); 
    close(aStdinPipe[PIPE_WRITE]); 
    close(aStdoutPipe[PIPE_READ]); 
    close(aStdoutPipe[PIPE_WRITE]); 
+0

Tiene razón. El proceso secundario no debe regresar desde ese marco de pila y tampoco debe intentar imprimir errores. Probablemente nadie se dio cuenta porque esto nunca sucede. –

+0

Actualizó el código anterior –

Cuestiones relacionadas