【问题标题】:How do I execute a command and get the output of the command within C++ using POSIX?如何使用 POSIX 在 C++ 中执行命令并获取命令的输出?
【发布时间】:2010-10-03 11:53:08
【问题描述】:

我正在寻找一种在 C++ 程序中运行命令时获取其输出的方法。我看过使用system() 函数,但这只会执行一个命令。这是我正在寻找的示例:

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

我需要运行任意命令并获取其输出。我查看了boost.org,但没有找到任何可以满足我需要的东西。

【问题讨论】:

标签: c++ process posix system return-value


【解决方案1】:
#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;
}

C++11 之前的版本:

#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;
}

在 Windows 上将 popenpclose 替换为 _popen_pclose

【讨论】:

  • 请注意,这只会抓取 stdout 而不会抓取 stderr
  • 还要注意result += buffer中可能会发生异常,所以管道可能没有正确关闭。
  • 答案很好,但如果将 'char* cmd' 替换为 'const char* cmd' 会更好
  • unique_ptr 更适合这里,这里从不使用实际的引用计数。
  • 这仍然是 C++17 的最佳实践吗?
【解决方案2】:

使用我的pstreams 标头可以轻松获取stdout 和stderr(以及写入stdin,此处未显示),它定义了像popen 一样工作的iostream 类:

#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';
} 

【讨论】:

  • 我不同意。 popen 要求你使用 C stdio API,我更喜欢 iostreams API。 popen 要求您手动清理 FILE 句柄,pstreams 会自动执行此操作。 popen 只接受const char* 作为参数,这需要小心避免shell 注入攻击,pstreams 允许您传递类似于execv 的字符串向量,这样更安全。 popen 只给你一个管道,pstreams 告诉你孩子的 PID 允许你发送信号,例如如果它被阻止或不退出,则杀死它。即使您只想要单向 IO,所有这些都是优势。
  • 此解决方案的另一个问题是,如果子级写入标准错误足以填充缓冲区并在开始写入标准输出之前阻塞。父级将阻止读取 stdout,而子级将被阻止等待读取 stderr。资源僵局!至少其中一个循环作为异步(即线程)会更好。
  • @JesseChisholm,是的,这可能是个问题。但是您不需要使用线程,因为 pstreams 允许使用 iostream 接口近似非阻塞 I/O,特别是使用 readsome 函数,该函数使用 pstreambuf::in_avail() 检查准备情况,因此不会阻塞。这允许对进程的 stdout 和 stderr 进行多路分解,因为每个都有可用的数据。 pstreambuf::in_avail() 只有在操作系统支持非标准 FIONREAD ioctl 时才能 100% 可靠工作,但(至少)GNU/Linux 和 Solaris 支持。
  • @chiliNUT 新的 1.0.1 版本使用 Boost 许可证。
  • @JonathanWakely 如何在 5 秒超时后终止 ipstream?
【解决方案3】:

对于 Windows,popen 也可以使用,但它会打开一个控制台窗口 - 该窗口会快速闪过您的 UI 应用程序。如果您想成为专业人士,最好禁用此“闪烁”(特别是如果最终用户可以取消它)。

所以这是我自己的 Windows 版本:

(此代码部分重组自 The Code Project 和 MSDN 示例中编写的想法。)

#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

【讨论】:

  • 这是我最喜欢的 Windows 解决方案,希望您原谅我的更改。我建议使 const-cast 更加明确,而我认为明确使用 wchar_tCreateProcessW 是不必要的限制。
  • 你觉得这个演员有什么问题或潜在的问题吗?我更喜欢将代码保持在最低限度,不要在没有需要的情况下编写它。
  • 在阅读CreateProcess function (Windows)之后,我看到了这样做的真正危险: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. 所以最好先将命令行复制到单独的缓冲区中,以防止调用者更改其原始输入。
  • 这个答案不能正确处理标准错误。
  • 这也适用于 Unix 系统吗?还是我必须为 Unix 设备使用其他东西?
【解决方案4】:

我会使用 popen() (++waqas)

但有时你需要阅读和写作......

似乎没有人再用艰难的方式做事了。

(假设是 Unix/Linux/Mac 环境,或者可能是具有 POSIX 兼容层的 Windows...)

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())*/
}

您可能还想尝试使用 select() 和非阻塞读取。

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

【讨论】:

  • 困难的方法是正确的 :) 我喜欢 select() 调用的想法,但在这种情况下,我实际上需要等到任务完成。我会为我的另一个项目保留这段代码:)
  • ...或者您可以使用现有的 posix_spawnp 函数
  • 您的execlp 调用有一个错误:传递的最后一个arg 指针必须是(char *) NULL 才能正确终止可变参数列表(请参阅execlp(3) 以供参考)。
  • 这可以在 unix、linux 和 windows 上运行吗?请问头文件也可以吗?
【解决方案5】:

两种可能的方法:

  1. 我不认为popen() 是 C++ 标准的一部分(它是内存中 POSIX 的一部分),但它在我使用过的每个 UNIX 上都可用(而且你似乎从你的命令是./some_command)。

  2. 如果没有popen(),你可以使用system("./some_command &gt;/tmp/some_command.out");,然后使用普通的I/O函数来处理输出文件。

【讨论】:

    【解决方案6】:

    以下可能是一个可移植的解决方案。它遵循标准。

    #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;
    }
    

    【讨论】:

    • 我收到 gcc 的警告:“警告:使用 tmpnam 很危险,最好使用 mkstemp
    【解决方案7】:

    我无法弄清楚为什么Code::Blocks/MinGW 中缺少 popen/pclose。所以我改用 CreateProcess() 和 CreatePipe() 解决了这个问题。

    这是对我有用的解决方案:

    //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());
                    ListStdOut += 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;
    }
    

    【讨论】:

    • 谢谢!这是 Internet 上 Windows 最好的 popen 实现!通过传递 CREATE_NO_WINDOW 标志,最终可以摆脱出现的烦人的 cmd 提示。
    • 你在哪里通过CREATE_NO_WINDOW的东西?
    • @Bill Moore,如果您注意到,您的答案中有一个错误。 ListStdErr 从未使用过。
    • @RefaelSheinker:我认为您可以将 createProcess 中的 0 替换为 CREATE_NO_WINDOWdocs.microsoft.com/en-us/windows/win32/procthread/…
    • @RefaelSheinker:确实如此。我认为第二个ListStdOut += s; 应该替换为ListStdErr += s;,如果你想有两个不同的字符串。无论如何我都想让它们合并,所以我将简单地删除 ListStdErr。最后,List 是一个奇怪的字符串名称。
    【解决方案8】:

    注意,你可以通过将输出重定向到文件然后读取它来获得输出

    它显示在std::system的文档中

    您可以通过调用WEXITSTATUS 宏来接收退出代码。

        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;
    

    【讨论】:

      【解决方案9】:

      假设 POSIX,捕获标准输出的简单代码:

      #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;
      }
      

      欢迎贡献代码以获得更多功能:

      https://github.com/ericcurtin/execxx

      【讨论】:

        【解决方案10】:

        您可以在使用管道运行脚本后获得输出。当我们想要子进程的输出时,我们使用管道。

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

        这是您要运行的脚本。将它与脚本采用的参数一起放入命令变量中(如果没有参数,则什么都没有)。以及要捕获脚本输出的文件,放在copy_fp中。

        因此 popen 运行您的脚本并将输出放入 fpipe,然后您可以将其中的所有内容复制到您的输出文件中。

        通过这种方式,您可以捕获子进程的输出。

        另一个过程是您可以直接将&gt; 运算符放在命令中。因此,如果我们在运行命令时将所有内容都放在一个文件中,您就不必复制任何内容。

        在这种情况下,不需要使用管道。您可以只使用system,它会运行命令并将输出放入该文件中。

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

        您可以阅读YoLinux Tutorial: Fork, Exec and Process control了解更多信息。

        【讨论】:

          【解决方案11】:

          waqas的C++流实现@的回答:

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

          此代码通过 stdout 捕获所有输出。如果您只想捕获 stderr,请像这样传递您的命令:

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

          如果你想同时捕获stdoutstderr,那么命令应该是这样的:

          sh -c '<your-command>' 2>&1
          

          【讨论】:

            【解决方案12】:

            Command 类使用 system("cmd > stdout 2> stderr") 为用户提供标准输出和标准错误,以及退出代码。

            试运行:

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

            【讨论】:

              猜你喜欢
              • 2013-01-01
              • 1970-01-01
              • 2016-03-16
              • 2021-03-27
              • 1970-01-01
              相关资源
              最近更新 更多