之前有很多关于将 stdout/stderr 重定向到文件的问题。有没有办法将 stdout/stderr 重定向到字符串?
是的,您可以将其重定向到
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;
}
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;
};
需要开始捕捉时拨打
BeginCapture()
EndCapture()
GetCapture()
检索捕获的输出
为了提供线程安全和跨平台的解决方案,我将 rmflow 的方法改编为类似的接口。由于此类修改全局文件描述符,我将其改编为互斥锁保护的静态类,以防止多个实例对全局文件描述符造成影响。此外,rmflow 的答案不会清除所有已使用的文件描述符,如果在一个应用程序中使用许多 BeginCapture() 和 EndCapture() 调用,这可能会导致打开新文件描述符(对于输出流或文件)时出现问题。此代码已在 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;
调用
Init()
一次(在捕获之前)以删除对 stdout / stderr 的缓冲
需要开始捕捉时拨打
BeginCapture()
需要停止拍摄时拨打
EndCapture()
调用
GetCapture()
检索捕获的输出
调用
IsCapturing()
查看 stdout/stderr 当前是否已重定向
我已经提供了 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;
};
我修改了
Sir Digby Chicken Caesar
中的类,使其不是静态的并且可以在单元测试中轻松使用。它在 Windows 上适用于 gcc (g++) 编译,但我不能保证它是 100% 正确的,如果不是请留下评论。
创建 StdCapture 类的对象,只需调用
BeginCapture()
即可开始捕获,并在结束时调用 EndCapture()
。 Init()
中的代码已移至构造函数。一次只能有一个这样的对象在工作。
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;
}
由于您的问题被标记为 C 和 C++,因此似乎应该提到,尽管您无法将字符串与标准 C 中的 FILE * 关联起来,但有几个非标准库允许这样做。 glibc 几乎是标准的,因此您可能会非常高兴使用 fmemopen() 请参阅 http://www.gnu.org/s/libc/manual/html_mono/libc.html#String-Streams