有没有办法将 stdout/stderr 重定向到字符串?

发布于 2024-10-26 21:26:07 字数 69 浏览 2 评论 0原文

之前有很多关于将 stdout/stderr 重定向到文件的问题。有没有办法将 stdout/stderr 重定向到字符串?

there has been many previous questions about redirecting stdout/stderr to a file. is there a way to redirect stdout/stderr to a string?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

阪姬 2024-11-02 21:26:07

是的,您可以将其重定向到 std::stringstream:

std::stringstream buffer;
std::streambuf * old = std::cout.rdbuf(buffer.rdbuf());

std::cout << "Bla" << std::endl;

std::string text = buffer.str(); // text will now contain "Bla\n"

您可以使用一个简单的保护类来确保缓冲区始终重置:

struct cout_redirect {
    cout_redirect( std::streambuf * new_buffer ) 
        : old( std::cout.rdbuf( new_buffer ) )
    { }

    ~cout_redirect( ) {
        std::cout.rdbuf( old );
    }

private:
    std::streambuf * old;
};

Yes, you can redirect it to an std::stringstream:

std::stringstream buffer;
std::streambuf * old = std::cout.rdbuf(buffer.rdbuf());

std::cout << "Bla" << std::endl;

std::string text = buffer.str(); // text will now contain "Bla\n"

You can use a simple guard class to make sure the buffer is always reset:

struct cout_redirect {
    cout_redirect( std::streambuf * new_buffer ) 
        : old( std::cout.rdbuf( new_buffer ) )
    { }

    ~cout_redirect( ) {
        std::cout.rdbuf( old );
    }

private:
    std::streambuf * old;
};
甜味拾荒者 2024-11-02 21:26:07

您可以使用此类:

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string>

class StdCapture
{
public:
    StdCapture(): m_capturing(false), m_init(false), m_oldStdOut(0), m_oldStdErr(0)
    {
        m_pipe[READ] = 0;
        m_pipe[WRITE] = 0;
        if (_pipe(m_pipe, 65536, O_BINARY) == -1)
            return;
        m_oldStdOut = dup(fileno(stdout));
        m_oldStdErr = dup(fileno(stderr));
        if (m_oldStdOut == -1 || m_oldStdErr == -1)
            return;

        m_init = true;
    }

    ~StdCapture()
    {
        if (m_capturing)
        {
            EndCapture();
        }
        if (m_oldStdOut > 0)
            close(m_oldStdOut);
        if (m_oldStdErr > 0)
            close(m_oldStdErr);
        if (m_pipe[READ] > 0)
            close(m_pipe[READ]);
        if (m_pipe[WRITE] > 0)
            close(m_pipe[WRITE]);
    }


    void BeginCapture()
    {
        if (!m_init)
            return;
        if (m_capturing)
            EndCapture();
        fflush(stdout);
        fflush(stderr);
        dup2(m_pipe[WRITE], fileno(stdout));
        dup2(m_pipe[WRITE], fileno(stderr));
        m_capturing = true;
    }

    bool EndCapture()
    {
        if (!m_init)
            return false;
        if (!m_capturing)
            return false;
        fflush(stdout);
        fflush(stderr);
        dup2(m_oldStdOut, fileno(stdout));
        dup2(m_oldStdErr, fileno(stderr));
        m_captured.clear();

        std::string buf;
        const int bufSize = 1024;
        buf.resize(bufSize);
        int bytesRead = 0;
        if (!eof(m_pipe[READ]))
        {
            bytesRead = read(m_pipe[READ], &(*buf.begin()), bufSize);
        }
        while(bytesRead == bufSize)
        {
            m_captured += buf;
            bytesRead = 0;
            if (!eof(m_pipe[READ]))
            {
                bytesRead = read(m_pipe[READ], &(*buf.begin()), bufSize);
            }
        }
        if (bytesRead > 0)
        {
            buf.resize(bytesRead);
            m_captured += buf;
        }
        m_capturing = false;
        return true;
    }

    std::string GetCapture() const
    {
        std::string::size_type idx = m_captured.find_last_not_of("\r\n");
        if (idx == std::string::npos)
        {
            return m_captured;
        }
        else
        {
            return m_captured.substr(0, idx+1);
        }
    }

private:
    enum PIPES { READ, WRITE };
    int m_pipe[2];
    int m_oldStdOut;
    int m_oldStdErr;
    bool m_capturing;
    bool m_init;
    std::string m_captured;
};

在需要开始捕获时调用BeginCapture()
当您需要停止捕获时调用 EndCapture()
调用 GetCapture() 检索捕获的输出

You can use this class:

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string>

class StdCapture
{
public:
    StdCapture(): m_capturing(false), m_init(false), m_oldStdOut(0), m_oldStdErr(0)
    {
        m_pipe[READ] = 0;
        m_pipe[WRITE] = 0;
        if (_pipe(m_pipe, 65536, O_BINARY) == -1)
            return;
        m_oldStdOut = dup(fileno(stdout));
        m_oldStdErr = dup(fileno(stderr));
        if (m_oldStdOut == -1 || m_oldStdErr == -1)
            return;

        m_init = true;
    }

    ~StdCapture()
    {
        if (m_capturing)
        {
            EndCapture();
        }
        if (m_oldStdOut > 0)
            close(m_oldStdOut);
        if (m_oldStdErr > 0)
            close(m_oldStdErr);
        if (m_pipe[READ] > 0)
            close(m_pipe[READ]);
        if (m_pipe[WRITE] > 0)
            close(m_pipe[WRITE]);
    }


    void BeginCapture()
    {
        if (!m_init)
            return;
        if (m_capturing)
            EndCapture();
        fflush(stdout);
        fflush(stderr);
        dup2(m_pipe[WRITE], fileno(stdout));
        dup2(m_pipe[WRITE], fileno(stderr));
        m_capturing = true;
    }

    bool EndCapture()
    {
        if (!m_init)
            return false;
        if (!m_capturing)
            return false;
        fflush(stdout);
        fflush(stderr);
        dup2(m_oldStdOut, fileno(stdout));
        dup2(m_oldStdErr, fileno(stderr));
        m_captured.clear();

        std::string buf;
        const int bufSize = 1024;
        buf.resize(bufSize);
        int bytesRead = 0;
        if (!eof(m_pipe[READ]))
        {
            bytesRead = read(m_pipe[READ], &(*buf.begin()), bufSize);
        }
        while(bytesRead == bufSize)
        {
            m_captured += buf;
            bytesRead = 0;
            if (!eof(m_pipe[READ]))
            {
                bytesRead = read(m_pipe[READ], &(*buf.begin()), bufSize);
            }
        }
        if (bytesRead > 0)
        {
            buf.resize(bytesRead);
            m_captured += buf;
        }
        m_capturing = false;
        return true;
    }

    std::string GetCapture() const
    {
        std::string::size_type idx = m_captured.find_last_not_of("\r\n");
        if (idx == std::string::npos)
        {
            return m_captured;
        }
        else
        {
            return m_captured.substr(0, idx+1);
        }
    }

private:
    enum PIPES { READ, WRITE };
    int m_pipe[2];
    int m_oldStdOut;
    int m_oldStdErr;
    bool m_capturing;
    bool m_init;
    std::string m_captured;
};

call BeginCapture() when you need to start capture
call EndCapture() when you need to stop capture
call GetCapture() to retrieve captured output

坐在坟头思考人生 2024-11-02 21:26:07

为了提供线程安全&跨平台解决方案,我已将 rmflow 的方法改编为类似的界面。由于此类修改全局文件描述符,我将其改编为互斥锁保护的静态类,以防止多个实例对全局文件描述符造成影响。此外,rmflow 的答案不会清除所有已使用的文件描述符,如果许多 BeginCapture() 和文件描述符,这可能会导致打开新文件描述符(用于输出流或文件)时出现问题。 EndCapture() 调用在一个应用程序中使用。此代码已在 Windows 7/8、Linux、OSX、Android 和 iOS 上进行了测试。

注意:为了使用 std::mutex,您必须针对 c++ 11 进行编译。如果您不/不能使用 c++11,您可以完全删除互斥调用(牺牲线程安全),或者您可以找到遗留的同步机制来完成工作。

#ifdef _MSC_VER
#include <io.h>
#define popen _popen 
#define pclose _pclose
#define stat _stat 
#define dup _dup
#define dup2 _dup2
#define fileno _fileno
#define close _close
#define pipe _pipe
#define read _read
#define eof _eof
#else
#include <unistd.h>
#endif
#include <fcntl.h>
#include <stdio.h>
#include <mutex>

class StdCapture
{
public:
    static void Init()
    {
        // make stdout & stderr streams unbuffered
        // so that we don't need to flush the streams
        // before capture and after capture 
        // (fflush can cause a deadlock if the stream is currently being 
        std::lock_guard<std::mutex> lock(m_mutex);
        setvbuf(stdout,NULL,_IONBF,0);
        setvbuf(stderr,NULL,_IONBF,0);
    }

    static void BeginCapture()
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        if (m_capturing)
            return;

        secure_pipe(m_pipe);
        m_oldStdOut = secure_dup(STD_OUT_FD);
        m_oldStdErr = secure_dup(STD_ERR_FD);
        secure_dup2(m_pipe[WRITE],STD_OUT_FD);
        secure_dup2(m_pipe[WRITE],STD_ERR_FD);
        m_capturing = true;
#ifndef _MSC_VER
        secure_close(m_pipe[WRITE]);
#endif
    }
    static bool IsCapturing()
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        return m_capturing;
    }
    static bool EndCapture()
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        if (!m_capturing)
            return;

        m_captured.clear();
        secure_dup2(m_oldStdOut, STD_OUT_FD);
        secure_dup2(m_oldStdErr, STD_ERR_FD);

        const int bufSize = 1025;
        char buf[bufSize];
        int bytesRead = 0;
        bool fd_blocked(false);
        do
        {
            bytesRead = 0;
            fd_blocked = false;
#ifdef _MSC_VER
            if (!eof(m_pipe[READ]))
                bytesRead = read(m_pipe[READ], buf, bufSize-1);
#else
            bytesRead = read(m_pipe[READ], buf, bufSize-1);
#endif
            if (bytesRead > 0)
            {
                buf[bytesRead] = 0;
                m_captured += buf;
            }
            else if (bytesRead < 0)
            {
                fd_blocked = (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR);
                if (fd_blocked)
                    std::this_thread::sleep_for(std::chrono::milliseconds(10));
            }
        }
        while(fd_blocked || bytesRead == (bufSize-1));

        secure_close(m_oldStdOut);
        secure_close(m_oldStdErr);
        secure_close(m_pipe[READ]);
#ifdef _MSC_VER
        secure_close(m_pipe[WRITE]);
#endif
        m_capturing = false;
    }
    static std::string GetCapture()
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        return m_captured;
    }
private:
    enum PIPES { READ, WRITE };

    int StdCapture::secure_dup(int src)
    {
        int ret = -1;
        bool fd_blocked = false;
        do
        {
             ret = dup(src);
             fd_blocked = (errno == EINTR ||  errno == EBUSY);
             if (fd_blocked)
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
        while (ret < 0);
        return ret;
    }
    void StdCapture::secure_pipe(int * pipes)
    {
        int ret = -1;
        bool fd_blocked = false;
        do
        {
#ifdef _MSC_VER
            ret = pipe(pipes, 65536, O_BINARY);
#else
            ret = pipe(pipes) == -1;
#endif
            fd_blocked = (errno == EINTR ||  errno == EBUSY);
            if (fd_blocked)
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
        while (ret < 0);
    }
    void StdCapture::secure_dup2(int src, int dest)
    {
        int ret = -1;
        bool fd_blocked = false;
        do
        {
             ret = dup2(src,dest);
             fd_blocked = (errno == EINTR ||  errno == EBUSY);
             if (fd_blocked)
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
        while (ret < 0);
    }

    void StdCapture::secure_close(int & fd)
    {
        int ret = -1;
        bool fd_blocked = false;
        do
        {
             ret = close(fd);
             fd_blocked = (errno == EINTR);
             if (fd_blocked)
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
        while (ret < 0);

        fd = -1;
    }

    static int m_pipe[2];
    static int m_oldStdOut;
    static int m_oldStdErr;
    static bool m_capturing;
    static std::mutex m_mutex;
    static std::string m_captured;
};

// actually define vars.
int StdCapture::m_pipe[2];
int StdCapture::m_oldStdOut;
int StdCapture::m_oldStdErr;
bool StdCapture::m_capturing;
std::mutex StdCapture::m_mutex;
std::string StdCapture::m_captured;

调用 Init() 一次(在捕获之前)以删除对 stdout / stderr 的缓冲

在需要开始捕获时

调用 BeginCapture() 调用 EndCapture() 当您需要停止捕获时,

调用 GetCapture() 来检索捕获的输出

,调用 IsCapturing() 来查看 stdout/stderr 当前是否已重定向

In order to provide a thread-safe & cross platform solution, I have adapted rmflow's approach into a similar interface. As this class modifies global file descriptors, I adapted it to a mutex-guarded static class that protects against multiple instances thrashing global file descriptors. In addition, rmflow's answer does not clean up all of the used file descriptors which can lead to problems opening new ones (for output streams or files) if many BeginCapture() & EndCapture() calls are used in one application. This code has been tested on Windows 7/8, Linux, OSX, Android, and iOS.

NOTE: In order to use std::mutex you must compile against c++ 11. If you do not / cannot use c++11, you can remove the mutex calls completely (sacrificing thread safety) or you can find a legacy sychronization mechanism to get the job done.

#ifdef _MSC_VER
#include <io.h>
#define popen _popen 
#define pclose _pclose
#define stat _stat 
#define dup _dup
#define dup2 _dup2
#define fileno _fileno
#define close _close
#define pipe _pipe
#define read _read
#define eof _eof
#else
#include <unistd.h>
#endif
#include <fcntl.h>
#include <stdio.h>
#include <mutex>

class StdCapture
{
public:
    static void Init()
    {
        // make stdout & stderr streams unbuffered
        // so that we don't need to flush the streams
        // before capture and after capture 
        // (fflush can cause a deadlock if the stream is currently being 
        std::lock_guard<std::mutex> lock(m_mutex);
        setvbuf(stdout,NULL,_IONBF,0);
        setvbuf(stderr,NULL,_IONBF,0);
    }

    static void BeginCapture()
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        if (m_capturing)
            return;

        secure_pipe(m_pipe);
        m_oldStdOut = secure_dup(STD_OUT_FD);
        m_oldStdErr = secure_dup(STD_ERR_FD);
        secure_dup2(m_pipe[WRITE],STD_OUT_FD);
        secure_dup2(m_pipe[WRITE],STD_ERR_FD);
        m_capturing = true;
#ifndef _MSC_VER
        secure_close(m_pipe[WRITE]);
#endif
    }
    static bool IsCapturing()
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        return m_capturing;
    }
    static bool EndCapture()
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        if (!m_capturing)
            return;

        m_captured.clear();
        secure_dup2(m_oldStdOut, STD_OUT_FD);
        secure_dup2(m_oldStdErr, STD_ERR_FD);

        const int bufSize = 1025;
        char buf[bufSize];
        int bytesRead = 0;
        bool fd_blocked(false);
        do
        {
            bytesRead = 0;
            fd_blocked = false;
#ifdef _MSC_VER
            if (!eof(m_pipe[READ]))
                bytesRead = read(m_pipe[READ], buf, bufSize-1);
#else
            bytesRead = read(m_pipe[READ], buf, bufSize-1);
#endif
            if (bytesRead > 0)
            {
                buf[bytesRead] = 0;
                m_captured += buf;
            }
            else if (bytesRead < 0)
            {
                fd_blocked = (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR);
                if (fd_blocked)
                    std::this_thread::sleep_for(std::chrono::milliseconds(10));
            }
        }
        while(fd_blocked || bytesRead == (bufSize-1));

        secure_close(m_oldStdOut);
        secure_close(m_oldStdErr);
        secure_close(m_pipe[READ]);
#ifdef _MSC_VER
        secure_close(m_pipe[WRITE]);
#endif
        m_capturing = false;
    }
    static std::string GetCapture()
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        return m_captured;
    }
private:
    enum PIPES { READ, WRITE };

    int StdCapture::secure_dup(int src)
    {
        int ret = -1;
        bool fd_blocked = false;
        do
        {
             ret = dup(src);
             fd_blocked = (errno == EINTR ||  errno == EBUSY);
             if (fd_blocked)
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
        while (ret < 0);
        return ret;
    }
    void StdCapture::secure_pipe(int * pipes)
    {
        int ret = -1;
        bool fd_blocked = false;
        do
        {
#ifdef _MSC_VER
            ret = pipe(pipes, 65536, O_BINARY);
#else
            ret = pipe(pipes) == -1;
#endif
            fd_blocked = (errno == EINTR ||  errno == EBUSY);
            if (fd_blocked)
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
        while (ret < 0);
    }
    void StdCapture::secure_dup2(int src, int dest)
    {
        int ret = -1;
        bool fd_blocked = false;
        do
        {
             ret = dup2(src,dest);
             fd_blocked = (errno == EINTR ||  errno == EBUSY);
             if (fd_blocked)
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
        while (ret < 0);
    }

    void StdCapture::secure_close(int & fd)
    {
        int ret = -1;
        bool fd_blocked = false;
        do
        {
             ret = close(fd);
             fd_blocked = (errno == EINTR);
             if (fd_blocked)
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
        while (ret < 0);

        fd = -1;
    }

    static int m_pipe[2];
    static int m_oldStdOut;
    static int m_oldStdErr;
    static bool m_capturing;
    static std::mutex m_mutex;
    static std::string m_captured;
};

// actually define vars.
int StdCapture::m_pipe[2];
int StdCapture::m_oldStdOut;
int StdCapture::m_oldStdErr;
bool StdCapture::m_capturing;
std::mutex StdCapture::m_mutex;
std::string StdCapture::m_captured;

call Init() once (before capture) to remove buffering to stdout / stderr

call BeginCapture() when you need to start capture

call EndCapture() when you need to stop capture

call GetCapture() to retrieve captured output

call IsCapturing() to see if stdout/stderr is currently redirected

陌伤浅笑 2024-11-02 21:26:07

我已经提供了 Björn Pollex 代码的 qt osx 就绪变体

#include <stdio.h>
#include <iostream>
#include <streambuf>
#include <stdlib.h>
#include <string>
#include <sstream>

class CoutRedirect {

public:
    CoutRedirect() {
        old = std::cout.rdbuf( buffer.rdbuf() ); // redirect cout to buffer stream
    }

    std::string getString() {
        return buffer.str(); // get string
    }

    ~CoutRedirect( ) {
        std::cout.rdbuf( old ); // reverse redirect
    }

private:
    std::stringstream buffer;
    std::streambuf * old;
};

i've furnished a qt osx ready variation from Björn Pollex code

#include <stdio.h>
#include <iostream>
#include <streambuf>
#include <stdlib.h>
#include <string>
#include <sstream>

class CoutRedirect {

public:
    CoutRedirect() {
        old = std::cout.rdbuf( buffer.rdbuf() ); // redirect cout to buffer stream
    }

    std::string getString() {
        return buffer.str(); // get string
    }

    ~CoutRedirect( ) {
        std::cout.rdbuf( old ); // reverse redirect
    }

private:
    std::stringstream buffer;
    std::streambuf * old;
};
初见你 2024-11-02 21:26:07

我修改了 Sir Digby Chicken Caesar 中的类,使其不是静态的,并且可以在单元测试中轻松使用。它在 gcc (g++) 编译的 Windows 上适用于我,但我不能保证它是 100% 正确的,如果不是,请留下评论。

创建 StdCapture 类的对象,然后调用 BeginCapture() 开始捕获,并在结束时调用 EndCapture()Init() 中的代码被移至构造函数。一次只能有一个这样的对象在工作。

StdCapture.h:

#ifdef _MSC_VER
#include <io.h>
#define popen _popen 
#define pclose _pclose
#define stat _stat 
#define dup _dup
#define dup2 _dup2
#define fileno _fileno
#define close _close
#define pipe _pipe
#define read _read
#define eof _eof
#else
#include <unistd.h>
#endif
#include <fcntl.h>
#include <stdio.h>
#include <mutex>
#include <chrono>
#include <thread>

#ifndef STD_OUT_FD 
#define STD_OUT_FD (fileno(stdout)) 
#endif 

#ifndef STD_ERR_FD 
#define STD_ERR_FD (fileno(stderr)) 
#endif

class StdCapture
{
public:

    StdCapture();

    void BeginCapture();
    bool IsCapturing();
    bool EndCapture();
    std::string GetCapture();

private:
    enum PIPES { READ, WRITE };
    
    int secure_dup(int src);
    void secure_pipe(int * pipes);
    void secure_dup2(int src, int dest);
    void secure_close(int & fd);

    int m_pipe[2];
    int m_oldStdOut;
    int m_oldStdErr;
    bool m_capturing;
    std::mutex m_mutex;
    std::string m_captured;
};

StdCapture.cpp:

#include "StdCapture.h"

StdCapture::StdCapture():
    m_capturing(false)
{
    // make stdout & stderr streams unbuffered
    // so that we don't need to flush the streams
    // before capture and after capture 
    // (fflush can cause a deadlock if the stream is currently being 
    std::lock_guard<std::mutex> lock(m_mutex);
    setvbuf(stdout,NULL,_IONBF,0);
    setvbuf(stderr,NULL,_IONBF,0);
}

void StdCapture::BeginCapture()
{
    std::lock_guard<std::mutex> lock(m_mutex);
    if (m_capturing)
        return;

    secure_pipe(m_pipe);
    m_oldStdOut = secure_dup(STD_OUT_FD);
    m_oldStdErr = secure_dup(STD_ERR_FD);
    secure_dup2(m_pipe[WRITE],STD_OUT_FD);
    secure_dup2(m_pipe[WRITE],STD_ERR_FD);
    m_capturing = true;
#ifndef _MSC_VER
    secure_close(m_pipe[WRITE]);
#endif
}
bool StdCapture::IsCapturing()
{
    std::lock_guard<std::mutex> lock(m_mutex);
    return m_capturing;
}
bool StdCapture::EndCapture()
{
    std::lock_guard<std::mutex> lock(m_mutex);
    if (!m_capturing)
        return true;

    m_captured.clear();
    secure_dup2(m_oldStdOut, STD_OUT_FD);
    secure_dup2(m_oldStdErr, STD_ERR_FD);

    const int bufSize = 1025;
    char buf[bufSize];
    int bytesRead = 0;
    bool fd_blocked(false);
    do
    {
        bytesRead = 0;
        fd_blocked = false;
#ifdef _MSC_VER
        if (!eof(m_pipe[READ]))
            bytesRead = read(m_pipe[READ], buf, bufSize-1);
#else
        bytesRead = read(m_pipe[READ], buf, bufSize-1);
#endif
        if (bytesRead > 0)
        {
            buf[bytesRead] = 0;
            m_captured += buf;
        }
        else if (bytesRead < 0)
        {
            fd_blocked = (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR);
            if (fd_blocked)
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
    while(fd_blocked || bytesRead == (bufSize-1));

    secure_close(m_oldStdOut);
    secure_close(m_oldStdErr);
    secure_close(m_pipe[READ]);
#ifdef _MSC_VER
    secure_close(m_pipe[WRITE]);
#endif
    m_capturing = false;
    return true;
}
std::string StdCapture::GetCapture()
{
    std::lock_guard<std::mutex> lock(m_mutex);
    return m_captured;
}

int StdCapture::secure_dup(int src)
{
    int ret = -1;
    bool fd_blocked = false;
    do
    {
         ret = dup(src);
         fd_blocked = (errno == EINTR ||  errno == EBUSY);
         if (fd_blocked)
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    while (ret < 0);
    return ret;
}
void StdCapture::secure_pipe(int * pipes)
{
    int ret = -1;
    bool fd_blocked = false;
    do
    {
#ifdef _MSC_VER
        ret = pipe(pipes, 65536, O_BINARY);
#else
        ret = pipe(pipes) == -1;
#endif
        fd_blocked = (errno == EINTR ||  errno == EBUSY);
        if (fd_blocked)
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    while (ret < 0);
}
void StdCapture::secure_dup2(int src, int dest)
{
    int ret = -1;
    bool fd_blocked = false;
    do
    {
         ret = dup2(src,dest);
         fd_blocked = (errno == EINTR ||  errno == EBUSY);
         if (fd_blocked)
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    while (ret < 0);
}

void StdCapture::secure_close(int & fd)
{
    int ret = -1;
    bool fd_blocked = false;
    do
    {
         ret = close(fd);
         fd_blocked = (errno == EINTR);
         if (fd_blocked)
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    while (ret < 0);

    fd = -1;
}

I modified class from Sir Digby Chicken Caesar so that it's not static and could be used easily in unit tests. It works for me on Windows compiled by gcc (g++), but I cannot guarantee that it is 100% correct, please leave comments if it is not.

Create object of class StdCapture, and just call BeginCapture() to begin capture and EndCapture() at the end. Code from Init() is moved to the constructor. There shall be only one such object working at a time.

StdCapture.h:

#ifdef _MSC_VER
#include <io.h>
#define popen _popen 
#define pclose _pclose
#define stat _stat 
#define dup _dup
#define dup2 _dup2
#define fileno _fileno
#define close _close
#define pipe _pipe
#define read _read
#define eof _eof
#else
#include <unistd.h>
#endif
#include <fcntl.h>
#include <stdio.h>
#include <mutex>
#include <chrono>
#include <thread>

#ifndef STD_OUT_FD 
#define STD_OUT_FD (fileno(stdout)) 
#endif 

#ifndef STD_ERR_FD 
#define STD_ERR_FD (fileno(stderr)) 
#endif

class StdCapture
{
public:

    StdCapture();

    void BeginCapture();
    bool IsCapturing();
    bool EndCapture();
    std::string GetCapture();

private:
    enum PIPES { READ, WRITE };
    
    int secure_dup(int src);
    void secure_pipe(int * pipes);
    void secure_dup2(int src, int dest);
    void secure_close(int & fd);

    int m_pipe[2];
    int m_oldStdOut;
    int m_oldStdErr;
    bool m_capturing;
    std::mutex m_mutex;
    std::string m_captured;
};

StdCapture.cpp:

#include "StdCapture.h"

StdCapture::StdCapture():
    m_capturing(false)
{
    // make stdout & stderr streams unbuffered
    // so that we don't need to flush the streams
    // before capture and after capture 
    // (fflush can cause a deadlock if the stream is currently being 
    std::lock_guard<std::mutex> lock(m_mutex);
    setvbuf(stdout,NULL,_IONBF,0);
    setvbuf(stderr,NULL,_IONBF,0);
}

void StdCapture::BeginCapture()
{
    std::lock_guard<std::mutex> lock(m_mutex);
    if (m_capturing)
        return;

    secure_pipe(m_pipe);
    m_oldStdOut = secure_dup(STD_OUT_FD);
    m_oldStdErr = secure_dup(STD_ERR_FD);
    secure_dup2(m_pipe[WRITE],STD_OUT_FD);
    secure_dup2(m_pipe[WRITE],STD_ERR_FD);
    m_capturing = true;
#ifndef _MSC_VER
    secure_close(m_pipe[WRITE]);
#endif
}
bool StdCapture::IsCapturing()
{
    std::lock_guard<std::mutex> lock(m_mutex);
    return m_capturing;
}
bool StdCapture::EndCapture()
{
    std::lock_guard<std::mutex> lock(m_mutex);
    if (!m_capturing)
        return true;

    m_captured.clear();
    secure_dup2(m_oldStdOut, STD_OUT_FD);
    secure_dup2(m_oldStdErr, STD_ERR_FD);

    const int bufSize = 1025;
    char buf[bufSize];
    int bytesRead = 0;
    bool fd_blocked(false);
    do
    {
        bytesRead = 0;
        fd_blocked = false;
#ifdef _MSC_VER
        if (!eof(m_pipe[READ]))
            bytesRead = read(m_pipe[READ], buf, bufSize-1);
#else
        bytesRead = read(m_pipe[READ], buf, bufSize-1);
#endif
        if (bytesRead > 0)
        {
            buf[bytesRead] = 0;
            m_captured += buf;
        }
        else if (bytesRead < 0)
        {
            fd_blocked = (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR);
            if (fd_blocked)
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
    while(fd_blocked || bytesRead == (bufSize-1));

    secure_close(m_oldStdOut);
    secure_close(m_oldStdErr);
    secure_close(m_pipe[READ]);
#ifdef _MSC_VER
    secure_close(m_pipe[WRITE]);
#endif
    m_capturing = false;
    return true;
}
std::string StdCapture::GetCapture()
{
    std::lock_guard<std::mutex> lock(m_mutex);
    return m_captured;
}

int StdCapture::secure_dup(int src)
{
    int ret = -1;
    bool fd_blocked = false;
    do
    {
         ret = dup(src);
         fd_blocked = (errno == EINTR ||  errno == EBUSY);
         if (fd_blocked)
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    while (ret < 0);
    return ret;
}
void StdCapture::secure_pipe(int * pipes)
{
    int ret = -1;
    bool fd_blocked = false;
    do
    {
#ifdef _MSC_VER
        ret = pipe(pipes, 65536, O_BINARY);
#else
        ret = pipe(pipes) == -1;
#endif
        fd_blocked = (errno == EINTR ||  errno == EBUSY);
        if (fd_blocked)
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    while (ret < 0);
}
void StdCapture::secure_dup2(int src, int dest)
{
    int ret = -1;
    bool fd_blocked = false;
    do
    {
         ret = dup2(src,dest);
         fd_blocked = (errno == EINTR ||  errno == EBUSY);
         if (fd_blocked)
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    while (ret < 0);
}

void StdCapture::secure_close(int & fd)
{
    int ret = -1;
    bool fd_blocked = false;
    do
    {
         ret = close(fd);
         fd_blocked = (errno == EINTR);
         if (fd_blocked)
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    while (ret < 0);

    fd = -1;
}
生生漫 2024-11-02 21:26:07

由于您的问题被标记为 C 和 C++,因此似乎应该提到,尽管您无法将字符串与标准 C 中的 FILE * 关联起来,但有几个非标准库允许这样做。 glibc 几乎是标准的,因此您可能会非常高兴使用 fmemopen() 请参阅 http://www.gnu.org/s/libc/manual/html_mono/libc.html#String-Streams

Since your question is tagged C as well as C++, it seems appropriate to mention that although you cannot associate a string to a FILE * in standard C, there are several non-standard libraries that allow that. glibc is almost standard, so you may be perfectly happy using fmemopen() See http://www.gnu.org/s/libc/manual/html_mono/libc.html#String-Streams

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文