我正在生成一个进程并使用 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();
}
我现在遇到的问题是有时我没有捕获所有输出,缓冲区中似乎还剩下一些输出,并且我的协程读取管道尚未完成。我怎样才能避免这场比赛?我应该以某种方式读取输出并等待同一个协程中的退出吗?
免责声明运行这样的命令存在严重的安全问题。我的演示假设您可以信任命令的来源。不要按原样使用我的代码!
尝试将其拼凑在一起,我认为展示我如何编写它会更容易:
住在科里鲁
#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();
}
演示运行(点击查看高分辨率):