要使用输出重定向创建boost :: process,您应该执行以下操作:
bp::ipstream out;
bp::child c("c++filt", std_out > out);
现在,有条件地控制重定向的语法是什么:
bool redirect = true; // or false
bp::ipstream out;
bp::child c("c++filt", (redirect) ? std_out > out : "what should I put here??" );
答案 0 :(得分:2)
花哨的boost::process
API围绕提供实现handler
,on_setup
,on_error
的{{1}}个对象(可能还有一些,取决于当前的操作系统)方法这将在某个内部流程启动器的上下文中的流程构造调用中执行,并且将能够改变流程启动器行为。 on_success
是一个重载运算符,它将返回这样的处理程序。有关详细信息,请访问boost process Extensions documentation。
因此,有条件地控制重定向和其他参数的通用方法是编写通用处理程序代理,接受可选的实际处理程序并调用实际处理程序的适当方法:
std_out > out
答案 1 :(得分:0)
我去过那儿。
事实上,我使用的是启动器功能(不完全是工厂,但是可组合的程序包装器)。
我们有一个带有遗留实现的CommandRunner,我重写为使用Boost。我正在跳过公共界面:
class CommandRunner {
public: struct IRunnerImpl;
};
实现是Pimpl-ed,并与基本实现一起使用,主要存储简单的与实现无关的参数:
struct CommandRunner::IRunnerImpl {
virtual ~IRunnerImpl() = default;
virtual void run() = 0;
virtual void ensure_completed() = 0;
virtual std::string to_string() const = 0;
friend class CommandRunner;
protected:
std::string _working_directory;
mylibrary::optional<mylibrary::io::time::duration> _time_constraint;
std::string _stdin_data;
int _redirected_output_fd = -1;
std::string _redirected_output_fname;
bool _discard_output = false;
int _exit_code;
std::string _stdout_str;
std::string _stderr_str;
bool _command_timed_out = false;
bool _sensitive_args = false;
string_map_t _env;
};
ensure_completed
的核心是使用这样的助手lambdas组成的:
try {
mylibrary::threads::safe_io_service safe_ios;
boost::asio::io_service& ios = safe_ios;
mylibrary::io::time::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) s_logger.log(LOG_WARNING) << "Child process returned " << ec.message();
else s_logger.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 [&,f](auto&&... args) {
if (_discard_output) {
if (_redirected_output_fd != -1 && !_redirected_output_fname.empty()) {
s_logger.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()) {
s_logger.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) {
s_logger.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) {
s_logger.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();
// Note: even kills get the child reaped; 'on_exit' handler is
// actually the 'on wait_pid() complete'). No need for c.wait()
// in this scenario
//// may_fail([&] { if (c.running()) c.wait(); }); // to avoid zombies
} catch(bp::process_error const& e) {
if (e.code().value() != static_cast<int>(std::errc::no_child_process)) throw;
}
这个编译,但没有任何公共接口。仅用于说明目的。
注意:
epoll
is fundamentally broken与我们的用例“疯狂”多线程和分叉相关的毛茸茸案例。safe_io_service
尝试的解决方法(保证所有活动的io_services上的fork同步和通知)这不包括添加受限FD继承的补丁:
auto launcher = [](auto&&... args) { return
bp::child(std::forward<decltype(args)>(args)...,
bp::posix::fd.restrict_inherit()); };
此处未显示其他关键事项,如全局(库)状态的on-fork处理程序(它们使用pthread_atfork
和类似的)
<强> Compiling On Coliru 强>
#include <boost/process.hpp>
#include <boost/process/extend.hpp>
#include <boost/process/async.hpp>
#include <boost/process/posix.hpp>
#include <boost/optional.hpp>
#include <boost/asio/high_resolution_timer.hpp>
#include <signal.h> // ::killpg
#include <iomanip>
#include <map>
///////
// mockups for standalone exposition
//#include "log.h"
enum LOG_LEVELS{LOG_DEBUG, LOG_WARNING, LOG_ERR};
struct DummyLogger {
struct Tx {
template <typename...Ts> friend Tx operator<<(Tx const&, Ts&&...) { return {}; }
};
Tx log(LOG_LEVELS) { return {}; }
} s_logger;
//#include "safe_io_service.h"
namespace mylibrary {
namespace threads {
//this did manage fork notifications
using safe_io_service = boost::asio::io_service;
}
using boost::optional;
namespace io { namespace time {
using clock = std::chrono::high_resolution_clock;
using duration = clock::duration;
using timer = boost::asio::high_resolution_timer;
}
}
}
using namespace std::chrono_literals;
using string_vector_t = std::vector<std::string>;
using string_map_t = std::map<std::string, std::string>;
class CommandRunner {
public: struct IRunnerImpl;
};
struct CommandRunner::IRunnerImpl {
virtual ~IRunnerImpl() = default;
virtual void run() = 0;
virtual void ensure_completed() = 0;
virtual std::string to_string() const = 0;
friend class CommandRunner;
protected:
std::string _working_directory;
mylibrary::optional<mylibrary::io::time::duration> _time_constraint;
std::string _stdin_data;
int _redirected_output_fd = -1;
std::string _redirected_output_fname;
bool _discard_output = false;
int _exit_code;
std::string _stdout_str;
std::string _stderr_str;
bool _command_timed_out = false;
bool _sensitive_args = false;
string_map_t _env;
};
//
//////////////////////////////////////
namespace {
namespace bp = boost::process;
template <typename F> static auto may_fail(F&& f) {
try {
return std::forward<F>(f)();
} catch(std::exception const& e) {
s_logger.log(LOG_DEBUG) << "Ignoring non-fatal error (" << e.what() << ")";
} catch(...) {
s_logger.log(LOG_DEBUG) << "Ignoring non-fatal, unspecified error";
}
}
}
namespace mylibrary { namespace process { namespace with_boost {
struct BoostRunnerImpl : CommandRunner::IRunnerImpl {
BoostRunnerImpl(std::string cmd_path, string_vector_t args) :
_command_path(std::move(cmd_path)),
_args(std::move(args))
{
}
std::string _command_path;
string_vector_t _args;
virtual void run() override {
if (_completed) {
s_logger.log(LOG_DEBUG) << "NOT running already completed command: " << *this;
return;
}
ensure_completed();
}
////////////////////////////
// implementation
virtual void ensure_completed() override {
if (_completed) return;
// Log command and args
s_logger.log(LOG_DEBUG) << "Running command: " << *this;
try {
try {
mylibrary::threads::safe_io_service safe_ios;
boost::asio::io_service& ios = safe_ios;
mylibrary::io::time::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) s_logger.log(LOG_WARNING) << "Child process returned " << ec.message();
else s_logger.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 [&,f](auto&&... args) {
if (_discard_output) {
if (_redirected_output_fd != -1 && !_redirected_output_fname.empty()) {
s_logger.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()) {
s_logger.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) {
s_logger.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) {
s_logger.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();
// Note: even kills get the child reaped; 'on_exit' handler is
// actually the 'on wait_pid() complete'). No need for c.wait()
// in this scenario
//// may_fail([&] { if (c.running()) c.wait(); }); // to avoid zombies
} catch(bp::process_error const& e) {
if (e.code().value() != static_cast<int>(std::errc::no_child_process)) throw;
}
} catch(std::exception const& e) {
s_logger.log(LOG_ERR) << "CommandRunner: " << e.what();
_completed = true;
_exit_code = 1;
throw;
} catch(...) {
s_logger.log(LOG_ERR) << "CommandRunner: unexpected error";
_completed = true;
_exit_code = 1;
throw;
}
_completed = true;
}
private:
bool _completed = false;
friend std::ostream& operator<<(std::ostream& os, BoostRunnerImpl const& i) {
os << i._command_path;
if (i._sensitive_args)
os << " (" << i._args.size() << " args)";
else for (auto& arg : i._args)
os << " " << std::quoted(arg);
return os;
}
virtual std::string to_string() const override {
std::ostringstream oss;
oss << *this;
return oss.str();
}
};
} } }
int main(){}