簡體   English   中英

boost::process 如何正確讀取進程 std::cout 和 std::cerr 並保持順序

[英]boost::process How to correctly read process std::cout and std::cerr and preserve order

我正在通過boost::process開始一個進程。 該過程使用std::coutstd::cerr向 output 一些信息。 我需要檢索這些信息。 在某些時候,我希望能夠存儲那些保留順序和嚴重性的輸出(來自coutcerr的輸出)。

但是考慮到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 運行一個循環

  • async_read 進入緩沖區,直到你得到一個完整的行或更多
  • 一旦輸入緩沖區可用,就將行從輸入緩沖區復制到 output 緩沖區

因為你最終會在管道/緩沖區 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM