posix_spawnp und Weiterleitung der untergeordneten Ausgabe an einen String

Lesezeit: 7 Minuten

Benutzeravatar von rubenvb
rubenvb

Ich kämpfe mit der Prozesserstellung und der Ausgabe des untergeordneten Prozesses in eine Zeichenfolge des übergeordneten Prozesses. Ich habe es unter Windows zum Laufen gebracht (mit CreatePipe und CreateProcess und ReadFile), kann aber anscheinend nicht das genaue Analogon unter Unix zum Laufen bringen. Das ist mein Code:

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

Die Ausgabe ist:

Exitcode: 0

Ich nehme also an, dass alles funktioniert, außer der eigentlichen Verrohrung. Was ist hier falsch? Ich frage mich auch, ob es eine Möglichkeit gibt, die geleiteten Bytes in einer Waitpid-Schleife zu lesen, aber wenn ich das versuche, hängt der übergeordnete Prozess unendlich.

  • Ich hatte noch nie davon gehört posix_spawnp. Was ist falsch an der guten alten popen? Was für eine interessante Systemaufruf-/Bibliotheksfunktion das ist. Werde mir das merken 🙂

    – sehen

    15. Dezember 2012 um 19:44 Uhr


  • popen hat nicht die erforderliche Flexibilität: AFAICT Ich kann stderr damit nicht umleiten. Es ist auch die Funktion, die der am nächsten kommt CreateProcess API. Verdammt, MSDN hat sogar _spawnvp was ähnelt posix_spawnpaber wie gesagt, meine CreateProcess Code funktioniert im Grunde gut. Es ist die Unix-Seite, die derzeit nicht kooperiert 🙁

    – rubenvb

    16. Dezember 2012 um 11:12 Uhr

  • Ich habe das C-Tag hinzugefügt, um das Interesse zu wecken. Obwohl der Code technisch gesehen C++ ist, sind die wichtigen Bits (d. h. das Piping und das Spawning) reines C.

    – rubenvb

    16. Dezember 2012 um 12:54 Uhr

Benutzeravatar von Brent Bradburn
Brent Bradburn

posix_spawn ist interessant und nützlich, was diese Frage nekromantisch wertvoll macht – auch wenn sie für das OP nicht mehr relevant ist.

Es gibt einige signifikante Fehler im geposteten Code. Ich vermute, dass einige davon das Ergebnis von verzweifeltem Hacken waren, aber ich weiß nicht, welches der ursprüngliche Fehler war:

  1. Das args Array enthält nicht die argv[0] das würde den Namen der ausführbaren Datei darstellen. Daraus ergibt sich die echo Programm sieht nie das beabsichtigte argv[1] (“bla”).
  2. Das read() Die Funktion wird von verschiedenen Stellen auf eine Weise aufgerufen, die einfach keinen Sinn ergibt. Ein korrekter Weg, dies zu tun, wäre, nur anzurufen read als Teil des Steuerausdrucks für die while Schleifen.
  3. waitpid() wird vor dem Lesen aus den Pipes aufgerufen. Dies verhindert, dass die E/A abgeschlossen wird (zumindest in nicht trivialen Fällen).
  4. Ein subtileres Problem bei diesem Code ist, dass versucht wird, alle Codes des Kindes zu lesen stdout bevor Sie etwas von lesen stderr. Im Prinzip könnte dies dazu führen, dass das Kind beim Schreiben blockiert stderr, wodurch verhindert wird, dass das Programm beendet wird. Das Erstellen einer effizienten Lösung dafür ist komplizierter, da es erforderlich ist, dass Sie aus jeder Pipe mit verfügbaren Daten lesen können. ich benutzte poll() dafür. Ein anderer Ansatz wäre die Verwendung mehrerer Threads.

Zusätzlich habe ich verwendet sh (die Befehlsshell, dh bash) als untergeordneter Prozess. Dies bietet viel zusätzliche Flexibilität, z. B. das Ausführen einer Pipeline anstelle einer einzelnen ausführbaren Datei. Vor allem aber mit sh bietet den einfachen Komfort, das Parsen der Befehlszeile nicht verwalten zu müssen.

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

  • Ich habe das nicht gründlich zugeknöpft. Es gibt viel Platz für eine bessere Fehlerprüfung und Ressourcenbereinigung.

    – Brent Bradburn

    6. Dezember 2014 um 17:24 Uhr

  • Warum haben Sie nicht nur args übergeben, sondern &args[0] zum 5. Argument von posix_spawn()?

    – Dula

    7. Januar 2016 um 2:28 Uhr

  • @Dula: Das Formular, das ich verwendet habe, ist allgemeiner und für mich klarer. Es funktioniert für Zeiger, aber auch für klassenbasierte Array-Manager wie z std::vector und std::string. Ich verwende dieses Formular an mehreren Stellen, einschließlich &buffer[0]was nicht so einfach funktioniert hätte buffer.

    – Brent Bradburn

    7. Januar 2016 um 15:38 Uhr

  • Hier ist ein Ausschnitt, der anstelle von poll() select() verwendet, github.com/pixley/InvestigativeProgramming/blob/…

    – Neonauge

    28. Mai 2016 um 19:02 Uhr

1432620cookie-checkposix_spawnp und Weiterleitung der untergeordneten Ausgabe an einen String

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy