Linux: ejecución de proceso secundario con stdin/stdout canalizado

Resuelto Andrew Tomazos asked hace 12 años • 3 respuestas

Usando Linux y C++, me gustaría una función que haga lo siguiente:

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

Obviamente lo anterior no funciona, pero ya entiendes la idea. Tengo una cadena sque me gustaría pasar como entrada estándar de la ejecución de un proceso secundario de la aplicación "foo", y luego me gustaría registrar su salida estándar en una cadena ry luego devolverla.

¿Qué combinación de llamadas al sistema Linux o funciones POSIX debo usar? Estoy usando Linux 3.0 y no necesito la solución para trabajar con sistemas más antiguos.

Andrew Tomazos avatar Feb 23 '12 08:02 Andrew Tomazos
Aceptado

El código proporcionado por eerpini no funciona tal como está escrito. Tenga en cuenta, por ejemplo, que los extremos de tubería que están cerrados en el padre se utilizan posteriormente. Mira a

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 utilizado. A continuación se muestra una versión que he probado. Desafortunadamente, cambié el estilo del código, por lo que no fue aceptado como una edición del código de eerpini.

El único cambio estructural es que solo redirijo las E/S en el hijo (tenga en cuenta que las llamadas dup2 solo están en la ruta del hijo). Esto es muy importante, porque de lo contrario las E/S del padre se estropean. Gracias a eerpini por la respuesta inicial, que utilicé para desarrollar esta.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#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;
}
Ammo Goettsch avatar Oct 11 '2012 12:10 Ammo Goettsch

Dado que desea acceso bidireccional al proceso, tendría que hacer lo que popen hace detrás de escena explícitamente con tuberías. No estoy seguro de si algo de esto cambiará en C++, pero aquí hay un ejemplo de C puro:

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

}

Efectivamente haces esto:

  1. Cree tuberías y redirija stdout y stdin a los extremos de las dos tuberías (tenga en cuenta que en Linux, pipe() crea tuberías unidireccionales, por lo que necesita usar dos tuberías para su propósito).
  2. Exec ahora iniciará 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 el proceso pueda volcar en la otra tubería.

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

Nota: Como lo mencionó Ammo@ en su solución, lo que proporcioné anteriormente es más o menos una plantilla, no se ejecutará si simplemente intenta ejecutar el código, ya que claramente falta un exec* (familia de funciones), por lo que el child terminará casi inmediatamente después de la bifurcación().

eerpini avatar Feb 23 '2012 03:02 eerpini

El código de Ammo tiene algunos errores de manejo. El proceso hijo regresa después del fallo del dup en lugar de salir. Quizás los duplicados infantiles puedan reemplazarse con:

    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]);
jws avatar Jan 14 '2014 17:01 jws