redirect stdout/stderr to a string

69,978

Solution 1

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;
};

Solution 2

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

Solution 3

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

Solution 4

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;
};

Solution 5

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

Share:
69,978
Prasanth Madhavan
Author by

Prasanth Madhavan

Software engineer who likes to program for fun......

Updated on July 05, 2022

Comments

  • Prasanth Madhavan
    Prasanth Madhavan almost 2 years

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