将stdout / stderrredirect到一个string

以前关于将stdout / stderrredirect到一个文件的问题已经有很多了。 有没有办法将stdout / stderrredirect到一个string?

是的,你可以redirect到一个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; }; 

你可以使用这个类:

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

为了提供一个线程安全和跨平台的解决scheme,我已经将rmflow的方法应用到了一个类似的界面中。 当这个类修改全局文件描述符时,我将其调整为一个互斥守护的静态类,以防止多个实例抖动全局文件描述符。 此外,如果在一个应用程序中使用了许多BeginCapture()和EndCapture()调用,rmflow的答案不会清除所有使用的文件描述符,这可能会导致打开新文件描述符(对于输出stream或文件)。 此代码已在Windows 7/8,Linux,OSX,Android和iOS上进行了testing。

注意:为了使用std :: mutex,你必须针对c ++ 11进行编译。如果你没有/不能使用c ++ 11,你可以完全移除mutex调用(牺牲线程安全性),或者你可以find一个传统的同步机制把事做好。

 #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()一次(捕获之前)删除缓冲到标准输出/标准错误

在需要开始捕获时调用BeginCapture()

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

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

调用IsCapturing()来查看stdout / stderr当前是否被redirect

既然你的问题被标记为C以及C ++,似乎应该提到,尽pipe你不能将一个string与标准C中的FILE *关联起来,但有几个非标准库允许这样做。 glibc几乎是标准的,所以使用fmemopen()可能会非常开心请参阅http://www.gnu.org/s/libc/manual/html_mono/libc.html#String-Streams

我已经提供了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; };