C++ - 将 stdout/stderr 复制到文件,同时保留控制台输出
C++ - Duplicating stdout/stderr to file while keeping console outputs
这里已经提出了非常相似的问题:
Writing to both terminal and file c++
但没有一个好的答案。所有答案都建议使用自定义流或复制 std::cout。但是我需要 stdout/stderr.
的行为
想要的行为:对于每次写入 stdout/stderr 我希望它出现在控制台上并且也被重定向到一个文件。
我正在考虑将标准输出重定向到管道并从那里写入文件和控制台 - 扩展这个答案
有什么更好的方法吗?
EDIT1: 为什么 stdout/stderr 而不是自定义流?
我正在调用(第 3 方)我无法修改的代码,该代码托管在我的进程中。所以我不能使用自定义流(调用的代码已经写入 stderr/stdout)。
EDIT2:
根据 JvO 的建议,我尝试了我的实现 (windows):
HANDLE hPipe = ::CreateNamedPipe(TEXT("\\.\pipe\StdErrPipe"),
PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE,
PIPE_TYPE_BYTE,
//single instance
1,
//default buffer sizes
0,
0,
0,
NULL);
if (hPipe == INVALID_HANDLE_VALUE)
{
//Error out
}
bool fConnected = ConnectNamedPipe(hPipe, NULL) ?
TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
if (!fConnected)
{
//Error out
}
int fd = _open_osfhandle(reinterpret_cast<intptr_t>(hPipe), _O_APPEND | /*_O_WTEXT*/_O_TEXT);
if (dup2(fd, 2) == -1)
{
//Error out
}
不过仍然存在一些问题 - 从管道的另一端我只收到一个垃圾信息(我首先尝试直接发送一些东西 - 效果很好;但是一旦 stderr 被重定向并且我写信给它;管道一遍又一遍地接收相同的非打印字符)
您可以重定向 cout
。一个(不完整的)示例可能如下所示:
#include <fstream>
#include <iostream>
template<class CharT, class Traits = std::char_traits<CharT> >
struct teestream : std::basic_streambuf<CharT, Traits> {
private:
std::basic_streambuf<CharT, Traits>* m_rdbuf1;
std::basic_streambuf<CharT, Traits>* m_rdbuf2;
public:
teestream(std::basic_streambuf<CharT, Traits>* rdbuf1, std::basic_streambuf<CharT, Traits>* rdbuf2)
:m_rdbuf1(rdbuf1)
,m_rdbuf2(rdbuf2)
{}
~teestream() {
m_rdbuf1->pubsync();
m_rdbuf2->pubsync();
}
protected:
int_type overflow(int_type ch = Traits::eof()) override
{
int_type result = m_rdbuf1->sputc(ch);
if (result != Traits::eof())
{
result = m_rdbuf2->sputc(ch);
}
return result;
}
virtual int sync() override
{
int result = m_rdbuf1->pubsync();
if (result == 0)
{
result = m_rdbuf2->pubsync();
}
return result;
}
};
typedef teestream<char, std::char_traits<char>> basic_teestream;
int main()
{
std::ofstream fout("out.txt");
std::streambuf *foutbuf = fout.rdbuf(); //Get streambuf for output stream
std::streambuf *coutbuf = std::cout.rdbuf(); //Get streambuf for cout
std::streambuf *teebuf = new basic_teestream(coutbuf, foutbuf); //create new teebuf
std::cout.rdbuf(teebuf);//Redirect cout
std::cout << "hello" << std::endl;
std::cout.rdbuf(coutbuf); //Restore cout
delete teebuf; //Destroy teebuf
}
正如您在这里看到的,cout
使用的 streambuf 被一个控制 streambuf 本身以及 ofstream 的 streambuf 的流缓冲取代。
该代码很可能有很多缺陷并且不完整,但您应该明白了。
来源:
How can I compose output streams, so output goes multiple places at once?
http://en.cppreference.com/w/cpp/io/basic_streambuf/pubsync
Implementing std::basic_streambuf subclass for manipulating input
您可以 'hijack' stdout 和 stderr 通过替换指针; stdout 和 stderr 只不过是 FILE *
。我建议你先打开一个管道对,然后使用 fdopen()
创建一个新的 FILE * 与管道的发送端相关联,然后将 stdout 指向你的新文件。使用管道的接收端提取写入 'old' 标准输出的内容。
伪代码:
int fd[2];
FILE *old_stdout, *new_stdout;
pipe(fd);
new_stdout = fdopen(fd[1], "w");
old_stdout = stdout;
stdout = new_stdout;
现在,您从 fd[0] 读取的所有内容都可以写入文件,old_stdout,等等
这里已经提出了非常相似的问题: Writing to both terminal and file c++
但没有一个好的答案。所有答案都建议使用自定义流或复制 std::cout。但是我需要 stdout/stderr.
的行为想要的行为:对于每次写入 stdout/stderr 我希望它出现在控制台上并且也被重定向到一个文件。
我正在考虑将标准输出重定向到管道并从那里写入文件和控制台 - 扩展这个答案
有什么更好的方法吗?
EDIT1: 为什么 stdout/stderr 而不是自定义流?
我正在调用(第 3 方)我无法修改的代码,该代码托管在我的进程中。所以我不能使用自定义流(调用的代码已经写入 stderr/stdout)。
EDIT2:
根据 JvO 的建议,我尝试了我的实现 (windows):
HANDLE hPipe = ::CreateNamedPipe(TEXT("\\.\pipe\StdErrPipe"),
PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE,
PIPE_TYPE_BYTE,
//single instance
1,
//default buffer sizes
0,
0,
0,
NULL);
if (hPipe == INVALID_HANDLE_VALUE)
{
//Error out
}
bool fConnected = ConnectNamedPipe(hPipe, NULL) ?
TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
if (!fConnected)
{
//Error out
}
int fd = _open_osfhandle(reinterpret_cast<intptr_t>(hPipe), _O_APPEND | /*_O_WTEXT*/_O_TEXT);
if (dup2(fd, 2) == -1)
{
//Error out
}
不过仍然存在一些问题 - 从管道的另一端我只收到一个垃圾信息(我首先尝试直接发送一些东西 - 效果很好;但是一旦 stderr 被重定向并且我写信给它;管道一遍又一遍地接收相同的非打印字符)
您可以重定向 cout
。一个(不完整的)示例可能如下所示:
#include <fstream>
#include <iostream>
template<class CharT, class Traits = std::char_traits<CharT> >
struct teestream : std::basic_streambuf<CharT, Traits> {
private:
std::basic_streambuf<CharT, Traits>* m_rdbuf1;
std::basic_streambuf<CharT, Traits>* m_rdbuf2;
public:
teestream(std::basic_streambuf<CharT, Traits>* rdbuf1, std::basic_streambuf<CharT, Traits>* rdbuf2)
:m_rdbuf1(rdbuf1)
,m_rdbuf2(rdbuf2)
{}
~teestream() {
m_rdbuf1->pubsync();
m_rdbuf2->pubsync();
}
protected:
int_type overflow(int_type ch = Traits::eof()) override
{
int_type result = m_rdbuf1->sputc(ch);
if (result != Traits::eof())
{
result = m_rdbuf2->sputc(ch);
}
return result;
}
virtual int sync() override
{
int result = m_rdbuf1->pubsync();
if (result == 0)
{
result = m_rdbuf2->pubsync();
}
return result;
}
};
typedef teestream<char, std::char_traits<char>> basic_teestream;
int main()
{
std::ofstream fout("out.txt");
std::streambuf *foutbuf = fout.rdbuf(); //Get streambuf for output stream
std::streambuf *coutbuf = std::cout.rdbuf(); //Get streambuf for cout
std::streambuf *teebuf = new basic_teestream(coutbuf, foutbuf); //create new teebuf
std::cout.rdbuf(teebuf);//Redirect cout
std::cout << "hello" << std::endl;
std::cout.rdbuf(coutbuf); //Restore cout
delete teebuf; //Destroy teebuf
}
正如您在这里看到的,cout
使用的 streambuf 被一个控制 streambuf 本身以及 ofstream 的 streambuf 的流缓冲取代。
该代码很可能有很多缺陷并且不完整,但您应该明白了。
来源:
How can I compose output streams, so output goes multiple places at once?
http://en.cppreference.com/w/cpp/io/basic_streambuf/pubsync
Implementing std::basic_streambuf subclass for manipulating input
您可以 'hijack' stdout 和 stderr 通过替换指针; stdout 和 stderr 只不过是 FILE *
。我建议你先打开一个管道对,然后使用 fdopen()
创建一个新的 FILE * 与管道的发送端相关联,然后将 stdout 指向你的新文件。使用管道的接收端提取写入 'old' 标准输出的内容。
伪代码:
int fd[2];
FILE *old_stdout, *new_stdout;
pipe(fd);
new_stdout = fdopen(fd[1], "w");
old_stdout = stdout;
stdout = new_stdout;
现在,您从 fd[0] 读取的所有内容都可以写入文件,old_stdout,等等