2

我正在尝试编写一个基于 boost::process 的程序,该程序能够根据是否定义了将它们重定向到的文件来模糊地重定向输入、输出和错误流,但我正在努力弄清楚如何处理它:

#include <string>
#include <boost/process.hpp>

int main(){
  std::string in;
  std::string out("out.txt");

  namespace bp = boost::process;

  bp::child c(bp::exe("test.exe"), bp::std_in < (in.empty() ? stdin : in),  bp::std_out > (out.empty() ? stdout : out)); // error

  return 0;
}

显然,由于类型不兼容,三元运算符不起作用,但我不知道如何实现这一点。我探索了 boost::variant 和 boost::any 但无济于事。

4

2 回答 2

1

Boost 选择了一种对熟悉命令行重定向的人来说很直观的表示法。但是,请记住这是 C++。和符号仍然是运算符<>它们没有成为命令行参数。代码bp::std_out > stdout是一个表达式。它评估为记录标准输出应该去哪里的某种对象。


适应不同类型的一个技巧是调整条件产生的点。不是有条件地选择 and 的参数,而是有条件地选择operator<构造函数operator>的参数bp::child

bp::child c(bp::exe("test.exe"),
            in.empty() ? (bp::std_in  < stdin) : (bp::std_in  < in),
            out.empty() ? (bp::std_out > stdout) : (bp::std_out > out));

如果不是运算符重载,这将更加明显。尽管<上面的两个符号看起来相同,但它们命名不同的运算符是因为它们的操作数类型不同。您的情况基本上需要调用f(bp::std_in, stdin)or g(bp::std_in, in),并且无法将这些调用与三元运算符合并。令人困惑的是,运算符名称不是fand ,而是and 。g<<

您可能希望使用辅助变量来使代码更易于阅读:

auto in_stream  =  in.empty() ? (bp::std_in  < stdin)  : (bp::std_in  < in);
auto out_stream = out.empty() ? (bp::std_out > stdout) : (bp::std_out > out);

bp::child c(bp::exe("test.exe"), in_stream, out_stream);
于 2021-01-23T17:54:53.697 回答
1

接口是静态类型和可变参数存在问题。[¹]

一般来说,各种关键字参数不能保证是兼容的类型,所以三元像cond? keyword1 : keyword2会由于类型不匹配而很快崩溃。

此外,有时您会想要动态添加/删除选项。

在我自己的生产代码中,我经常选择传统的控制流和一些代码重复,比如

if (condition)
    child = bp::child(/* params 1st style */);
else
    child = bp::child(/* params 2nd style */);

当然,如果有很多选择,这会导致难以维护的组合爆炸。您可以使用更复杂的可变参数 lambda 系统来组合关键字参数来欺骗它。

boost::asio::io_service ios;
boost::asio::deadline_timer deadline(ios);

bp::group process_group;

bp::async_pipe input(ios);
std::future<std::string> output, error;

if (_working_directory.empty())
    _working_directory = ".";

auto on_exit = [this, &deadline](int exit, std::error_code ec) {
    if (!_command_timed_out) {
        _exit_code = exit;
    }
    deadline.cancel();
    if (ec) log(LOG_WARNING) << "Child process returned " << ec.message();
    else    log(LOG_DEBUG)   << "Child process returned";
};

auto launcher = [](auto&&... args) { return bp::child(std::forward<decltype(args)>(args)..., bp::posix::fd.restrict_inherit()); };

auto redirect_out = [&](auto f) {
    return [&](auto&&... args) {
        if (_discard_output) {
            if (_redirected_output_fd != -1 && !_redirected_output_fname.empty()) {
                log(LOG_ERR) << "Ignoring output redirection with set_discard_output. This is a bug.";
            }
            return f(std::forward<decltype(args)>(args)..., bp::std_out > bp::null, bp::std_err > bp::null);
        }

        if (_redirected_output_fd != -1 && !_redirected_output_fname.empty()) {
            log(LOG_WARNING) << "Conflicting output redirection, ignoring filename with descriptor";
        }

        if (_redirected_output_fd != -1) {
            return f(std::forward<decltype(args)>(args)..., bp::posix::fd.bind(1, _redirected_output_fd), bp::std_err > error);
        }

        return _redirected_output_fname.empty()
            ? f(std::forward<decltype(args)>(args)..., bp::std_out > output,                   bp::std_err > error)
            : f(std::forward<decltype(args)>(args)..., bp::std_out > _redirected_output_fname, bp::std_err > error);
    };
};

bp::environment bp_env = boost::this_process::environment();
for (auto& p : _env)
    bp_env[p.first] = p.second;

auto c = redirect_out(launcher)(_command_path, _args,
        process_group,
        bp::std_in < input,
        bp::start_dir(_working_directory),
        bp_env,
        ios, bp::on_exit(on_exit)
    );

if (_time_constraint) {
    deadline.expires_from_now(*_time_constraint);
    deadline.async_wait([&](boost::system::error_code ec) {
        if (ec != boost::asio::error::operation_aborted) {
            if (ec) {
                log(LOG_WARNING) << "Unexpected condition in CommandRunner deadline: " << ec.message();
            }

            _command_timed_out = true;
            _exit_code = 1;
            ::killpg(process_group.native_handle(), SIGTERM);

            deadline.expires_from_now(3s); // grace time
            deadline.async_wait([&](boost::system::error_code ec) { if (!ec) process_group.terminate(); }); // timed out
        }
    });
}

boost::asio::async_write(input, bp::buffer(_stdin_data), [&input](auto ec, auto bytes_written){
    if (ec) {
        log(LOG_WARNING) << "Standard input rejected: " << ec.message() << " after " << bytes_written << " bytes written";
    }
    may_fail([&] { input.close(); });
});

ios.run();

if (output.valid()) _stdout_str = output.get();
if (error.valid())  _stderr_str = error.get();

// make sure no grand children survive
if (process_group && process_group.joinable() && !process_group.wait_for(1s))
    process_group.terminate();

当然,您应该添加异常处理并适应您的需求。


[¹] 不要让我开始。对我来说,这是过度设计的错误。在存在 process-exec 开销的情况下,没有性能参数可以弥补增加的复杂性和无数错误 [我多年来见过一些]。我不知道如何使用 Boost Process 做一些重要的事情。

当然,图书馆仍然有用,所以我会限制我的咆哮:) 我觉得今天有一些因果报应要燃烧

于 2021-01-23T23:19:39.890 回答