Boost.Process v2:如何异步读取输出并检查终止情况

问题描述 投票:0回答:1

我正在生成一个进程并使用 Boost.Process v2 和 C++ 20 协程读取其输出,如下所示:

boost::asio::io_context gContext;
namespace bp = boost::process::v2;
struct Subprocess {
  bp::process process;
  std::string output;
};

// ...

auto pipe_stdout = std::make_unique<boost::asio::readable_pipe>(gContext);
auto pipe_stderr = std::make_unique<boost::asio::readable_pipe>(gContext);
auto subprocess = std::make_unique<Subprocess>(Subprocess{
    bp::process(gContext, "/bin/sh", { "-c", "my command" },
                bp::process_stdio{ nullptr, *pipe_stdout, *pipe_stderr }),
    std::string{} });
for (auto* pipe : { &pipe_stdout, &pipe_stderr }) {
  boost::asio::co_spawn(
      gContext,
      [pipe = std::move(*pipe),
       output = &subprocess->output]() -> boost::asio::awaitable<void> {
        while (true) {
          std::array<char, 1024> buf;
          size_t len = co_await pipe->async_read_some(
              boost::asio::buffer(buf), boost::asio::use_awaitable);
          if (len == 0 && !pipe->is_open()) {
            co_return;
          }
          output->append(buf.data(), len);
        }
      },
      boost::asio::detached);
}

我还设置了一个处理程序,用于收集进程完成后的情况(

finished_
running_
属于
std::vector<std::unique_ptr<Subprocess>>
类型:

subprocess->process.async_wait(
    [this, p = subprocess.get()](bp::error_code ec, int exit_code) {
      assert(!ec);
      auto it = std::ranges::find_if(
        running_, [p](const auto& up) { return up.get() == p; });
      finished_.emplace_back(std::move(*it));
      running_.erase(it);
    });

然后我有一个主循环,它检查进程是否已完成,如果是,则处理输出:

while (true) {
  while (!running_.empty() && finished_.empty()) {
    gContext.run_one();
  }
  if (finished_.empty()) {
    continue;
  }
  // process finished_.back(), get its output, etc.
  finished_.pop_back();
}

我现在遇到的问题是有时我没有捕获所有输出,缓冲区中似乎还剩下一些输出,并且我的协程读取管道尚未完成。我怎样才能避免这场比赛?我应该以某种方式读取输出并等待同一个协程中的退出吗?

c++ c++20 boost-asio boost-process
1个回答
0
投票

免责声明运行这样的命令存在严重的安全问题。我的演示假设您可以信任命令的来源。不要按原样使用我的代码!

尝试将其拼凑在一起,我认为展示我如何编写它会更容易:

住在科里鲁

#include <boost/asio.hpp>
#include <boost/asio/experimental/awaitable_operators.hpp>
#include <boost/process/v1/group.hpp>
#include <boost/process/v2.hpp>
#include <iostream>
namespace asio = boost::asio;
namespace bp = boost::process::v2;

asio::awaitable<std::string> async_command_output(std::string command) {
    auto ex = co_await asio::this_coro::executor;

    asio::readable_pipe pout(ex), perr(ex);
    asio::signal_set    sigs(ex, SIGINT);
    bp::process         child{
        ex, "/bin/sh", {"-c", command}, bp::process_stdio{.in = nullptr, .out = pout, .err = perr}};

    std::string output;

    auto read_loop = [&output](asio::readable_pipe& p) -> asio::awaitable<void> {
        std::array<char, 1024> buf;
        for (;;) {
            auto [ec, n] = co_await p.async_read_some(asio::buffer(buf), asio::as_tuple(asio::deferred));
            if (n)
                output.append(buf.data(), n);

            if (ec) {
                std::cerr << "read_loop: " << ec.message() << std::endl;
                break; // or co_return;
            }
        }
    };

    using namespace asio::experimental::awaitable_operators;
    int exit_code = co_await (                //
        read_loop(pout) &&                    //
        read_loop(perr) &&                    //
        child.async_wait(asio::use_awaitable) //
    );

    std::cerr << "Command returned exit code " << exit_code << std::endl;

    co_return output;
}

int main(int argc, char** argv) {
    asio::io_context ioc;

    co_spawn(
        ioc,
        [cmd = argc > 1 ? argv[1] : "ls -l"] -> asio::awaitable<void> {
            std::string output = co_await async_command_output(cmd);
            std::cout << "Output: " << quoted(output) << std::endl;
        },
        asio::detached);

    ioc.run();
}

演示运行(点击查看高分辨率):

© www.soinside.com 2019 - 2024. All rights reserved.