I can grab stdout and stderr separately using fork, execvp, pipe, etc. functions and put them into two separate C++ strings. How can I use this family of functions to combine both stdout and stderr into a single combined string like the shell would as if I am redirecting like "2>&1"? The example below just captures stdout:
#include <sys/wait.h>
#include <unistd.h>
#include <string>
#include <vector>
std::string qx(const std::vector<std::string>& args) {
int stdout_fds[2];
pipe(stdout_fds);
int stderr_fds[2];
pipe(stderr_fds);
const pid_t pid = fork();
if (!pid) {
close(stdout_fds[0]);
dup2(stdout_fds[1], 1);
close(stdout_fds[1]);
close(stderr_fds[0]);
dup2(stderr_fds[1], 2);
close(stderr_fds[1]);
std::vector<char*> vc(args.size() + 1, NULL);
for (size_t i = 0; i < args.size(); ++i) {
vc[i] = const_cast<char*>(args[i].c_str());
}
execvp(vc[0], &vc[0]);
exit(0);
}
close(stdout_fds[1]);
std::string out;
const int buf_size = 4096;
char buffer[buf_size];
do {
const ssize_t r = read(stdout_fds[0], buffer, buf_size);
if (r > 0) {
out.append(buffer, r);
}
} while (errno == EAGAIN || errno == EINTR);
close(stdout_fds[0]);
close(stderr_fds[1]);
close(stderr_fds[0]);
int r, status;
do {
r = waitpid(pid, &status, 0);
} while (r == -1 && errno == EINTR);
return out;
}
int main() {
qx({"openssl", "hjas"});
qx({"openssl", "dkjsah"});
qx({"uname"});
qx({"uname"});
}
Just use one pipe to collect both:
std::string qx(const std::vector<std::string>& args) {
int output[2];
pipe(output);
const pid_t pid = fork();
if (!pid) {
// collect both stdout and stderr to the one pipe
close(output[0]);
dup2(output[1], STDOUT_FILENO);
dup2(output[1], STDERR_FILENO);
close(output[1]);
std::vector<char*> vc(args.size() + 1, NULL);
for (size_t i = 0; i < args.size(); ++i) {
vc[i] = const_cast<char*>(args[i].c_str());
}
execvp(vc[0], &vc[0]);
// if execvp() fails, we do *not* want to call exit()
// since that can call exit handlers and flush buffers
// copied from the parent process
_exit(0);
}
close(output[1]);
std::string out;
const int buf_size = 4096;
char buffer[buf_size];
do {
errno = 0;
const ssize_t r = read(output[0], buffer, buf_size);
if (r > 0) {
out.append(buffer, r);
}
} while (errno == EAGAIN || errno == EINTR);
Note that your original code doesn't collect the child's stderr
output. Should the child write enough to stderr
that the pipe fills up, the child process might block.