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

13

598

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.

Gifted answered 26/1, 2009 at 5:11 Comment(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 explainsHolleyholli
@Holleyholli you can create a link to #52165223Stalnaker
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, 2) C: Run a System Command and Get Output?, 3) How can I run an external program from C and parse its output?, 4) Capturing stdout from a system() command optimally, 5) (this question).Wyler
D
789
#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(), static_cast<int>(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.

Doig answered 26/1, 2009 at 6:12 Comment(39)
Be aware that this will only grab stdout and not stderr.Brenner
Also be aware that an exception can occur in result += buffer, so the pipe might not be properly closed.Perusse
@larsmans But it is because of a code error out of this function, right, not something that may be expected?Carcanet
when is this ever going to give "ERROR" as a return value from the function. Under what circumstances?Uitlander
@Yasky: When the program being executed is int main(){ puts("ERROR"); }.Patricia
If this is C++, you should throw an exception, not return "ERROR".Rationalize
The answer is good but it would be better if you replace 'char* cmd' with 'const char* cmd'Hackathorn
@Hackathorn I think it showed me some error and I actually did that. You might want to edit the answer to include your suggestionCinematograph
Why use while (!feof(...)) and an if statement in the loop, when all you have to do is while (fgets(...) != NULL)?Vasily
@JoachimPileborg Its actually safer too faq.cprogramming.com/cgi-bin/…Consult
unique_ptr is a better fit here, where the actual reference count is never used.Atonal
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 works.Stirpiculture
You could use constexpr on the buffer size and avoid specifying a literal twice.Mcdermott
Bless your soul for the pre-C++11 version.Parade
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!Vishnu
Is there a danger of the shell'd command never responding/exiting?Hevesy
while (!feof()) is always wrong, and fgets is equally wrong when you're not really reading lines. Use fread and check the return value.Lenis
@Doig is possible to return only instanceType from result. I'm new to this. Please help. Thanks in advanceButtery
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.Dabster
Is this still the best practice with C++17?Urge
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 insteadEnteritis
It is important to understand that this launches a shell which performs the command argument tokenization.Unlearn
How can I pass .bat file as argument instead of cmd?Convolve
Why buffer size exactly 128 ?Picky
But what if you need the return value from pclose() ? :)Fighterbomber
@Fighterbomber use pre-C++11 version or write a special deleter: auto pipe_deleter = [&exit_status](FILE* stream) { exit_status = pclose(stream); };Higgins
This breaks for me for the output of fping. Apparently this is related to what @Enteritis 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?Trucker
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 infoEnteritis
Beware of naming confusion with the POSIX standard family of 'exec' functionsFarrica
@Stirpiculture Could pclose works well when the popen returns NULL?Keyes
@FredFoo It's seems there is no such problem(i.e. the pipe might not be properly closed)now. Am I right?Keyes
@Keyes That's long ago, I'm not sure what my comment was about (maybe something like this https://mcmap.net/q/65642/-issue-when-using-_popen-windows/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”.Stirpiculture
@Stirpiculture If I understand you correctly, the code snippet in this answer needs to be improved. You see, std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose) would call pclose even if popen fails. How do you think about it?Keyes
@Keyes 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.)Stirpiculture
@Stirpiculture I see. Here is the new question.https://mcmap.net/q/65643/-pclose-with-null-parameterKeyes
@Keyes I don't see why pclose would be called if popen fails. The std::unique_ptr destructor (and also the 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())."Ellison
If I use popen like this (unique_ptr version) from multiple threads on the same binary I get a segmentation faultGlioma
The std::array appears to be completely pointless - how is it better than the char version?Sublett
If someone else wants to use this solution and still catch stderr, here's a workaround.Outstretched
A
103

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';
} 
Apothecium answered 22/5, 2012 at 12:55 Comment(18)
popen is the correct solution to use when either input or output communication is desired from the child process, but not both.Freemanfreemartin
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.Apothecium
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).Parmenides
@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 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.Apothecium
@JonathanWakely - good to know that pstreams has the necessary support. BTW, FIONREAD is also available in Windows, for those working over there.Parmenides
@chiliNUT the new 1.0.1 release uses the Boost licence.Apothecium
@JonathanWakely how can i kill the ipstream after say a 5 second timeout?Lumbering
Also string_view isn't supported for basic_ipstream.Lumbering
@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.Apothecium
@JonathanWakely Is there a way to get the return value from the process?Caber
@Caber read the FAQ: pstreams.sourceforge.net/faq.html#faq_exit_statusApothecium
@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.Apothecium
@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 :DDisaccord
@Disaccord you are mistaken.Apothecium
Good solution, but requires custom code, hence my downvote.Anglocatholic
Thanks. How can you kill a started redi::ipstream? I tried proc.rdbuf()->kill(15) without success.Nickell
@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.Apothecium
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!Nickell
G
47

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
Gaucho answered 26/2, 2016 at 18:17 Comment(5)
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.Stirpiculture
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.Gaucho
After reading CreateProcess function (Windows), 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.Stirpiculture
This answer does not handle stderr properly.Hedrick
Does this also work for Unix systems? Or would I have to use something else for a Unix device?Erlking
D
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);
}
Detain answered 26/1, 2009 at 8:21 Comment(4)
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 :)Gifted
...or you could use the existing posix_spawnp functionSledgehammer
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).Contessacontest
Will this work on unix, linux and windows ? Can you please header files as well?Convolve
F
20

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.

Faitour answered 26/1, 2009 at 6:6 Comment(0)
H
12

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;
}
Hypothesize answered 21/9, 2017 at 15:43 Comment(5)
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.Addington
Where do you pass the CREATE_NO_WINDOW thingy?Hedrick
@Bill Moore, if you notice, there is a bug in your answer. ListStdErr is never used.Hedrick
@RefaelSheinker: I think you can replace the 0 in createProcess by CREATE_NO_WINDOW. learn.microsoft.com/en-us/windows/win32/procthread/…Kielce
@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.Kielce
S
11

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;
}
Stratocumulus answered 16/12, 2013 at 18:12 Comment(1)
I get this warning with gcc: "warning: the use of tmpnam is dangerous, better use mkstemp"Rationalize
T
10

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;
Thermidor answered 22/12, 2019 at 19:22 Comment(1)
This only works partially. If you want to retrieve the ID of the started process, it won't work.Tachymetry
O
4

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

Ordinand answered 15/11, 2018 at 0:45 Comment(0)
S
4

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.

Shonda answered 5/7, 2019 at 12:38 Comment(0)
A
4

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();
}

Aldas answered 24/2, 2022 at 0:37 Comment(0)
S
3

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
Slivovitz answered 20/8, 2020 at 17:45 Comment(1)
One need to add #include <array> and remove , fd to make it compileCaprifig
I
0

I was surprised that there isn't a lightweight library that does that cross-platform. So I decided to create a library since I need the functionality anyway.

Here's the library https://github.com/Neko-Box-Coder/System2

Here's a short snippet of it (without checks)

int main(int argc, char** argv)
{
    System2CommandInfo commandInfo;

    System2Run("./some_command", &commandInfo);

    char outputBuffer[1024];
    uint32_t bytesRead = 0;
    System2ReadFromOutput(&commandInfo, outputBuffer, 1023, &bytesRead);
    outputBuffer[bytesRead] = 0;
    int returnCode;
    System2GetCommandReturnValueSync(&commandInfo, &returnCode);
    printf("%s\n", outputBuffer);
    return 0;
}

It works for posix and windows systems. (It doesn't use popen for windows so it should also work in GUI applications)

It can send input and receive output from command.

And have blocking and non-blocking version.

And it has both header only and source version as well

Icebreaker answered 23/3, 2024 at 16:40 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.