【问题标题】:redirect stdout/stderr to a string将 stdout/stderr 重定向到字符串
【发布时间】:2011-07-22 02:52:03
【问题描述】:

以前有很多关于将 stdout/stderr 重定向到文件的问题。有没有办法将 stdout/stderr 重定向到字符串?

【问题讨论】:

    标签: c++ c stdout stderr


    【解决方案1】:

    是的,您可以将其重定向到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;
    };
    

    【讨论】:

    • 用于标准输出。标准错误呢??我必须重定向 cerr 吗?
    • 是的,您也可以这样做,但使用std::cerr
    • 对不起,我的C(或C++)有点生锈了,但是当应用程序的其他部分执行fprintf(stderr, ...)fprintf(stdout, ...)或@时,这无济于事987654329@,不是吗?此外,它对write(1, ...)write(2, ...) 没有帮助,即使没有被问到:-)
    • @Christian:不,它没有。此解决方案仅适用于 C++ iostream。
    • @MinimusHeximus:这就是重点——输出被重定向到一个字符串,而不是屏幕上。要再次打印到屏幕,您必须将原始缓冲区恢复为cout
    【解决方案2】:

    你可以使用这个类:

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

    【讨论】:

    • 我对管道不太熟悉,但是内核中管道的缓冲区有多大?换句话说,在 write() 块之前,如果不调用 read(),您可以将多少字节传递给 write()?
    • +1 用于reallY 复杂的小问题解决方案! :)
    • +1 用于处理所有 stdout/stderr 的解决方案(与仅处理 cout/cerr 的公认解决方案不同)。
    • O_BINARY 和其他一些标识符对我的编译器来说是未知的。
    • @MinimusHeximus:我相信这是为 MSVC 编译器编写的。 _pipe() 函数记录在 MSDN 中。
    【解决方案3】:

    由于您的问题被标记为 C 和 C++,因此似乎应该提及虽然您不能将字符串与标准 C 中的 FILE * 相关联,但有几个非标准库允许这样做。 glibc 几乎是标准的,所以你可能会非常高兴使用 fmemopen() 见http://www.gnu.org/s/libc/manual/html_mono/libc.html#String-Streams

    【讨论】:

    • 不需要;只需重新分配stderr,如stderr = open_memstream(...);
    • 注意:需要fclosestderr,也需要处理错误。
    • stderr 不能用作左值进行赋值。它是一个#define。
    【解决方案4】:

    为了提供线程安全和跨平台的解决方案,我将 rmflow 的方法改编为类似的界面。当这个类修改全局文件描述符时,我将它调整为一个互斥锁保护的静态类,以防止多个实例破坏全局文件描述符。此外,如果在一个应用程序中使用了许多 BeginCapture() 和 EndCapture() 调用,rmflow 的答案不会清理所有使用的文件描述符,这可能会导致打开新文件描述符(用于输出流或文件)的问题。此代码已在 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() 一次(在捕获之前)以删除对标准输出/标准错误的缓冲

    当您需要开始捕获时,请致电BeginCapture()

    当您需要停止捕获时,请致电EndCapture()

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

    调用IsCapturing()查看stdout/stderr当前是否被重定向

    【讨论】:

    • 此答案中列出的代码完全可以免费供任何人使用。如有必要,我可以附上 BSD 许可证。
    • 迪格比。凉爽的!你能评论那个元线程吗?那里的 OP 还没有足够的代表在这里参与。
    • 完成。我可以在明天使用台式电脑时附上许可证。
    • 这甚至不能编译 - 缺少标题(chronothread)以...开始
    • 哎哟。这段代码在很多方面都存在缺陷。首先,线程安全背后的思想是有缺陷的。其次,静态数据成员和非静态成员函数的奇怪混合不起作用。第三,由于未定义的符号,它无法编译。第四,一开始不需要关闭缓冲,刷新缓冲区就足够了,而且停止捕获后没有代码可以重新打开缓冲。第五是捕获数据的处理方式:m_captured 仅在结束捕获时被清除,而不是通过 GetCapture(),这将是显而易见的地方。
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

      我从Sir Digby Chicken Caesar 修改了类,使其不是静态的,可以在单元测试中轻松使用。它适用于由 gcc (g++) 编译的 Windows,但我不能保证它是 100% 正确的,如果不是,请留下 cmets。

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

      【讨论】:

      猜你喜欢
      • 2014-07-22
      • 1970-01-01
      • 2022-01-18
      • 2012-08-07
      • 2012-07-14
      • 1970-01-01
      • 2011-12-28
      • 1970-01-01
      • 2011-12-27
      相关资源
      最近更新 更多