redirect stdout/stderr to a string

C++CStdoutStderr

C++ Problem Overview


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

C++ Solutions


Solution 1 - C++

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 - C++

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 - C++

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 - C++

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 - C++

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

Solution 6 - C++

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPrasanth MadhavanView Question on Stackoverflow
Solution 1 - C++Björn PollexView Answer on Stackoverflow
Solution 2 - C++rmflowView Answer on Stackoverflow
Solution 3 - C++Sir Digby Chicken CaesarView Answer on Stackoverflow
Solution 4 - C++robertoView Answer on Stackoverflow
Solution 5 - C++William PursellView Answer on Stackoverflow
Solution 6 - C++StaszekView Answer on Stackoverflow