如何在C ++中执行命令并获取命令的返回码stdout和stderr

时间:2018-09-04 10:58:08

标签: c++ c++11 process stdout stderr

给出以下答案(第一个c ++ 11答案):

How to execute a command and get output of command within C++ using POSIX?

以下是为您提供方便的实现方式:

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

执行命令(例如std::string res = exec("ls");)并将stdout转换为字符串确实非常好。

但是它不执行的操作是获取命令返回码(通过/失败整数)或stderr。理想情况下,我想要一种获取所有三个(返回码,stdout,stderr)的方法。

我会选择stdout和stderr。我以为我需要添加另一个管道,但是我真的看不到如何设置第一个管道以获取stdout,因此我无法考虑如何对其进行更改以同时获得这两个管道。

任何人都知道如何做到这一点,或者可能有用的替代方法?

更新

请参阅我的完整示例here和输出:

Start
1 res: /home

2 res: stdout

stderr
3 res: 
End

您会看到3 res:不会像2 res: stdout那样打印stderr,但是stderr只是由进程(而不是我的程序)在单独的一行上转储到屏幕上。

外部库

我真的不想使用Qt和boost之类的外部库-主要是因为我希望它具有可移植性,而且我从事的许多项目都不使用boost。但是,我将标记包含这些选项的解决方案,因为它们对于其他用户有效:)

使用评论/答案完成解决方案

非常感谢您的回答/评论,这是经过修改的解决方案(可运行):

working-solution

4 个答案:

答案 0 :(得分:5)

摘自popen的手册页:

The pclose() function waits for the associated process to terminate  and returns the exit status of the command as returned by wait4(2).

因此,您自己调用pclose()(而不是使用std::shared_ptr<>的destructor-magic)将为您提供进程的返回码(或在进程尚未终止的情况下阻塞)。

std::string exec(const char* cmd) {
    std::array<char, 128> buffer;
    std::string result;

    auto pipe = popen(cmd, "r"); // get rid of shared_ptr

    if (!pipe) throw std::runtime_error("popen() failed!");

    while (!feof(pipe)) {
        if (fgets(buffer.data(), 128, pipe) != nullptr)
            result += buffer.data();
    }

    auto rc = pclose(pipe);

    if (rc == EXIT_SUCCESS) { // == 0

    } else if (rc == EXIT_FAILURE) {  // EXIT_FAILURE is not used by all programs, maybe needs some adaptation.

    }
    return result;
}

使用popen()获取stderr和stdout,恐怕您需要通过将2>&1添加到传递给popen()的命令行中,将stderr的输出重定向到stdout。 。令人不安的是,两种流的混合都是不可预测的。

如果您真的想为stderr和stdout提供两个出色的文件描述符,一种方法是自己进行分叉,并将新进程stdout / stderr复制到两个可从父进程访问的管道中。 (请参见dup2()pipe())。我可以在这里详细介绍,但这是一种很繁琐的处理方式,必须格外小心。互联网上到处都是例子。

答案 1 :(得分:3)

有一种解决方法。您可以通过在cmd后面附加“ 2>&1”将stderr重定向到stdout。这会满足您的需求吗?

答案 2 :(得分:3)

下面是Patrick. B答案的稍有不同的版本,它使用fread()而不是fgets()

Antti Haapala在此线程中建议使用fread()

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

此版本将标准输出读取到std::string,必须将其作为第二个参数传递。

该函数返回cmd返回的pclose()执行的退出代码。

要获得stderr,请将cmd添加到"2>&1"的末尾,如Alex所建议的那样。

main()下面的函数显示了如何使用:

int execute(std::string cmd, std::string& output)

列出当前目录并将其打印到stdout:

#include <iostream>
#include <array>
#include <unistd.h>
    
int execute(std::string cmd, std::string& output) {
    const int bufsize=128;
    std::array<char, bufsize> buffer;

    auto pipe = popen(cmd.c_str(), "r");
    if (!pipe) throw std::runtime_error("popen() failed!");

    size_t count;
    do {
        if ((count = fread(buffer.data(), 1, bufsize, pipe)) > 0) {
            output.insert(output.end(), std::begin(buffer), std::next(std::begin(buffer), count));
        }
    } while(count > 0);

    return pclose(pipe);
}    
    
int main(int argc, char** argv) {
std::string output;

    execute("ls", output);
    std::cout << output;
}

答案 3 :(得分:1)

您可以使用以下自定义删除器从管道中获取返回代码:

#include <cstdio>
#include <iostream>
#include <memory>
#include <string>
#include <array>
#include <utility>

using namespace std;
pair<string, int> exec(const char* cmd) {
    array<char, 128> buffer;
    string result;
    int return_code = -1;
    auto pclose_wrapper = [&return_code](FILE* cmd){ return_code = pclose(cmd); };
    { // scope is important, have to make sure the ptr goes out of scope first
    const unique_ptr<FILE, decltype(pclose_wrapper)> pipe(popen(cmd, "r"), pclose_wrapper);
    if (pipe) {
        while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
            result += buffer.data();
        }
    }
    }
    return make_pair(result, return_code);
}

int main(int argc, char* argv[]) {
    if (argc <= 1) return 0;
    cout << "calling with " << argv[1] << '\n';
    const auto process_ret = exec(argv[1]);
    cout << "captured stdout : " << '\n' << process_ret.first << endl;
    cout << "program exited with status code " << process_ret.second << endl;
    return 0;
}