如何使用POSIX执行命令并在C++中获取命令输出?

Mis*_*a M 427 c++ posix system process return-value

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

std::string result = system("./some_command");
Run Code Online (Sandbox Code Playgroud)

我需要运行一个任意命令并获取其输出.我看过Boost.org,但我找不到任何可以满足我需要的东西.

waq*_*qas 561

#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;
}
Run Code Online (Sandbox Code Playgroud)

Pre-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;
}
Run Code Online (Sandbox Code Playgroud)

替换popenpclose用于_popen_pcloseWindows.

  • 请注意,这只会抓取_stdout_而不是_stderr_. (62认同)
  • unique_ptr更适合这里,从不使用实际引用计数. (26认同)
  • 另请注意,`result + = buffer`中可能会发生异常,因此管道可能无法正常关闭. (13认同)
  • @Yasky:正在执行的程序是`int main(){puts("ERROR"); }`. (6认同)
  • The answer is good but it would be better if you replace 'char*cmd' with 'const char*cmd' (6认同)
  • 为 C++11 之前的版本祝福你的灵魂。 (6认同)
  • 这仍然是C ++ 17的最佳实践吗? (5认同)
  • 为什么在循环中使用`while(!feof(...))`和`if`语句,当你所要做的只是`while(fgets(...)!= NULL)`? (4认同)
  • `while(!feof())`总是错的,当你没有真正读行时,`fgets`同样错了.使用`fread`并检查返回值. (4认同)
  • 为什么缓冲区大小正好是 128 ? (4认同)
  • @Janis:`bad_alloc`. (3认同)
  • 即使在 c++11 中,这也不是最佳实践,因为 fgets 实际上并不返回读取的字节数,因此读取 `'\0'` 会破坏代码 @AaronFranke ,因为 @AnttiHaapala 建议应该使用 fread (2认同)

Jon*_*ely 74

同时获取stdout和stderr(以及写入stdin,这里没有显示)很容易使用我的pstreams标头,它定义了iostream类,其工作方式如下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';
  // read child's stderr
  while (std::getline(proc.err(), line))
    std::cout << "stderr: " << line << '\n';
} 
Run Code Online (Sandbox Code Playgroud)

  • 然而,好的图书馆,LGPL杀了它.没有人需要一些简单的小功能,如果它将在商业项目中,那么它将把这样的功能作为动态库链接起来. (24认同)
  • 我不同意.`popen`要求你使用C stdio API,我更喜欢iostreams API.`popen`要求你手动清理`FILE`句柄,pstreams自动完成.`popen`只接受一个`const char*`作为参数,需要注意避免shell注入攻击,pstreams允许你传递类似于`execv`的字符串向量,这更安全.`popen`只给你一个管道,pstreams告诉你孩子的PID允许你发送信号,例如,如果它被阻止或没有退出就杀死它.即使您只需要单向IO,所有这些都是有优势的. (17认同)
  • @chiliNUT新的1.0.1版本使用Boost许可证. (12认同)
  • 但是pstreams是在GNU LGPL许可下发布的,这对商业用途有点限制.http://stackoverflow.com/questions/2145216/gnu-lesser-gpl-application-sell (10认同)
  • @tach:您不需要将其链接为动态库.除了头文件之外什么都没有.要确认LGPL可以与仅标题库一起使用,请查看[特征库常见问题解答](http://eigen.tuxfamily.org/index.php?title=Licensing_FAQ&oldid=1117#So_what_does_the_LGPL_require_me_to_do.3F).请注意,这适用于LGPL2,因此LGPL3可能会有其他并发症. (5认同)
  • 是的我打算更改v1.0版本的许可证(可能是Boost许可证) (2认同)

Mr.*_*Ree 32

我会用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())*/
}
Run Code Online (Sandbox Code Playgroud)

您也可能想要使用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);
}
Run Code Online (Sandbox Code Playgroud)

  • ...或者您可以使用现有的posix_spawnp函数 (4认同)
  • 你的`execlp`调用有一个错误:传递的最后一个`arg`指针必须是`(char*)NULL`才能正确终止可变参数列表(参见`execlp(3)`以供参考). (4认同)
  • 困难的方法是正确的:) 我喜欢 select() 调用的想法,但在这种情况下,我实际上需要等到任务完成。我将保留此代码用于我拥有的另一个项目:) (2认同)

Tar*_*aro 30

对于Windows,popen也可以工作,但它会打开一个控制台窗口 - 它会快速闪烁在UI应用程序上.如果你想成为一名专业人士,最好禁用这种"闪烁"(特别是如果最终用户可以取消它).

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

(此代码部分重新组合在代码项目和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
Run Code Online (Sandbox Code Playgroud)

  • 阅读[CreateProcess function(Windows)](https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425(v = vs.85).aspx)后,我发现这样做真的很危险:*`此函数的Unicode版本CreateProcessW可以修改此字符串的内容.因此,此参数不能是只读内存的指针(例如const变量或文字字符串).如果此参数是常量字符串,则该函数可能会导致访问冲突.*因此,最好先将命令行复制到单独的缓冲区中,以防止调用者更改其原始输入. (4认同)

pax*_*blo 16

两种可能的方法:

  1. 我不认为它popen()是C++标准的一部分(它是来自内存的POSIX的一部分),但它可以在我使用的每个UNIX上使用(并且你似乎是因为你的命令而瞄准UNIX ./some_command).

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


Bil*_*ore 9

我不知道为什么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;
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢!这是 Internet 上 Windows 的最佳 popen 实现!通过传递 CREATE_NO_WINDOW 标志,您终于可以摆脱出现的烦人的 cmd 提示。 (6认同)
  • @Bill Moore,如果您注意到,您的答案中存在错误。`ListStdErr` 从未被使用过。 (2认同)

小智 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;
}
Run Code Online (Sandbox Code Playgroud)

  • 我在gcc上收到此警告:“警告:使用`tmpnam`是危险的,最好使用`mkstemp`” (4认同)

Pik*_*acz 6

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;
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

417446 次

最近记录:

6 年,1 月 前