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.
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:
- 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”).
- 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.
waitpid()
wird vor dem Lesen aus den Pipes aufgerufen. Dies verhindert, dass die E/A abgeschlossen wird (zumindest in nicht trivialen Fällen).
- 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 hatte noch nie davon gehört
posix_spawnp
. Was ist falsch an der guten altenpopen
? 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 kommtCreateProcess
API. Verdammt, MSDN hat sogar_spawnvp
was ähneltposix_spawnp
aber wie gesagt, meineCreateProcess
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