posix_spawnp y canalizar la salida secundaria a una cadena

7 minutos de lectura

avatar de usuario
rubenvb

Estoy luchando con la creación de procesos y canalizando la salida del proceso secundario a una cadena del proceso principal. Lo hice funcionar en Windows (usando CreatePipe y CreateProcess y ReadFile), pero parece que no puedo hacer que funcione el análogo exacto en Unix. Este es mi código:

#include <spawn.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <iostream>
#include <string>
#include <vector>
using namespace std;

int main()
{
  int exit_code;
  int cout_pipe[2];
  int cerr_pipe[2];
  posix_spawn_file_actions_t action;

  if(pipe(cout_pipe) || pipe(cerr_pipe))
    cout << "pipe returned an error.\n";

  posix_spawn_file_actions_init(&action);
  posix_spawn_file_actions_addclose(&action, cout_pipe[0]);
  posix_spawn_file_actions_addclose(&action, cerr_pipe[0]);
  posix_spawn_file_actions_adddup2(&action, cout_pipe[1], 1);
  posix_spawn_file_actions_adddup2(&action, cerr_pipe[1], 2);

  posix_spawn_file_actions_addclose(&action, cout_pipe[1]);
  posix_spawn_file_actions_addclose(&action, cerr_pipe[1]);

  vector<string> argmem = {"bla"};
  vector<char*> args = {&argmem[0][0], nullptr}; // I don't want to call new.

  pid_t pid;
  if(posix_spawnp(&pid, "echo", &action, NULL, &args[0], NULL) != 0)
    cout << "posix_spawnp failed with error: " << strerror(errno) << "\n";
  //close(cout_pipe[0]);
  //close(cerr_pipe[0]);

  close(cout_pipe[1]);
  close(cerr_pipe[1]);

  waitpid(pid,&exit_code,0);
  cout << "exit code: " << exit_code << "\n";

  // Read from pipes
  const size_t buffer_size = 1024;
  string buffer;
  buffer.resize(buffer_size);
  ssize_t bytes_read = read(cout_pipe[0], &buffer[0], buffer_size);
  while ((bytes_read = read(cout_pipe[0], &buffer[0], buffer_size)) > 0)
  {
    cout << "read " << bytes_read << " bytes from stdout.\n";
    cout << buffer.substr(0, static_cast<size_t>(bytes_read)+1) << "\n";
    bytes_read = read(cout_pipe[0], &buffer[0], buffer_size);
  }
  if(bytes_read == -1)
    cout << "Failure reading from stdout pipe.\n";
  while ((bytes_read = read(cerr_pipe[0], &buffer[0], buffer_size)) > 0)
  {
    cout << "read " << bytes_read << " bytes from stderr.\n";
    cout << buffer.substr(0, static_cast<size_t>(bytes_read)+1) << "\n";
    bytes_read = read(cout_pipe[0], &buffer[0], buffer_size);
  }
  if(bytes_read == -1)
    cout << "Failure reading from stderr pipe.\n";

  posix_spawn_file_actions_destroy(&action);
}

La salida es:

código de salida: 0

Así que supongo que todo funciona excepto las tuberías reales. ¿Que esta mal aquí? También me pregunto si hay una forma de leer los bytes canalizados en un ciclo de espera, pero cuando lo intento, el proceso principal se cuelga infinitamente.

  • nunca había oído hablar posix_spawnp. ¿Qué tiene de malo el buen viejo? popen? Qué interesante función syscall/library es. recordare esto 🙂

    – seje

    15 de diciembre de 2012 a las 19:44


  • popen no tiene la flexibilidad requerida: AFAICT No puedo redirigir stderr con eso. También es la función que más se parece a la CreateProcess API. Diablos, MSDN incluso tiene _spawnvp que se asemeja posix_spawnppero como dije, mi CreateProcess el código básicamente funciona bien. Es el lado de Unix que actualmente no está cooperando 🙁

    – rubenvb

    16 de diciembre de 2012 a las 11:12

  • Agregué la etiqueta C para aumentar el interés. Aunque el código es técnicamente C++, las partes importantes (es decir, las tuberías y el desove) son C puro.

    – rubenvb

    16 de diciembre de 2012 a las 12:54

avatar de usuario
brent bradburn

posix_spawn es interesante y útil, lo que hace que esta pregunta valga la pena, incluso si ya no es relevante para el OP.

Hay algunos errores significativos en el código publicado. Sospecho que algunos de estos fueron el resultado de una piratería desesperada, pero no sé cuál fue el error original:

  1. los args matriz no incluye el argv[0] que representaría el nombre del ejecutable. Esto resulta en el echo el programa nunca ve lo previsto argv[1] (“bla”).
  2. los read() La función se llama desde diferentes lugares de una manera que simplemente no tiene sentido. Una forma correcta de hacerlo sería llamar únicamente read como parte de la expresión de control para la while bucles
  3. waitpid() se llama antes de la lectura de las tuberías. Esto evita que se complete la E/S (al menos en casos no triviales).
  4. Un problema más sutil con este código es que intenta leer todos los stdout antes de leer algo de stderr. En principio, esto podría provocar que el niño se bloquee al intentar escribir en stderr, evitando así que el programa se complete. Crear una solución eficiente para esto es más complicado, ya que requiere que pueda leer desde cualquier tubería que tenga datos disponibles. solía poll() para esto. Otro enfoque sería utilizar varios subprocesos.

Además, he usado sh (el shell de comandos, es decir bash) como proceso hijo. Esto proporciona una gran cantidad de flexibilidad adicional, como ejecutar una canalización en lugar de un solo ejecutable. En particular, sin embargo, usando sh proporciona la simple comodidad de no tener que gestionar el análisis de la línea de comandos.

/*BINFMTCXX: -std=c++11 -Wall -Werror
*/

#include <spawn.h> // see manpages-posix-dev
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <iostream>
#include <string>
#include <vector>
using namespace std;

int main()
{
  int exit_code;
  int cout_pipe[2];
  int cerr_pipe[2];
  posix_spawn_file_actions_t action;

  if(pipe(cout_pipe) || pipe(cerr_pipe))
    cout << "pipe returned an error.\n";

  posix_spawn_file_actions_init(&action);
  posix_spawn_file_actions_addclose(&action, cout_pipe[0]);
  posix_spawn_file_actions_addclose(&action, cerr_pipe[0]);
  posix_spawn_file_actions_adddup2(&action, cout_pipe[1], 1);
  posix_spawn_file_actions_adddup2(&action, cerr_pipe[1], 2);

  posix_spawn_file_actions_addclose(&action, cout_pipe[1]);
  posix_spawn_file_actions_addclose(&action, cerr_pipe[1]);

//string command = "echo bla"; // example #1
  string command = "pgmcrater -width 64 -height 9 |pgmtopbm |pnmtoplainpnm";
  string argsmem[] = {"sh","-c"}; // allows non-const access to literals
  char * args[] = {&argsmem[0][0],&argsmem[1][0],&command[0],nullptr};

  pid_t pid;
  if(posix_spawnp(&pid, args[0], &action, NULL, &args[0], NULL) != 0)
    cout << "posix_spawnp failed with error: " << strerror(errno) << "\n";

  close(cout_pipe[1]), close(cerr_pipe[1]); // close child-side of pipes

  // Read from pipes
  string buffer(1024,' ');
  std::vector<pollfd> plist = { {cout_pipe[0],POLLIN}, {cerr_pipe[0],POLLIN} };
  for ( int rval; (rval=poll(&plist[0],plist.size(),/*timeout*/-1))>0; ) {
    if ( plist[0].revents&POLLIN) {
      int bytes_read = read(cout_pipe[0], &buffer[0], buffer.length());
      cout << "read " << bytes_read << " bytes from stdout.\n";
      cout << buffer.substr(0, static_cast<size_t>(bytes_read)) << "\n";
    }
    else if ( plist[1].revents&POLLIN ) {
      int bytes_read = read(cerr_pipe[0], &buffer[0], buffer.length());
      cout << "read " << bytes_read << " bytes from stderr.\n";
      cout << buffer.substr(0, static_cast<size_t>(bytes_read)) << "\n";
    }
    else break; // nothing left to read
  }

  waitpid(pid,&exit_code,0);
  cout << "exit code: " << exit_code << "\n";

  posix_spawn_file_actions_destroy(&action);
}

  • No abotoné esto completamente. Hay mucho espacio para una mejor verificación de errores y limpieza de recursos.

    –Brent Bradburn

    6 de diciembre de 2014 a las 17:24

  • ¿Por qué no pasaste solo argumentos sino &args?[0] al quinto argumento de posix_spawn()?

    – Dula

    7 de enero de 2016 a las 2:28

  • @Dula: el formulario que utilicé es más general y más claro para mí. Funciona para punteros, pero también para administradores de arreglos basados ​​en clases como std::vector y std::string. Utilizo ese formulario en varios lugares, incluyendo &buffer[0]que no habría funcionado tan solo buffer.

    –Brent Bradburn

    7 de enero de 2016 a las 15:38

  • aquí hay un fragmento que en lugar de poll() usa select(), github.com/pixley/InvestigativeProgramming/blob/…

    – ojo de neón

    28 mayo 2016 a las 19:02

¿Ha sido útil esta solución?