posix_spawnp and piping child output to a string
posix_spawn
is interesting and useful, which makes this question worth necromancing -- even if it is no longer relevant to the OP.
There are some significant bugs in the code as posted. I suspect that some of these were the result of hacking in desperation, but I don't know which was the original bug:
- The
args
array does not include theargv[0]
that would represent the executable name. This results in theecho
program never seeing the intendedargv[1]
("bla"). - The
read()
function is called from different places in a way that just doesn't make sense. A correct way to do this would be to only callread
as part of the control expression for thewhile
loops. waitpid()
is called before reading from the pipes. This prevents the I/O from completing (in non-trivial cases at least).- A more subtle issue with this code is that attempts to read all of the child's
stdout
before reading anything fromstderr
. In principle, this could cause the child to block while attempting to write tostderr
, thus preventing the program from completing. Creating an efficient solution to this is more complicated as it requires that you can read from whichever pipe has available data. I usedpoll()
for this. Another approach would be to use multiple threads.
Additionally, I have used sh
(the command shell, i.e. bash
) as the child process. This provides a great deal of additional flexibility, such as running a pipeline instead of a single executable. In particular, though, using sh
provides the simple convenience of not having to manage the parsing of the command-line.
/*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);}