如何在进程运行时捕获标准输出并将其同时打印到控制台和文件中(C++/Boost.Process)

4
我正在处理一个应用程序,使用Boost的C++ Process库(http://www.highscore.de/boost/process0.5)启动子进程,并使用以下代码将该进程的标准输出重定向到文件中: boost::process::initializers::bind_stdout(boost::iostreams::file_descriptor_sink 此处为要输入的代码 ) 上述解决方案和代码已经有效。
然而,现在我需要能够同时将子进程的标准输出打印到文件和控制台。
我找到了下面的代码,看起来应该可以完成这项工作:
#include <boost/process.hpp>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <iostream>

using namespace boost::process;
using namespace boost::process::initializers;
using namespace boost::iostreams;

int main()
{
    boost::process::pipe p = create_pipe();
    {
        file_descriptor_sink sink(p.sink, close_handle);
        execute(
            run_exe("/bin/echo"),
            set_cmd_line("echo hello"),
            bind_stdout(sink)
        );
    }
    file_descriptor_source source(p.source, close_handle);
    stream<file_descriptor_source> is(source);
    std::string s;
    while (std::getline(is, s))
    {
            std::cout << s << std::endl;
            //will write line by line to file here
    }
}

我已经尝试运行上面的代码,确实可以将输出打印到控制台和文件中。 但是,在我的情况下,上述代码存在一个主要问题, while循环(while (std::getline(is, s)))只有在调用的进程退出后才开始运行,这意味着运行过程中积累的所有输出都会在进程完成后一次性打印出来。
现在, 我需要能够在调用进程运行时从中获取输出,并将其打印到控制台和文件中,而不仅仅是在结尾处打印。
(由于我启动的进程不受我的控制,可能需要几分钟以上的时间才能运行,当前屏幕控制台为空,直到进程退出)。
我已经查看了很多网站,试图找到解决方案,也对代码进行了很多尝试,但似乎还没有实现解决方案。
这如何实现?(使用Boost.Process是否可行?) 我该如何使字符串/流在子进程运行时填充标准输出,而不仅仅是在结束时才填充?

std::getline(s, is) 是一个打字错误。 - sehe
好的,谢谢你。我已经相应地修复了原始帖子。 - MichaelEr
1个回答

1
原则上,它应该按照您的要求工作。
也许您需要清空缓存(尝试使用std::unitbuf)。
或者,您可能在使用不支持异步管道的Windows操作系统。文档中描述了一个create_async_pipe函数:
bp::pipe create_async_pipe()
{
#if defined(BOOST_WINDOWS_API)
    std::string name = "\\\\.\\pipe\\boost_process_async_io";
    HANDLE handle1 = ::CreateNamedPipeA(name.c_str(), PIPE_ACCESS_INBOUND |
        FILE_FLAG_OVERLAPPED, 0, 1, 8192, 8192, 0, NULL);
    HANDLE handle2 = ::CreateFileA(name.c_str(), GENERIC_WRITE, 0, NULL,
        OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
    return make_pipe(handle1, handle2);
#elif defined(BOOST_POSIX_API)
    return create_pipe();
#endif
}

所以,这里有一个结合了所有内容的版本,包括 tee 设备:

#include <boost/process.hpp>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/tee.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <iostream>
#include <fstream>

namespace bp = boost::process;
namespace io = boost::iostreams;
using namespace bp;
using namespace bp::initializers;

bp::pipe create_async_pipe()
{
#if defined(BOOST_WINDOWS_API)
    std::string name = "\\\\.\\pipe\\boost_process_async_io";
    HANDLE handle1 = ::CreateNamedPipeA(name.c_str(), PIPE_ACCESS_INBOUND |
        FILE_FLAG_OVERLAPPED, 0, 1, 8192, 8192, 0, NULL);
    HANDLE handle2 = ::CreateFileA(name.c_str(), GENERIC_WRITE, 0, NULL,
        OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
    return make_pipe(handle1, handle2);
#elif defined(BOOST_POSIX_API)
    return create_pipe();
#endif
}

int main()
{
    bp::pipe p = create_async_pipe();

    {
        io::file_descriptor_sink sink(p.sink, io::close_handle);
        execute(
            run_exe("/bin/sh"),
            set_cmd_line("sh -c \"echo started; sleep 3 && date\""),
            bind_stdout(sink)
        );
    }

    io::file_descriptor_source source(p.source, io::close_handle);
    io::stream<io::file_descriptor_source> is(source);

    std::cout << "Process is running, let's wait before printing\n";

    system("sleep 1");

    std::ofstream ofile("test.log");
    io::filtering_ostream filt(io::tee(std::cout << std::unitbuf, ofile << std::unitbuf));

    std::string s;
    while (std::getline(is, s))
        filt << s << "\n";
}

命令已经选择了这样一种方式,以便您可以验证它以异步方式工作。
如果您想要真正的异步性,您也可以使用Boost Asio,请参阅文档了解详情:http://www.highscore.de/boost/process0.5/boost_process/tutorial.html#boost_process.tutorial.asynchronous_i_o

非常感谢您的帮助。在您确认代码无误后,我决定再次检查作为子进程执行的应用程序,结果发现问题出在Python可执行文件上(Python缓冲标准输出 - 这里有一个解决方案:https://dev59.com/inVD5IYBdhLWcg3wDHDm)。 - MichaelEr
另外,我无法编译上面的代码,在这一行编译时出现错误:io::filtering_ostream filt(io::tee(std::cout << std::unitbuf, ofile << std::unitbuf)); 异常信息大致如下:没有2个重载能够转换所有参数类型。你有什么想法为什么会发生这种情况以及如何解决? - MichaelEr
顺便说一下,我已经测试了我的原始代码,现在它运行得非常好。你认为我还是应该调整到你发布的代码吗? - MichaelEr
@MichaelEr 当然你可以自由选择 :) 感谢你的反馈。 - sehe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接