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

前端 未结 11 1128
我在风中等你
我在风中等你 2020-11-21 05:39

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 jus

相关标签:
11条回答
  • 2020-11-21 05:59

    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);
    }
    
    0 讨论(0)
  • 2020-11-21 06:01

    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
    
    0 讨论(0)
  • 2020-11-21 06:03

    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;
    }
    
    0 讨论(0)
  • 2020-11-21 06:13

    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;
    
    0 讨论(0)
  • 2020-11-21 06:16

    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.

    0 讨论(0)
  • 2020-11-21 06:16

    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
    
    0 讨论(0)
提交回复
热议问题