【问题标题】:How to execute a command and get return code stdout and stderr of command in C++如何在 C++ 中执行命令并获取命令的返回码 stdout 和 stderr
【发布时间】:2021-05-29 09:56:37
【问题描述】:

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

How do I execute a command and get the output of the 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");)并将标准输出转换为字符串。

但它不做的是获取命令返回码(通过/失败整数)或标准错误。理想情况下,我想要一种方法来获取所有三个(返回代码、标准输出、标准错误)。

我会满足于标准输出和标准错误。我在想我需要添加另一个管道,但我真的看不到第一个管道是如何设置来获取标准输出的,所以我想不出如何改变它来获取两者。

任何人有任何想法如何做到这一点,或可能有效的替代方法?

更新

查看我的完整示例here 的输出:

Start
1 res: /home

2 res: stdout

stderr
3 res: 
End

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

外部库

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

使用评论/答案的完整解决方案

感谢大家的回答/cmets,这是修改后的解决方案(并且可以运行):

working-solution

【问题讨论】:

  • boost 是一个选项吗?如果是这样,您可能想看看boost::process。
  • @user1810087 嗯...不,我宁愿保持纯 c++/c++11。在我的特定项目中,我们选择不使用 boost 库。
  • 有一种可能的解决方法。您可以通过将“2>&1”附加到您的 cmd 来将标准错误重定向到标准输出。这会满足您的需求吗?
  • @Alex 该死的......这很聪明,我一直在 bash 脚本中使用它,但没想到在这里使用它!......如果它有效,那么是的:)..我仍然会建议做一个 c++ 版本,但这是一个很好的解决方法,先生......现在测试......
  • @Alex 效果很好 - 请添加您的评论作为答案,我会投票赞成

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


【解决方案1】:

下面是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&gt;&amp;1",正如同一线程中Alex 所建议的那样。

main()函数如下,展示如何使用:

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

列出当前目录并将其打印到标准输出:

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

【讨论】:

  • 感谢此次更新。我只是在徘徊(这是真正的好奇心而不是批评)这种方法有什么优势?对我来说 std::string 输出很好,向量给我们带来什么好处? - 我认为字符串会更容易执行字符串操作(如查找等)。除非这是用于处理具有二进制输出的命令? - 谢谢! (+1 顺便说一句)
  • @code_fodder 欢迎您。好吧,是的,使用向量存储原始数据的想法是。如果需要将其转换为字符串,可以只使用std::string(aVector.data()),但我想你是对的。这里std::string 可能是预期的结果。我更改了代码以将标准输出存储在std::string 中。还有一个错误,允许将垃圾存储在向量(或字符串)的末尾,已更正。
  • 正如您所说,字符串可能是输出的最常见要求,但我认为如果您要获得某种原始/二进制输出,这是一个不错的选择 - 我可以想到一些可能有用的地方,谢谢:)
  • 我编辑了这个答案,但显然它没有通过(StackOverflow 变得非常不受欢迎)。 “std::next(std::begin(buffer), count-1)”中有一个错误。它应该是“count”而不是“count-1”。迭代器必须是最后一个。当前答案将为您提供缺少字符的输出。
  • @4nt,是的,它可以防止从管道接收并存储在 buffer.data() 中的最后一个字符被添加到字符串输出中。这个想法是防止将 linux 命令输出中的尾随 '\n' 字符添加到字符串中。这样,用户可以根据用例选择是否添加“\n”。但你是对的。从管道读取的最后一个字符不存储在输出字符串中,无论是 '\n' 还是其他。代码已根据您的建议进行了编辑。谢谢。我还更正了 #includes 中的一些错误以及 main() 中对 execute() 的调用。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    有一种可能的解决方法。您可以通过将“2>&1”附加到您的 cmd 来将标准错误重定向到标准输出。这会满足您的需求吗?

    【讨论】:

      【解决方案4】:

      来自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&lt;&gt; 的析构函数)将为您提供进程的返回码(如果进程尚未终止,则阻塞)。

      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&gt;&amp;1 将stderr 的输出从传递给popen() 的命令行重定向到stdout。这样做的不便之处在于,两种流都无法预料地混合在一起。

      如果你真的想为 stderr 和 stdout 提供两个不同的文件描述符,一种方法是自己进行分叉并将新进程 stdout/stderr 复制到两个可从父进程访问的管道。 (见dup2() 和pipe())。我可以在这里更详细地介绍,但这是一种相当乏味的做事方式,必须非常小心。互联网上到处都是例子。

      【讨论】:

      • Patrick - 你能发布一个示例代码的 sn-p 我将如何改变它吗?我想我只需要在最后添加一个int res = pclose()?
      • 我拿走了你的代码并删除了std::shared_ptr的使用。
      • 谢谢 - 一切正常!,在我的帖子末尾发布了一个完整的解决方案:)
      • 如何传递 .bat 文件而不是 cmd 参数?
      猜你喜欢
      • 1970-01-01
      • 2023-03-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-11
      • 2012-02-02
      • 1970-01-01
      相关资源
      最近更新 更多