![](/img/trans.png)
[英]How to color the output stream of std::cout, but not of std::cerr and std::clog?
[英]boost::process How to correctly read process std::cout and std::cerr and preserve order
我正在通過boost::process
開始一個進程。 該過程使用std::cout
和std::cerr
向 output 一些信息。 我需要檢索這些信息。 在某些時候,我希望能夠存儲那些保留順序和嚴重性的輸出(來自cout
或cerr
的輸出)。
但是考慮到boost::process
重定向輸出的方式,我無法做到這一點。 我只能將std::cout
重定向到特定的 ipstream 並將std::cerr
重定向到另一個。 然后,在閱讀它們時,我無法保留順序。
這是一個 MCVE 隔離問題:
#include <iostream>
#include <boost/process.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
void doReadOutput( boost::process::ipstream* is, boost::process::ipstream* err, std::ostream* out )
{
std::string line;
if ( std::getline( *is, line ) )
*out << "cout: " << line << std::endl;
if ( std::getline( *err, line ) )
*out << "cerr: " << line << std::endl;
}
void readOutput( boost::process::ipstream* is, boost::process::ipstream* err, std::ostream* out, std::atomic_bool* continueFlag )
{
std::string line;
while ( *continueFlag )
{
doReadOutput( is, err, out );
}
// get last outputs that may remain in buffers
doReadOutput( is, err, out );
}
int main( int argc, char* argv[] )
{
if ( argc == 1 )
{
// run this same program with "foo" as parameter, to enter "else" statement below from a different process
try
{
boost::process::ipstream is_stream, err_stream;
std::stringstream merged_output;
std::atomic_bool continueFlag = true;
boost::process::child child( argv[0],
std::vector<std::string>{ "foo" },
boost::process::std_out > is_stream,
boost::process::std_err > err_stream );
boost::thread thrd( boost::bind( readOutput, &is_stream, &err_stream, &merged_output, &continueFlag ) );
child.wait();
continueFlag = false;
thrd.join();
std::cout << "Program output was:" << std::endl;
std::cout << merged_output.str();
}
catch ( const boost::process::process_error& err )
{
std::cerr << "Error: " << err.code() << std::endl;
}
catch (...) // @NOCOVERAGE
{
std::cerr << "Unknown error" << std::endl;
}
}
else
{
// program invoked through boost::process by "if" statement above
std::cerr << "Error1" << std::endl;
std::cout << "Hello World1" << std::endl;
std::cerr << "Error2" << std::endl;
std::cerr << "Error3" << std::endl;
std::cerr << "Error4" << std::endl;
std::cerr << "Error5" << std::endl;
std::cout << "Hello World2" << std::endl;
std::cerr << "Error6" << std::endl;
std::cout << "Hello World3" << std::endl;
}
return 0;
}
當我執行這個程序(在 Windows 10 下使用 Visual Studio 2019 編譯)時,它輸出:
Program output was:
cout: Hello World1
cerr: Error1
cout: Hello World2
cerr: Error2
cout: Hello World3
cerr: Error3
cerr: Error4
cerr: Error5
cerr: Error6
雖然我想要:
Program output was:
cerr: Error1
cout: Hello World1
cerr: Error2
cerr: Error3
cerr: Error4
cerr: Error5
cout: Hello World2
cerr: Error6
cout: Hello World3
有沒有辦法做到這一點?
編輯,正如一些程序員老兄所建議的,為每個 output stream 創建了一個線程:
#include <iostream>
#include <boost/process.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/thread/mutex.hpp>
void doReadOutput( boost::process::ipstream* str, std::ostream* out, const std::string& prefix, boost::mutex* mutex )
{
std::string line;
if ( std::getline( *str, line ) )
{
boost::mutex::scoped_lock lock( *mutex );
*out << prefix << ": " << line << std::endl;
}
}
void readOutput( boost::process::ipstream* str, std::ostream* out, std::string prefix, boost::mutex* mutex, std::atomic_bool* continueFlag )
{
while ( *continueFlag )
{
doReadOutput( str, out, prefix, mutex );
boost::thread::yield();
}
// get last outputs that may remain in buffers
doReadOutput( str, out, prefix, mutex );
}
int main( int argc, char* argv[] )
{
if ( argc == 1 )
{
// run this same program with "foo" as parameter, to enter "else" statement below from a different process
try
{
boost::process::ipstream is_stream, err_stream;
std::stringstream merged_output;
std::atomic_bool continueFlag = true;
boost::process::child child( argv[0],
std::vector<std::string>{ "foo" },
boost::process::std_out > is_stream,
boost::process::std_err > err_stream );
boost::mutex mutex;
boost::thread thrdis( boost::bind( readOutput, &is_stream, &merged_output, "cout", &mutex, &continueFlag ) );
boost::thread thrderr( boost::bind( readOutput, &err_stream, &merged_output, "cerr", &mutex, &continueFlag ) );
child.wait();
continueFlag = false;
thrdis.join();
thrderr.join();
std::cout << "Program output was:" << std::endl;
std::cout << merged_output.str();
}
catch ( const boost::process::process_error& err )
{
std::cerr << "Error: " << err.code() << std::endl;
}
catch (...) // @NOCOVERAGE
{
std::cerr << "Unknown error" << std::endl;
}
}
else
{
// program invoked through boost::process by "if" statement above
std::cerr << "Error1" << std::endl;
std::cout << "Hello World1" << std::endl;
std::cerr << "Error2" << std::endl;
std::cerr << "Error3" << std::endl;
std::cerr << "Error4" << std::endl;
std::cerr << "Error5" << std::endl;
std::cout << "Hello World2" << std::endl;
std::cerr << "Error6" << std::endl;
std::cout << "Hello World3" << std::endl;
}
return 0;
}
那么output就是:
Program output was:
cerr: Error1
cout: Hello World1
cerr: Error2
cout: Hello World2
cerr: Error3
cout: Hello World3
cerr: Error4
cerr: Error5
cerr: Error6
還是出乎意料...
您將需要非阻塞 IO。 庫中支持的方式是使用異步管道。
您將為 stderr/stdout 運行一個循環
因為你最終會在管道/緩沖區 state 上擁有兩次非常相同的循環,所以將它封裝成一個類型是有意義的,例如
struct IoPump {
IoPump(io_context& io, std::string& merged) : _pipe(io), _merged(merged) {}
boost::asio::streambuf _buf;
bp::async_pipe _pipe;
std::string& _merged;
void do_loop();
};
io_context io;
std::string merged;
IoPump outp{io, merged}, errp{io, merged};
bp::child child(program, std::vector<std::string> { "foo" },
bp::std_out > outp._pipe, bp::std_err > errp._pipe);
outp.do_loop(); // prime the pump
errp.do_loop(); // prime the pump
io.run();
就這樣。 好吧,當然除了IoPump::do_loop()
實際做了什么:
void do_loop() {
boost::asio::async_read_until(_pipe, _buf, "\n",
[this, out = boost::asio::dynamic_buffer(_merged)](
error_code ec, size_t xfer) mutable {
if (!ec) {
out.commit(buffer_copy(
out.prepare(xfer), _buf.data(), xfer));
_buf.consume(xfer);
do_loop(); // chain
} else {
std::cerr << "IoPump: " << ec.message() << "\n";
}
});
}
注意
std::string merged;
output緩沖區直接不用擔心同步static void main_program(char const* program);
static void child_program();
int main(int argc, char** argv) {
if (argc == 1)
main_program(argv[0]);
else
child_program();
}
#include <iostream>
static void child_program() {
std::cerr << "Error1" << std::endl;
std::cout << "Hello World1" << std::endl;
std::cerr << "Error2" << std::endl;
std::cerr << "Error3" << std::endl;
std::cerr << "Error4" << std::endl;
std::cerr << "Error5" << std::endl;
std::cout << "Hello World2" << std::endl;
std::cerr << "Error6" << std::endl;
std::cout << "Hello World3" << std::endl;
}
#include <boost/process.hpp>
#include <boost/asio.hpp>
static void main_program(char const* program) {
namespace bp = boost::process;
try {
using boost::system::error_code;
using boost::asio::io_context;
struct IoPump {
IoPump(io_context& io, std::string& merged) : _pipe(io), _merged(merged) {}
boost::asio::streambuf _buf;
bp::async_pipe _pipe;
std::string& _merged;
void do_loop() {
boost::asio::async_read_until(_pipe, _buf, "\n",
[this, out = boost::asio::dynamic_buffer(_merged)](
error_code ec, size_t xfer) mutable {
if (!ec) {
out.commit(buffer_copy(
out.prepare(xfer), _buf.data(), xfer));
_buf.consume(xfer);
do_loop(); // chain
} else {
std::cerr << "IoPump: " << ec.message() << "\n";
}
});
}
};
io_context io;
std::string merged;
IoPump outp{io, merged}, errp{io, merged};
bp::child child(program, std::vector<std::string> { "foo" },
bp::std_out > outp._pipe, bp::std_err > errp._pipe);
outp.do_loop(); // prime the pump
errp.do_loop(); // prime the pump
io.run();
std::cout << "Program output was:" << std::endl;
std::cout << merged;
} catch (const bp::process_error& err) {
std::cerr << "Error: " << err.code().message() << std::endl;
} catch (...) { // @NOCOVERAGE
std::cerr << "Unknown error" << std::endl;
}
}
印刷
IoPump: End of file
IoPump: End of file
和標准output:
Program output was:
Error1
Error2
Hello World1
Error3
Hello World2
Error4
Hello World3
Error5
Error6
我在這個網站上已經有很多例子了。 只需尋找async_pipe
您可以簡單地將 stderr 重定向到描述符級別的 stdout 並完成。 例如
boost::asio::io_context io;
std::future<std::string> merged;
bp::child child(program, std::vector<std::string> { "foo" },
bp::std_out > merged, bp::posix::fd.bind(2, 1), io);
io.run();
std::cout << "Program output was:" << std::quoted(merged.get()) << "\n";
或者使用逐行閱讀循環:
bp::ipstream merged;
bp::child child(program, std::vector<std::string> { "foo" },
bp::std_out > merged, bp::posix::fd.bind(2, 1));
child.wait();
std::cout << "Program output was:" << std::endl;
for (std::string line; getline(merged, line);)
std::cout << "merged: " << std::quoted(line) << "\n";
印刷
Program output was:
merged: "Error1"
merged: "Hello World1"
merged: "Error2"
merged: "Error3"
merged: "Error4"
merged: "Error5"
merged: "Hello World2"
merged: "Error6"
merged: "Hello World3"
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.