0

How can I execute a system command with C++ and capture its output and the status. It should look something like this:

Response launch(std::string command);

int main()
{
    auto response = launch("pkg-config --cflags spdlog");
    std::cout << "status: " << response.get_status() << '\n'; // -> "status: 0"
    std::cout << "output: " << response.get_output() << '\n'; // -> "output: -DSPDLOG_SHARED_LIB -DSPDLOG_COMPILED_LIB -DSPDLOG_FMT_EXTERNAL"
}

Using std::system you can only get status. I also tried this solution but it only captures the output and it seems to be very "hacky" and not safe. There must be a better way to do it but I haven't found one yet. If there isn't a simple and portable solution, I would also use a library.

octowaddle
  • 80
  • 5
  • 2
    "The" solution? It has 17 answers ;) – klutt Feb 17 '21 at 16:39
  • 1
    your question is basically a duplicate of the one you link. Imho there isnt much value in asking: "I found the answer here, but now I am asking for the real answer". If there is a better way it should be posted as answer on the other question. Or is the difference that the other q is about posix and you are looking for windows? – 463035818_is_not_an_ai Feb 17 '21 at 16:47
  • @largest_prime_is_463035818 The answer does not capture the status but only the output. – octowaddle Feb 17 '21 at 16:51
  • ok, don't get me wrong. My comment was not meant as offense. My first impression was that its the same question, though actually you already say how it is different: "only captures the output". – 463035818_is_not_an_ai Feb 17 '21 at 16:54
  • Bear with me if this is a poor comment. Maybe this is an issue of understanding encapsulation. The command you're launching may be a child process, but that doesn't mean you control it's output. The ways to get its output are going to be: 1. if you control "pkg-config", make it return what you want or 2. Make pkg-config pipe its output to a file that you can subsequently read. I think "hacky" is your only real option here, but it's likely not as bad as you think. – Skewjo Feb 17 '21 at 17:01

1 Answers1

0

I found a way with redirecting the output to a file and reading from it:

#include <string>
#include <fstream>
#include <filesystem>

struct response
{
    int status = -1;
    std::string output;
};

response launch(std::string command)
{
    // set up file redirection
    std::filesystem::path redirection = std::filesystem::absolute(".output.temp");
    command.append(" &> \"" + redirection.string() + "\"");

    // execute command
    auto status = std::system(command.c_str());

    // read redirection file and remove the file
    std::ifstream output_file(redirection);
    std::string output((std::istreambuf_iterator<char>(output_file)), std::istreambuf_iterator<char>());
    std::filesystem::remove(redirection);

    return response{status, output};
}

Still seems a bit "hacky" but it works. I would love to see a better way without creating and removing files.

octowaddle
  • 80
  • 5
  • This is hacky for real. Using the operating system's support for redirecting the I/O is _not_ hacky. You are moving away from the "better" solution. – Ted Lyngmo Feb 17 '21 at 18:04
  • @TedLyngmo How would you do this? If you mean the answer I referenced: it does not capture the status. – octowaddle Feb 17 '21 at 18:37
  • If I couldn't use [`boost::process`](https://www.boost.org/doc/libs/release/doc/html/process.html) or a similar already written class to do it, I'd just read up on the OS functions and implement it myself. I've done it on posix platforms and windows so it's not _that_ much work. If you need the exit status (which sounds good), you should probably not use a `unique_ptr` to close the file descriptors so you could create a small `unique_ptr`-like class which is capable of holding the exit status. The second answer you referenced is capable of getting the exit status btw. – Ted Lyngmo Feb 17 '21 at 19:03