21

Given the following answer (first c++11 answer):

How do I execute a command and get the output of the command within C++ using POSIX?

Here is the implementation for your convenience:

#include <cstdio>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <array>

std::string exec(const char* cmd) {
    std::array<char, 128> buffer;
    std::string result;
    std::shared_ptr<FILE> pipe(popen(cmd, "r"), pclose);
    if (!pipe) throw std::runtime_error("popen() failed!");
    while (!feof(pipe.get())) {
        if (fgets(buffer.data(), 128, pipe.get()) != nullptr)
            result += buffer.data();
    }
    return result;
}

This works really nicely to execute a command (e.g. std::string res = exec("ls");) and get the stdout into a string.

But what it does not do is get the command return code (pass/fail integer) or the stderr. Ideally I would like a way to get all three (return code, stdout, stderr).

I would settle for stdout and stderr. I am thinking that I need to add another pipe, but I can't really see how the first pipe is setup to get stdout so I can't think how I would change it to get both.

Any one got any ideas how to do that, or alternative approaches that may work?

update

See my complete example here with the output:

Start
1 res: /home

2 res: stdout

stderr
3 res: 
End

You can see that 3 res: does not print stderr in the same way that 2 res: stdout does, but stderr is just dumped onto the screen on a separate line by the process (and not my program).

External Libs

I really don't want to use external libraries like Qt and boost - mostly because I want the portability of it and also many projects that I work on don't use boost. However I will mark up solutions that contain these options as they are valid for other users :)

Complete Solution Using Comments/Answer

Thanks all for your answers / comments, here is the modified solution (and runable):

working-solution

parsley72
  • 8,449
  • 8
  • 65
  • 98
code_fodder
  • 15,263
  • 17
  • 90
  • 167
  • 1
    Is `boost` an option? If so you might want to have a look at [boost::process](https://www.boost.org/doc/libs/1_68_0/doc/html/boost_process/tutorial.html). – G.M. Sep 04 '18 at 11:11
  • @user1810087 hmm...no I would prefer to keep it pure c++/c++11. In my particular project we have chosen not to use boost libs. – code_fodder Sep 04 '18 at 11:12
  • There is kind of workaround possible. You may redirect the stderr to stdout by appending "2>&1" to your cmd. Would this suit your needs? – Alex Sep 04 '18 at 11:13
  • @Alex damn...that is clever, I use that all the time in bash scripts but did not think to employ it here!... if it works, then yes : ) .. I would still profer to do a c++ version, but that is a good workaround idea sir...testing now... – code_fodder Sep 04 '18 at 11:15
  • Ideally... posix is ok since that is what I use. In my real code I have a minor devaition for windows code (msvs uses `_popen` instead of `popen`) and I have a #define to do that... so I already have some specific OS stuff... hard to avoid completely :( – code_fodder Sep 04 '18 at 11:20
  • You should read the [Advanced Linux Programming](http://www.cse.hcmut.edu.vn/~hungnq/courses/nap/alp.pdf) book or something newer. An entire book could be needed to answer. Consider also [POCO](https://pocoproject.org/) libraries. You might want some event loop (or library) around [poll(2)](http://man7.org/linux/man-pages/man2/poll.2.html). See also [syscalls(2)](http://man7.org/linux/man-pages/man2/syscalls.2.html) – Basile Starynkevitch Sep 04 '18 at 11:23
  • 1
    @Alex that worked nicely - please add your comment as an answer and I will vote it up – code_fodder Sep 04 '18 at 11:40
  • Glad to know, thanks! Added – Alex Sep 04 '18 at 11:41
  • Is this solution safe to be used from different threads or will it block? – parsley72 Nov 15 '22 at 01:57

4 Answers4

25

From the man-page of popen:

The pclose() function waits for the associated process to terminate  and returns the exit status of the command as returned by wait4(2).

So, calling pclose() yourself (instead of using std::shared_ptr<>'s destructor-magic) will give you the return code of your process (or block if the process has not terminated).

std::string exec(const char* cmd) {
    std::array<char, 128> buffer;
    std::string result;

    auto pipe = popen(cmd, "r"); // get rid of shared_ptr

    if (!pipe) throw std::runtime_error("popen() failed!");

    while (!feof(pipe)) {
        if (fgets(buffer.data(), buffer.size(), pipe) != nullptr)
            result += buffer.data();
    }

    auto rc = pclose(pipe);

    if (rc == EXIT_SUCCESS) { // == 0

    } else if (rc == EXIT_FAILURE) {  // EXIT_FAILURE is not used by all programs, maybe needs some adaptation.

    }
    return result;
}

Getting stderr and stdout with popen(), I'm afraid you'd need to redirect the output of stderr to stdout from the command-line you're passing to popen() by adding 2>&1. This has the inconvinience that both streams are unpredictably mixed.

If you really want to have two distinguished file-descriptors for stderr and stdout, one way to do it is to do the forking yourself and to duplicate the new processes stdout/stderr to two pipes which are accessible from the parent process. (see dup2() and pipe()). I could go into more detail here, but this is quite a tedious way of doing things and much care must be taken. And the internet is full of examples.

Zitrax
  • 19,036
  • 20
  • 88
  • 110
Patrick B.
  • 11,773
  • 8
  • 58
  • 101
6

There is kind of workaround possible. You may redirect the stderr to stdout by appending "2>&1" to your cmd. Would this suit your needs?

Alex
  • 163
  • 7
5

You can get the return code from the pipe by using a custom deleter as such:

#include <cstdio>
#include <iostream>
#include <memory>
#include <string>
#include <array>
#include <utility>

using namespace std;
pair<string, int> exec(const char* cmd) {
    array<char, 128> buffer;
    string result;
    int return_code = -1;
    auto pclose_wrapper = [&return_code](FILE* cmd){ return_code = pclose(cmd); };
    { // scope is important, have to make sure the ptr goes out of scope first
    const unique_ptr<FILE, decltype(pclose_wrapper)> pipe(popen(cmd, "r"), pclose_wrapper);
    if (pipe) {
        while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
            result += buffer.data();
        }
    }
    }
    return make_pair(result, return_code);
}

int main(int argc, char* argv[]) {
    if (argc <= 1) return 0;
    cout << "calling with " << argv[1] << '\n';
    const auto process_ret = exec(argv[1]);
    cout << "captured stdout : " << '\n' << process_ret.first << endl;
    cout << "program exited with status code " << process_ret.second << endl;
    return 0;
} 
Zhou Shao
  • 116
  • 1
  • 4
5

Below is a slightly different version of Patrick. B answer, that uses fread() instead of fgets().

The use of fread() was suggested by Antti Haapala in this thread:

How do I execute a command and get the output of the command within C++ using POSIX?

This version reads stdout to a std::string, which must be passed as the 2nd argument.

The function returns the exit code of the execution of cmd, as returned from pclose().

To get also stderr, add to the end of cmd: "2>&1", as suggested by Alex in this same thread.

main() function below, shows how to use:

int execute(std::string cmd, std::string& output)

to list the current directory and print it to stdout:

#include <iostream>
#include <array>
#include <unistd.h>
    
int execute(std::string cmd, std::string& output) {
    const int bufsize=128;
    std::array<char, bufsize> buffer;

    auto pipe = popen(cmd.c_str(), "r");
    if (!pipe) throw std::runtime_error("popen() failed!");

    size_t count;
    do {
        if ((count = fread(buffer.data(), 1, bufsize, pipe)) > 0) {
            output.insert(output.end(), std::begin(buffer), std::next(std::begin(buffer), count));
        }
    } while(count > 0);

    return pclose(pipe);
}    
    
int main(int argc, char** argv) {
std::string output;

    execute("ls", output);
    std::cout << output;
}
Helder Daniel
  • 331
  • 3
  • 8
  • 1
    Thanks for this update. I am just wandering (and this is genuine curiosity and not a criticism) what would be the advantage to this approach? For me a std::string output is good, what benefit does a vector give us? - I think strings would be easier to perform string operations on (like find etc). Unless this is for handling commands that have binary output? - thanks! (+1 btw) – code_fodder May 22 '20 at 21:07
  • 1
    @code_fodder you are welcome. Well, yes the idea wat to use a vector to store raw data. If needed to transform it in a string, one could just `std::string(aVector.data())`, but I guess you are right. Here a `std::string` is probably what is expect. I changed the code to store stdout in a `std::string`. There was also an error that allows garbage to be stored at the end of the vector (or string), that was corrected. – Helder Daniel May 22 '20 at 22:18
  • As you say string is probably the most common requirement for an output, but I think this is a nice option to have if you are getting some sort of raw/binary output - I can think of a few places that might be useful, thanks : ) – code_fodder May 22 '20 at 22:21
  • I edited this answer but apparently it's not going through (StackOverflow is becoming very unwelcoming). There is a mistake in "std::next(std::begin(buffer), count-1)". It should be "count" not "count-1". Iterator must be one past the end. The current answer will give you an output with missing characters. – 4nt Aug 09 '20 at 07:07
  • 1
    @4nt, well yes, it prevents the last character received from the pipe and stored in buffer.data(), to be added to the string output. The idea was to prevent the trailing '\n' character from a linux command output to be added to the string. This way the user can choose to add a '\n' or not, depending on the use case. But you are right. The last character read from the pipe is not stored in the output string, whether it is a '\n' or whatever. The code was edited with your suggestion. thanks. I also corrected some errors in the #includes and the call to execute() in main(). – Helder Daniel Aug 09 '20 at 16:30