C++ 将 stdout/stderr 重定向到字符串
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5419356/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
redirect stdout/stderr to a string
提问by Prasanth Madhavan
there has been many previous questions about redirecting stdout/stderr to a file. is there a way to redirect stdout/stderr to a string?
之前有很多关于将 stdout/stderr 重定向到文件的问题。有没有办法将 stdout/stderr 重定向到字符串?
回答by Bj?rn Pollex
Yes, you can redirect it to an std::stringstream
:
是的,您可以将其重定向到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;
};
回答by rmflow
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;
}
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
打电话BeginCapture()
时您需要开始捕捉
通话EndCapture()
时,您需要停止捕获
调用GetCapture()
来检索捕获输出
回答by Sir Digby Chicken Caesar
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.
为了提供线程安全和跨平台的解决方案,我将 rmflow 的方法改编成类似的界面。由于这个类修改了全局文件描述符,我将它修改为一个互斥保护的静态类,以防止多个实例颠簸全局文件描述符。此外,如果在一个应用程序中使用了许多 BeginCapture() 和 EndCapture() 调用,则 rmflow 的答案不会清除所有使用的文件描述符,这可能会导致打开新文件描述符(用于输出流或文件)出现问题。此代码已在 Windows 7/8、Linux、OSX、Android 和 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.
注意:为了使用 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;
call Init()
once (before capture) to remove buffering to stdout / stderr
调用Init()
一次(在捕获之前)以删除对 stdout / stderr 的缓冲
call BeginCapture()
when you need to start capture
BeginCapture()
需要开始捕获时调用
call EndCapture()
when you need to stop capture
EndCapture()
需要停止捕获时调用
call GetCapture()
to retrieve captured output
调用GetCapture()
以检索捕获的输出
call IsCapturing()
to see if stdout/stderr is currently redirected
调用IsCapturing()
以查看当前是否重定向了 stdout/stderr
回答by William Pursell
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
由于您的问题被标记为 C 和 C++,因此尽管您不能在标准 C 中将字符串与 FILE * 相关联,但有几个非标准库允许这样做。glibc 几乎是标准的,所以你可能会很高兴使用 fmemopen() 参见http://www.gnu.org/s/libc/manual/html_mono/libc.html#String-Streams
回答by roberto
i've furnished a qt osx ready variation from Bj?rn Pollex code
我已经从 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;
};