重定向stdout / stderr到一个字符串

以前有很多关于将stdout / stderr重定向到文件的问题。 有没有一种方法可以将stdout / stderr重定向到字符串?

Prasanth Madhavan asked 2020-01-12T18:27:14Z
5个解决方案
53 votes

是的,您可以将其重定向到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;
};
Björn Pollex answered 2020-01-12T18:27:27Z
33 votes

您可以使用此类:

#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;
        }
        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()检索捕获的输出

rmflow answered 2020-01-12T18:28:00Z
17 votes

为了提供线程安全和跨平台的解决方案,我将rmflow的方法改编为类似的接口。 当此类修改全局文件描述符时,我将其调整为互斥量保护的静态类,该类防止多个实例撞击全局文件描述符。 此外,如果在一个应用程序中使用了许多BeginCapture()和EndCapture()调用,rmflow的答案并不会清除所有使用的文件描述符,这可能导致打开新文件描述符(对于输出流或文件)时出现问题。 该代码已经在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;

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

需要开始捕获时致电IsCapturing()

需要停止捕获时致电IsCapturing()

调用IsCapturing()检索捕获的输出

呼叫IsCapturing()以查看stdout / stderr当前是否已重定向

Sir Digby Chicken Caesar answered 2020-01-12T18:28:48Z
3 votes

由于您的问题被标记为C以及C ++,因此似乎应该提一下,尽管您不能将字符串与标准C中的FILE *关联,但是有多个非标准库允许这样做。 glibc几乎是标准的,因此您可以使用fmemopen()感到非常满意,请参阅[http://www.gnu.org/s/libc/manual/html_mono/libc.html#String-Streams]

William Pursell answered 2020-01-12T18:29:08Z
2 votes

我已经提供了BjörnPollex代码的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;
};
roberto answered 2020-01-12T18:29:28Z
translate from https://stackoverflow.com:/questions/5419356/redirect-stdout-stderr-to-a-string