587

I am looking for a way to get the output of a command when it is run from within a C++ program. I have looked at using the system() function, but that will just execute a command. Here's an example of what I'm looking for:

std::string result = system("./some_command");

I need to run an arbitrary command and get its output. I've looked at boost.org, but I have not found anything that will give me what I need.

Jonas Stein
  • 6,826
  • 7
  • 40
  • 72
Misha M
  • 10,979
  • 17
  • 53
  • 65
  • 3
    Also see answers in this question:`https://stackoverflow.com/questions/52164723/how-to-execute-a-command-and-get-return-code-stdout-and-stderr-of-command-in-c` for an extension of the great answer below that provides methods to get the `return code` and `stderr` as well as `stdout` that this answer already explains – code_fodder Sep 04 '18 at 12:21
  • 8
    @code_fodder you can create a link to https://stackoverflow.com/questions/52164723/how-to-execute-a-command-and-get-return-code-stdout-and-stderr-of-command-in-c – Jonas Stein Nov 10 '19 at 16:47
  • 1
    Here are 5 questions and answers for C and/or C++ which seem to touch on this topic: 1) [how to read from stdout in C](https://stackoverflow.com/q/24214038/4561887), 2) [C: Run a System Command and Get Output?](https://stackoverflow.com/q/646241/4561887), 3) [How can I run an external program from C and parse its output?](https://stackoverflow.com/q/43116/4561887), 4) [Capturing stdout from a system() command optimally](https://stackoverflow.com/q/125828/4561887), 5) (this question). – Gabriel Staples Feb 01 '21 at 22:11

12 Answers12

774
#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::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
    if (!pipe) {
        throw std::runtime_error("popen() failed!");
    }
    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
        result += buffer.data();
    }
    return result;
}

Pre-C++11 version:

#include <iostream>
#include <stdexcept>
#include <stdio.h>
#include <string>

std::string exec(const char* cmd) {
    char buffer[128];
    std::string result = "";
    FILE* pipe = popen(cmd, "r");
    if (!pipe) throw std::runtime_error("popen() failed!");
    try {
        while (fgets(buffer, sizeof buffer, pipe) != NULL) {
            result += buffer;
        }
    } catch (...) {
        pclose(pipe);
        throw;
    }
    pclose(pipe);
    return result;
}

Replace popen and pclose with _popen and _pclose for Windows.

gregpaton08
  • 383
  • 3
  • 7
waqas
  • 10,323
  • 2
  • 20
  • 11
  • 97
    Be aware that this will only grab _stdout_ and not _stderr_. – kalaxy Oct 31 '11 at 23:53
  • 16
    Also be aware that an exception can occur in `result += buffer`, so the pipe might not be properly closed. – Fred Foo May 19 '12 at 20:27
  • @larsmans But it is because of a code error out of this function, right, not something that may be expected? – Jānis Elmeris Mar 15 '13 at 11:23
  • 1
    when is this ever going to give "ERROR" as a return value from the function. Under what circumstances? – Igbanam Nov 14 '13 at 05:31
  • 7
    @Yasky: When the program being executed is `int main(){ puts("ERROR"); }`. – dreamlax Dec 16 '13 at 21:03
  • If this is C++, you should throw an exception, not return `"ERROR"`. – Mark Lakata Aug 27 '14 at 23:47
  • 11
    The answer is good but it would be better if you replace 'char* cmd' with 'const char* cmd' – fnc12 Dec 27 '14 at 14:20
  • 1
    @fnc12 I think it showed me some error and I actually did that. You might want to edit the answer to include your suggestion – ᴍᴇʜᴏᴠ Sep 25 '15 at 07:46
  • 4
    Why use `while (!feof(...))` and an `if` statement in the loop, when all you have to do is `while (fgets(...) != NULL)`? – Some programmer dude Feb 16 '16 at 12:28
  • 1
    @JoachimPileborg Its actually safer too http://faq.cprogramming.com/cgi-bin/smartfaq.cgi?answer=1046476070&id=1043284351 – QuantumKarl May 28 '16 at 23:01
  • 33
    unique_ptr is a better fit here, where the actual reference count is never used. – Czipperz May 29 '16 at 08:42
  • Simply changing `popen` and `pclose` into `_pclose` and `_popen` seems not enough for getting this running under Windows (producing, as you'll guess it, `"popen() failed!"`), whereas [TarmoPikaro's solution](http://stackoverflow.com/a/35658917/2932052) works. – Wolf May 16 '17 at 10:18
  • 1
    You could use `constexpr` on the buffer size and avoid specifying a literal twice. – Trevor Hickey Jul 06 '17 at 18:09
  • 8
    Bless your soul for the pre-C++11 version. –  Jul 06 '17 at 22:38
  • This code is just what I'm looking for! but I have one more problem. With windows programs like notepad.exe the while (!feof(pipe)) never finished. Do you have any idea how I can solve without adding a 'boolean isGui' var? Thanks! – arturn Oct 27 '17 at 06:51
  • 1
    Is there a danger of the shell'd command never responding/exiting? – ColH Jul 27 '18 at 14:26
  • 8
    `while (!feof())` is always wrong, and `fgets` is equally wrong when you're not really reading lines. Use `fread` and check the return value. – Antti Haapala -- Слава Україні Oct 19 '18 at 04:46
  • @waqas is possible to return only instanceType from result. I'm new to this. Please help. Thanks in advance – Alex Mathew Nov 20 '18 at 05:00
  • C++11 example doesn't compile; unnecessary curly bracket before return statement. Also, I would use fread instead of fgets, no need to split on newlines etc. – Darren Smith Nov 29 '18 at 15:13
  • 14
    Is this still the best practice with C++17? – Aaron Franke Feb 27 '19 at 09:52
  • 2
    it's not best practice even in c++11, as fgets doesn't actually return the number of read bytes so reading a `'\0'` would break the code @AaronFranke , as @AnttiHaapala suggests fread should be used instead – xception Sep 11 '19 at 11:26
  • It is important to understand that this launches a shell which performs the command argument tokenization. – Steven Lu Jan 10 '20 at 23:54
  • How can I pass .bat file as argument instead of cmd? – kittu May 20 '20 at 18:41
  • 7
    Why buffer size exactly 128 ? – Ahmed Hussein Jun 12 '20 at 19:36
  • But what if you need the return value from pclose() ? :) – 0x0C4 Aug 11 '20 at 09:07
  • @0x0C4 use pre-C++11 version or write a special deleter: `auto pipe_deleter = [&exit_status](FILE* stream) { exit_status = pclose(stream); };` – Mikolasan Jun 29 '21 at 07:17
  • This breaks for me for the output of `fping`. Apparently this is related to what @xception said, because if I go to debug and check buffer it indeed includes `\0`. However, I also tried `while (fread(buffer, 1, ftell(pipe.get()), pipe.get()) > 0)` and this will just get the first line. Can someone fix that for me? – Max Feb 24 '22 at 16:15
  • As I said you should use fread instead, and account for the fact that fread returns the number of bytes read... I hope you can take it further from there. fread only has 1 extra parameter which in this example is the second parameter and should have the value 1, read the man page for the rest of the info – xception Feb 25 '22 at 09:24
  • Beware of naming confusion with [the POSIX standard family of 'exec' functions](https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html) – Ben Voigt Mar 14 '22 at 18:35
  • @Wolf Could `pclose` works well when the `popen` returns NULL? – John May 24 '22 at 11:17
  • @FredFoo It's seems there is no such problem(i.e. the pipe might not be properly closed)now. Am I right? – John May 24 '22 at 11:19
  • @John That's long ago, I'm not sure what my comment was about (maybe something like this https://stackoverflow.com/q/40758828/2932052 ?). But `pclose` should not be attempted if `popen` has already failed, even though there may be APIs that are tolerant in handling such *“null handles”*. – Wolf May 24 '22 at 15:42
  • 1
    @Wolf If I understand you correctly, the code snippet in this answer needs to be improved. You see, `std::unique_ptr pipe(popen(cmd, "r"), pclose)` would call `pclose` even if `popen` fails. How do you think about it? – John May 25 '22 at 02:08
  • @John Sorry, I have not the resources to deal with your question. If you fail to search for it, maybe you should ask a new one about this detail of the `std::unique_ptr` approach. (In that case, it might be beneficial to link the new question here as well.) – Wolf May 25 '22 at 06:48
  • 1
    @Wolf I see. Here is the new question.https://stackoverflow.com/questions/72371379/pclose-with-null-parameter – John May 25 '22 at 06:55
  • @John I don't see why `pclose` would be called if `popen` fails. The [`std::unique_ptr` destructor](https://en.cppreference.com/w/cpp/memory/unique_ptr/~unique_ptr) (and also the [`reset`](https://en.cppreference.com/w/cpp/memory/unique_ptr/reset) function) doesn't call the deleter if it's not holding a value: "If `get() == nullptr` there are no effects. Otherwise, the owned object is destroyed via `get_deleter()(get())`." – Kevin Jun 24 '22 at 20:18
  • If I use popen like this (unique_ptr version) from multiple threads on the same binary I get a segmentation fault – Avrdan Aug 24 '22 at 14:13
  • The `std::array` appears to be completely pointless - how is it better than the `char` version? – EML Feb 20 '23 at 13:40
101

Getting both stdout and stderr (and also writing to stdin, not shown here) is easy peasy with my pstreams header, which defines iostream classes that work like popen:

#include <pstream.h>
#include <string>
#include <iostream>

int main()
{
  // run a process and create a streambuf that reads its stdout and stderr
  redi::ipstream proc("./some_command", redi::pstreams::pstdout | redi::pstreams::pstderr);
  std::string line;
  // read child's stdout
  while (std::getline(proc.out(), line))
    std::cout << "stdout: " << line << '\n';
  // if reading stdout stopped at EOF then reset the state:
  if (proc.eof() && proc.fail())
    proc.clear();
  // read child's stderr
  while (std::getline(proc.err(), line))
    std::cout << "stderr: " << line << '\n';
} 
Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • popen is the correct solution to use when either input or output communication is desired from the child process, but not both. – DragonLord Jun 23 '12 at 20:44
  • 32
    I disagree. `popen` requires you to use the C stdio API, I prefer the iostreams API. `popen` requires you to manually clean up the `FILE` handle, pstreams do that automatically. `popen` only accepts a `const char*` for the argument, which requires care to avoid shell injection attacks, pstreams allows you to pass a vector of strings similar to `execv`, which is safer. `popen` gives you nothing but a pipe, pstreams tells you the child's PID allowing you to send signals e.g. to kill it if it's blocked or not exiting. All of those are advantages even if you only want unidirectional IO. – Jonathan Wakely Oct 10 '12 at 17:08
  • 1
    Another issue with this solution is if the child writes to stderr enough to fill the buffers and block before it starts writing to stdout. The parent will block reading stdout, while the child is blocked waiting for stderr to be read. resource deadlock! At least one of those loops would be better as asynchronous (i.e., threaded). – Jesse Chisholm Dec 17 '15 at 16:54
  • 1
    @JesseChisholm, yes, that could be a problem. But you don't need to use threads because pstreams allows an approximation of non-blocking I/O using the iostream interface, specifically using the [readsome](http://en.cppreference.com/w/cpp/io/basic_istream/readsome) function, which checks for readiness using `pstreambuf::in_avail()`, so won't block. That allows demultiplexing on the process' stdout and stderr as each has data available. `pstreambuf::in_avail()` only works 100% reliably if the OS supports the non-standard FIONREAD ioctl, but that is supported on (at least) GNU/Linux and Solaris. – Jonathan Wakely Dec 17 '15 at 17:23
  • @JonathanWakely - good to know that `pstreams` has the necessary support. BTW, `FIONREAD` is also available in Windows, for those working over there. – Jesse Chisholm Dec 19 '15 at 13:34
  • 13
    @chiliNUT the new 1.0.1 release uses the Boost licence. – Jonathan Wakely Feb 03 '17 at 13:50
  • 1
    @JonathanWakely how can i kill the ipstream after say a 5 second timeout? – A. K. Jan 09 '20 at 13:04
  • Also string_view isn't supported for basic_ipstream. – A. K. Jan 09 '20 at 13:26
  • 1
    @A.K. you'd have to implement the timeout yourself using non-blocking reads (i.e. `readsome`) and a loop. Adding string_view support is simple, once I get a round tuit. – Jonathan Wakely Jan 09 '20 at 13:43
  • @JonathanWakely Is there a way to get the return value from the process? – CK. Jan 07 '21 at 19:39
  • 1
    @CK. read the FAQ: http://pstreams.sourceforge.net/faq.html#faq_exit_status – Jonathan Wakely Jan 08 '21 at 21:51
  • @AdityaG15 the process' current working directory has nothing to do with where the executable is installed. It depends where you run it from, not where it's installed. And after changing the working directory, a new process created by a pstreams object will inherit the changed working directory from the parent process. So I think you are mistaken. – Jonathan Wakely Mar 02 '21 at 18:30
  • @JonathanWakely yes I also meant that, i have my executable in build/ directory, say i run it from a directory inside build/ with ../exec, the working directory should have been the directory where i executed this, but it executed as if in build/ and behaviour of std::system and pstreams was SURELY different (adjacent to each other, same command, in my case it was `git rev-parse HEAD`) that time. I will check it again though, thanks for your efforts on the library :D – AdityaG15 Mar 02 '21 at 18:56
  • @AdityaG15 you are [mistaken](https://wandbox.org/permlink/TYJo7m6y0RsVP1LH). – Jonathan Wakely Mar 02 '21 at 21:46
  • Good solution, but requires custom code, hence my downvote. – Richard Lalancette Aug 26 '21 at 01:11
  • Thanks. How can you kill a started redi::ipstream? I tried proc.rdbuf()->kill(15) without success. – Tim Autin Apr 06 '22 at 12:36
  • @TimAutin that will send `SIGTERM` to the child process. If that doesn't kill it, that's your child process' fault. `kill(9)` will be more aggressive. What are you expecting to happen? Maybe the child was killed but is a zombie until you `wait` for it. – Jonathan Wakely Apr 07 '22 at 13:55
  • SIGTERM does kill the process (it's an FFmpeg instance). I switched to libexecstream, it works now (with SIGTERM, no need for SIGKILL). Thanks anyway! – Tim Autin Apr 07 '22 at 15:29
46

For Windows, popen also works, but it opens up a console window - which quickly flashes over your UI application. If you want to be a professional, it's better to disable this "flashing" (especially if the end-user can cancel it).

So here is my own version for Windows:

(This code is partially recombined from ideas written in The Code Project and MSDN samples.)

#include <windows.h>
#include <atlstr.h>
//
// Execute a command and get the results. (Only standard output)
//
CStringA ExecCmd(
    const wchar_t* cmd              // [in] command to execute
)
{
    CStringA strResult;
    HANDLE hPipeRead, hPipeWrite;

    SECURITY_ATTRIBUTES saAttr = {sizeof(SECURITY_ATTRIBUTES)};
    saAttr.bInheritHandle = TRUE; // Pipe handles are inherited by child process.
    saAttr.lpSecurityDescriptor = NULL;

    // Create a pipe to get results from child's stdout.
    if (!CreatePipe(&hPipeRead, &hPipeWrite, &saAttr, 0))
        return strResult;

    STARTUPINFOW si = {sizeof(STARTUPINFOW)};
    si.dwFlags     = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
    si.hStdOutput  = hPipeWrite;
    si.hStdError   = hPipeWrite;
    si.wShowWindow = SW_HIDE; // Prevents cmd window from flashing.
                              // Requires STARTF_USESHOWWINDOW in dwFlags.

    PROCESS_INFORMATION pi = { 0 };

    BOOL fSuccess = CreateProcessW(NULL, (LPWSTR)cmd, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
    if (! fSuccess)
    {
        CloseHandle(hPipeWrite);
        CloseHandle(hPipeRead);
        return strResult;
    }

    bool bProcessEnded = false;
    for (; !bProcessEnded ;)
    {
        // Give some timeslice (50 ms), so we won't waste 100% CPU.
        bProcessEnded = WaitForSingleObject( pi.hProcess, 50) == WAIT_OBJECT_0;

        // Even if process exited - we continue reading, if
        // there is some data available over pipe.
        for (;;)
        {
            char buf[1024];
            DWORD dwRead = 0;
            DWORD dwAvail = 0;

            if (!::PeekNamedPipe(hPipeRead, NULL, 0, NULL, &dwAvail, NULL))
                break;

            if (!dwAvail) // No data available, return
                break;

            if (!::ReadFile(hPipeRead, buf, min(sizeof(buf) - 1, dwAvail), &dwRead, NULL) || !dwRead)
                // Error, the child process might ended
                break;

            buf[dwRead] = 0;
            strResult += buf;
        }
    } //for

    CloseHandle(hPipeWrite);
    CloseHandle(hPipeRead);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    return strResult;
} //ExecCmd
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
TarmoPikaro
  • 4,723
  • 2
  • 50
  • 62
  • 1
    This is my favourite solution for Windows, I hope you forgive my changes. I'd suggest to make the const-cast more explicit, whereas I consider the explicit usage of `wchar_t` and `CreateProcessW` as an unnecessary restriction. – Wolf May 16 '17 at 10:13
  • Do you see any problem or potential problem with this cast ? I prefer to keep code at minimum and don't write it without need. – TarmoPikaro May 17 '17 at 16:27
  • 6
    After reading [CreateProcess function (Windows)](https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v=vs.85).aspx), I see a real danger in doing this: *`The Unicode version of this function, CreateProcessW, can modify the contents of this string. Therefore, this parameter cannot be a pointer to read-only memory (such as a const variable or a literal string). If this parameter is a constant string, the function may cause an access violation.`* So it's maybe better to copy the command line into a separate buffer first, to prevent the caller from getting its original input changed. – Wolf May 18 '17 at 12:29
  • 1
    This answer does not handle stderr properly. – Refael Sheinker Feb 14 '19 at 18:39
  • Does this also work for Unix systems? Or would I have to use something else for a Unix device? – 255.tar.xz Oct 28 '19 at 00:28
39

I'd use popen() (++waqas).

But sometimes you need reading and writing...

It seems like nobody does things the hard way any more.

(Assuming a Unix/Linux/Mac environment, or perhaps Windows with a POSIX compatibility layer...)

enum PIPE_FILE_DESCRIPTERS
{
  READ_FD  = 0,
  WRITE_FD = 1
};

enum CONSTANTS
{
  BUFFER_SIZE = 100
};

int
main()
{
  int       parentToChild[2];
  int       childToParent[2];
  pid_t     pid;
  string    dataReadFromChild;
  char      buffer[BUFFER_SIZE + 1];
  ssize_t   readResult;
  int       status;

  ASSERT_IS(0, pipe(parentToChild));
  ASSERT_IS(0, pipe(childToParent));

  switch (pid = fork())
  {
    case -1:
      FAIL("Fork failed");
      exit(-1);

    case 0: /* Child */
      ASSERT_NOT(-1, dup2(parentToChild[READ_FD], STDIN_FILENO));
      ASSERT_NOT(-1, dup2(childToParent[WRITE_FD], STDOUT_FILENO));
      ASSERT_NOT(-1, dup2(childToParent[WRITE_FD], STDERR_FILENO));
      ASSERT_IS(0, close(parentToChild [WRITE_FD]));
      ASSERT_IS(0, close(childToParent [READ_FD]));

      /*     file, arg0, arg1,  arg2 */
      execlp("ls", "ls", "-al", "--color");

      FAIL("This line should never be reached!!!");
      exit(-1);

    default: /* Parent */
      cout << "Child " << pid << " process running..." << endl;

      ASSERT_IS(0, close(parentToChild [READ_FD]));
      ASSERT_IS(0, close(childToParent [WRITE_FD]));

      while (true)
      {
        switch (readResult = read(childToParent[READ_FD],
                                  buffer, BUFFER_SIZE))
        {
          case 0: /* End-of-File, or non-blocking read. */
            cout << "End of file reached..."         << endl
                 << "Data received was ("
                 << dataReadFromChild.size() << "): " << endl
                 << dataReadFromChild                << endl;

            ASSERT_IS(pid, waitpid(pid, & status, 0));

            cout << endl
                 << "Child exit staus is:  " << WEXITSTATUS(status) << endl
                 << endl;

            exit(0);


          case -1:
            if ((errno == EINTR) || (errno == EAGAIN))
            {
              errno = 0;
              break;
            }
            else
            {
              FAIL("read() failed");
              exit(-1);
            }

          default:
            dataReadFromChild . append(buffer, readResult);
            break;
        }
      } /* while (true) */
  } /* switch (pid = fork())*/
}

You also might want to play around with select() and non-blocking reads.

fd_set          readfds;
struct timeval  timeout;

timeout.tv_sec  = 0;    /* Seconds */
timeout.tv_usec = 1000; /* Microseconds */

FD_ZERO(&readfds);
FD_SET(childToParent[READ_FD], &readfds);

switch (select (1 + childToParent[READ_FD], &readfds, (fd_set*)NULL, (fd_set*)NULL, & timeout))
{
  case 0: /* Timeout expired */
    break;

  case -1:
    if ((errno == EINTR) || (errno == EAGAIN))
    {
      errno = 0;
      break;
    }
    else
    {
      FAIL("Select() Failed");
      exit(-1);
    }

  case 1:  /* We have input */
    readResult = read(childToParent[READ_FD], buffer, BUFFER_SIZE);
    // However you want to handle it...
    break;

  default:
    FAIL("How did we see input on more than one file descriptor?");
    exit(-1);
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mr.Ree
  • 8,320
  • 27
  • 30
  • 2
    The hard way is right :) I like the idea with select() call, though in this case, I actually need to wait until the task completes. I'll keep this code for another project I have :) – Misha M Jan 27 '09 at 02:21
  • 4
    ...or you could use the existing posix_spawnp function – Per Johansson Jan 03 '13 at 15:18
  • 7
    Your `execlp` call has a bug: the last `arg` pointer passed must be `(char *) NULL` to properly terminate the variadic argument list (see `execlp(3)` for reference). – Kristóf Marussy Oct 19 '13 at 18:38
  • 1
    Will this work on unix, linux and windows ? Can you please header files as well? – kittu May 20 '20 at 18:34
21

Two possible approaches:

  1. I don't think popen() is part of the C++ standard (it's part of POSIX from memory), but it's available on every UNIX I've worked with (and you seem to be targeting UNIX since your command is ./some_command).

  2. On the off-chance that there is no popen(), you can use system("./some_command >/tmp/some_command.out");, then use the normal I/O functions to process the output file.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
13

I couldn't figure out why popen/pclose is missing from Code::Blocks/MinGW. So I worked around the problem by using CreateProcess() and CreatePipe() instead.

Here's the solution that worked for me:

//C++11
#include <cstdio>
#include <iostream>
#include <windows.h>
#include <cstdint>
#include <deque>
#include <string>
#include <thread>

using namespace std;

int SystemCapture(
    string         CmdLine,    //Command Line
    string         CmdRunDir,  //set to '.' for current directory
    string&        ListStdOut, //Return List of StdOut
    string&        ListStdErr, //Return List of StdErr
    uint32_t&      RetCode)    //Return Exit Code
{
    int                  Success;
    SECURITY_ATTRIBUTES  security_attributes;
    HANDLE               stdout_rd = INVALID_HANDLE_VALUE;
    HANDLE               stdout_wr = INVALID_HANDLE_VALUE;
    HANDLE               stderr_rd = INVALID_HANDLE_VALUE;
    HANDLE               stderr_wr = INVALID_HANDLE_VALUE;
    PROCESS_INFORMATION  process_info;
    STARTUPINFO          startup_info;
    thread               stdout_thread;
    thread               stderr_thread;

    security_attributes.nLength              = sizeof(SECURITY_ATTRIBUTES);
    security_attributes.bInheritHandle       = TRUE;
    security_attributes.lpSecurityDescriptor = nullptr;

    if (!CreatePipe(&stdout_rd, &stdout_wr, &security_attributes, 0) ||
            !SetHandleInformation(stdout_rd, HANDLE_FLAG_INHERIT, 0)) {
        return -1;
    }

    if (!CreatePipe(&stderr_rd, &stderr_wr, &security_attributes, 0) ||
            !SetHandleInformation(stderr_rd, HANDLE_FLAG_INHERIT, 0)) {
        if (stdout_rd != INVALID_HANDLE_VALUE) CloseHandle(stdout_rd);
        if (stdout_wr != INVALID_HANDLE_VALUE) CloseHandle(stdout_wr);
        return -2;
    }

    ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION));
    ZeroMemory(&startup_info, sizeof(STARTUPINFO));

    startup_info.cb         = sizeof(STARTUPINFO);
    startup_info.hStdInput  = 0;
    startup_info.hStdOutput = stdout_wr;
    startup_info.hStdError  = stderr_wr;

    if(stdout_rd || stderr_rd)
        startup_info.dwFlags |= STARTF_USESTDHANDLES;

    // Make a copy because CreateProcess needs to modify string buffer
    char      CmdLineStr[MAX_PATH];
    strncpy(CmdLineStr, CmdLine.c_str(), MAX_PATH);
    CmdLineStr[MAX_PATH-1] = 0;

    Success = CreateProcess(
        nullptr,
        CmdLineStr,
        nullptr,
        nullptr,
        TRUE,
        0,
        nullptr,
        CmdRunDir.c_str(),
        &startup_info,
        &process_info
    );
    CloseHandle(stdout_wr);
    CloseHandle(stderr_wr);

    if(!Success) {
        CloseHandle(process_info.hProcess);
        CloseHandle(process_info.hThread);
        CloseHandle(stdout_rd);
        CloseHandle(stderr_rd);
        return -4;
    }
    else {
        CloseHandle(process_info.hThread);
    }

    if(stdout_rd) {
        stdout_thread=thread([&]() {
            DWORD  n;
            const size_t bufsize = 1000;
            char         buffer [bufsize];
            for(;;) {
                n = 0;
                int Success = ReadFile(
                    stdout_rd,
                    buffer,
                    (DWORD)bufsize,
                    &n,
                    nullptr
                );
                printf("STDERR: Success:%d n:%d\n", Success, (int)n);
                if(!Success || n == 0)
                    break;
                string s(buffer, n);
                printf("STDOUT:(%s)\n", s.c_str());
                ListStdOut += s;
            }
            printf("STDOUT:BREAK!\n");
        });
    }

    if(stderr_rd) {
        stderr_thread=thread([&]() {
            DWORD        n;
            const size_t bufsize = 1000;
            char         buffer [bufsize];
            for(;;) {
                n = 0;
                int Success = ReadFile(
                    stderr_rd,
                    buffer,
                    (DWORD)bufsize,
                    &n,
                    nullptr
                );
                printf("STDERR: Success:%d n:%d\n", Success, (int)n);
                if(!Success || n == 0)
                    break;
                string s(buffer, n);
                printf("STDERR:(%s)\n", s.c_str());
                ListStdErr += s;
            }
            printf("STDERR:BREAK!\n");
        });
    }

    WaitForSingleObject(process_info.hProcess,    INFINITE);
    if(!GetExitCodeProcess(process_info.hProcess, (DWORD*) &RetCode))
        RetCode = -1;

    CloseHandle(process_info.hProcess);

    if(stdout_thread.joinable())
        stdout_thread.join();

    if(stderr_thread.joinable())
        stderr_thread.join();

    CloseHandle(stdout_rd);
    CloseHandle(stderr_rd);

    return 0;
}

int main()
{
    int            rc;
    uint32_t       RetCode;
    string         ListStdOut;
    string         ListStdErr;

    cout << "STARTING.\n";

    rc = SystemCapture(
        "C:\\Windows\\System32\\ipconfig.exe",    //Command Line
        ".",                                     //CmdRunDir
        ListStdOut,                              //Return List of StdOut
        ListStdErr,                              //Return List of StdErr
        RetCode                                  //Return Exit Code
    );
    if (rc < 0) {
        cout << "ERROR: SystemCapture\n";
    }

    cout << "STDOUT:\n";
    cout << ListStdOut;

    cout << "STDERR:\n";
    cout << ListStdErr;

    cout << "Finished.\n";

    cout << "Press Enter to Continue";
    cin.ignore();

    return 0;
}
Community
  • 1
  • 1
Bimo
  • 5,987
  • 2
  • 39
  • 61
  • 6
    Thank you! This is the best popen implementation for Windows on the Internet! And by passing the CREATE_NO_WINDOW flag one can finally get rid of the annoying cmd prompts that show up. – LachoTomov Jun 19 '18 at 18:05
  • 1
    Where do you pass the `CREATE_NO_WINDOW` thingy? – Refael Sheinker Feb 14 '19 at 19:35
  • 3
    @Bill Moore, if you notice, there is a bug in your answer. `ListStdErr` is never used. – Refael Sheinker Feb 14 '19 at 19:49
  • 1
    @RefaelSheinker: I think you can replace the 0 in createProcess by `CREATE_NO_WINDOW`. https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags – Eric Duminil Nov 07 '21 at 12:47
  • 1
    @RefaelSheinker: Indeed. I think the second `ListStdOut += s;` should be replaced with `ListStdErr += s;`, if you want to have two distinct strings. I'd like to have them merged anyway, so I'll simply remove ListStdErr. Finally, List is kind of a weird name for a string. – Eric Duminil Nov 07 '21 at 12:57
12

The following might be a portable solution. It follows standards.

#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib>
#include <sstream>

std::string ssystem (const char *command) {
    char tmpname [L_tmpnam];
    std::tmpnam ( tmpname );
    std::string scommand = command;
    std::string cmd = scommand + " >> " + tmpname;
    std::system(cmd.c_str());
    std::ifstream file(tmpname, std::ios::in | std::ios::binary );
    std::string result;
    if (file) {
        while (!file.eof()) result.push_back(file.get())
            ;
        file.close();
    }
    remove(tmpname);
    return result;
}

// For Cygwin

int main(int argc, char *argv[])
{
    std::string bash = "FILETWO=/cygdrive/c/*\nfor f in $FILETWO\ndo\necho \"$f\"\ndone ";
    std::string in;
    std::string s = ssystem(bash.c_str());
    std::istringstream iss(s);
    std::string line;
    while (std::getline(iss, line))
    {
        std::cout << "LINE-> " + line + "  length: " << line.length() << std::endl;
    }
    std::cin >> in;
    return 0;
}
hanshenrik
  • 19,904
  • 4
  • 43
  • 89
11

Take note that you can get output by redirecting output to the file and then reading it

It was shown in documentation of std::system

You can receive exit code by calling WEXITSTATUS macro.

    int status = std::system("ls -l >test.txt"); // execute the UNIX command "ls -l >test.txt"
    std::cout << std::ifstream("test.txt").rdbuf();
    std::cout << "Exit code: " << WEXITSTATUS(status) << std::endl;
Pikacz
  • 431
  • 1
  • 5
  • 10
5

Assuming POSIX, simple code to capture 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, 0);
    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;
}

Code contributions are welcome for more functionality:

https://github.com/ericcurtin/execxx

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ericcurtin
  • 1,499
  • 17
  • 20
5

You can get the output after running a script using a pipe. We use pipes when we want the output of the child process.

int my_func() {
    char ch;
    FILE *fpipe;
    FILE *copy_fp;
    FILE *tmp;
    char *command = (char *)"/usr/bin/my_script my_arg";
    copy_fp = fopen("/tmp/output_file_path", "w");
    fpipe = (FILE *)popen(command, "r");
    if (fpipe) {
        while ((ch = fgetc(fpipe)) != EOF) {
            fputc(ch, copy_fp);
        }
    }
    else {
        if (copy_fp) {
            fprintf(copy_fp, "Sorry there was an error opening the file");
        }
    }
    pclose(fpipe);
    fclose(copy_fp);
    return 0;
}

So here is the script, which you want to run. Put it in a command variable with the arguments your script takes (nothing if no arguments). And the file where you want to capture the output of the script, put it in copy_fp.

So the popen runs your script and puts the output in fpipe and then you can just copy everything from that to your output file.

In this way you can capture the outputs of child processes.

And another process is you can directly put the > operator in the command only. So if we will put everything in a file while we run the command, you won't have to copy anything.

In that case, there isn't any need to use pipes. You can use just system, and it will run the command and put the output in that file.

int my_func(){
    char *command = (char *)"/usr/bin/my_script my_arg > /tmp/my_putput_file";
    system(command);
    printf("everything saved in my_output_file");
    return 0;
}

You can read YoLinux Tutorial: Fork, Exec and Process control for more information.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Muskan Agarwal
  • 378
  • 5
  • 19
5

Command class uses system("cmd > stdout 2> stderr") to provide user with stdout and stderr, in addition to the exit code.

Test run:

./a.out 'ls .'
exit code: 0
stdout: HelloWorld
HelloWorld.c
HelloWorld.cpp
HelloWorld.dSYM
a.out
gcc_container.bash
linuxsys
macsys
test.sh

stderr: 

#include <iostream>
#include <fstream>
#include <sstream>
#include <unistd.h>
using namespace std;

class Command {
    public:
        Command() {
            exit_code_ = -1;
        }

        int GetExitCode() { return exit_code_;}

        string GetStdOutStr() {return stdout_str_;}

        string GetStdErrStr() {return stderr_str_;}

        int Run(const char* cmd) {
            return Run(string(cmd));
        }

        /**
         * @brief run a given command
         * 
         * @param cmd: command string
         * @return int: the exit code of running the command
         */
        int Run(string cmd) {

            // create temp files
            char tmp_dir[] = "/tmp/stdir.XXXXXX";
            mkdtemp(tmp_dir);
            string stdout_file = string(tmp_dir) + "/stdout";
            string stderr_file = string(tmp_dir) + "/stderr";

            // execute the command "cmd > stdout_file 2> stderr_file"
            string cli = cmd + " > " + stdout_file + " 2> " + stderr_file;
            exit_code_ = system(cli.c_str());
            exit_code_ = WEXITSTATUS(exit_code_);
            stdout_str_ = File2Str(stdout_file);
            stderr_str_ = File2Str(stderr_file);

            // rid of the temp files
            remove(stdout_file.c_str());
            remove(stderr_file.c_str());
            remove(tmp_dir);

            return exit_code_;
        }

    private:
        int exit_code_;
        string stderr_str_;
        string stdout_str_;

        /**
         * @brief read a file
         * 
         * @param file_name: file path 
         * @return string the contents of the file.
         */
        string File2Str(string file_name) {
            ifstream file;
            stringstream str_stream;

            file.open(file_name);
            if (file.is_open()) {
                str_stream << file.rdbuf();
                file.close();
            }
            return str_stream.str();
        }
};

int main(int argc, const char* argv[]) {
    Command command;

    command.Run(argv[1]);
    cout << "exit code: " << command.GetExitCode() << endl;
    cout << "stdout: " << command.GetStdOutStr() << endl;
    cout << "stderr: " << command.GetStdErrStr() << endl;
    return  command.GetExitCode();
}

Dahai Li
  • 91
  • 1
  • 3
4

C++ stream implemention of waqas's answer:

#include <istream>
#include <streambuf>
#include <cstdio>
#include <cstring>
#include <memory>
#include <stdexcept>
#include <string>

class execbuf : public std::streambuf {
    protected:
        std::string output;
        int_type underflow(int_type character) {
            if (gptr() < egptr()) return traits_type::to_int_type(*gptr());
            return traits_type::eof();
        }
    public:
        execbuf(const char* command) {
            std::array<char, 128> buffer;
            std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(command, "r"), pclose);
            if (!pipe) {
                throw std::runtime_error("popen() failed!");
            }
            while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
                this->output += buffer.data();
            }
            setg((char*)this->output.data(), (char*)this->output.data(), (char*)(this->output.data() + this->output.size()));
        }
};

class exec : public std::istream {
    protected:
        execbuf buffer;
    public:
        exec(char* command) : std::istream(nullptr), buffer(command, fd) {
            this->rdbuf(&buffer);
        }
};

This code catches all output through stdout . If you want to catch only stderr then pass your command like this:

sh -c '<your-command>' 2>&1 > /dev/null

If you want to catch both stdout and stderr then the command should be like this:

sh -c '<your-command>' 2>&1
Akib Azmain Turja
  • 1,142
  • 7
  • 27