【问题标题】:Discrimination between file and console streams区分文件流和控制台流
【发布时间】:2013-08-06 13:18:31
【问题描述】:

如何判断天气 ostream 是文件还是控制台流。在下面的程序中,我想打印“Hello file!”在写入文件和“你好控制台!”时在写入控制台时。我应该在第 17 行指定什么条件?

#include <fstream>
#include<iostream>
#include <string>
using namespace std;

class A{
public:
        A(string msg):_str(msg){}
        string str()const {return _str;};
private:
        string _str;
};

ostream & operator << (ostream & os, const A & a)
{
        if (os is ofstream) //this is line 17
                os << "Hello file! " << a.str() << endl;
        else
                os << "Hello console! " << a.str() << endl;

        return os;
}

int main()
{
        A a("message");
        ofstream ofile("test.txt");
        if (!ofile)
                cerr << "Unable to open file";
        else
                ofile << a;  // "Hello file"

        cout << a << endl; // "Hello console"
}

【问题讨论】:

  • 答案当然取决于操作系统。例如,在 UNIX 和类 UNIX 系统中,您可以使用isatty(2)(其中2 是与stderr 对应的fd)来检测stderr 是否指向终端。我不知道 Windows 等价物是什么。
  • 正如 Joe Z 所说,这取决于操作系统,Windows 由于 API 压倒性而更加苛刻。检查this 以了解开始。
  • @JoeZ:即使stderr指向终端,也不代表operator&lt;&lt;中的流对象指向控制台。它也可以是文件:您也可以在终端程序中打开任何流!
  • @Nawaz:同意。您还需要获取与ofstream 关联的fd。如果您将问题重述为“我如何将 cout / cerr 与其他 ofstreams 区分开来?”,那么问题会简单得多,并且对操作系统的依赖程度更低,并且可能足以达到目的。
  • @JoeZ:这可能还不够,因为通常您可能想要检测 bash 重定向(例如,为了避免将颜色控制字符放入文件中)。因此,您实际上确实需要检测ostream 是否指向coutcerr 以及stdoutstderr 是TTY 还是文件。当然,为了增加乐趣,如果它是 TTY,您可能需要检查其属性以了解它是否真的支持颜色...

标签: c++ fstream ostream


【解决方案1】:

也许不漂亮,但是

std::streambuf const * coutbuf = std::cout.rdbuf();
std::streambuf const * cerrbuf = std::cerr.rdbuf();

ostream & operator << (ostream & os, const A & a)
{
        std::streambuf const * osbuf = os.rdbuf();

        if ( osbuf == coutbuf || osbuf == cerrbuf )
                os << "Hello console! " << a.str() << endl;
        else
                os << "Hello file! " << a.str() << endl;

        return os;
}

我们可以使用&amp;os == &amp;std::cout,但标准输出可能会被重定向到文件,所以我认为最好使用streambuf 对象。 (请参阅this answer 以更好地了解重定向的工作原理,以及为什么比较 streambuf 可以安全地解决问题!)

【讨论】:

  • 另外,除了std::cout,你还需要在测试中包含std::cerr吗? IE。 (&amp;os == &amp;std::cout || &amp;os == &amp;std::cerr)
  • @Nawaz “我认为这种方法没有任何问题”:当然,除了它不起作用。例如,如果标准输出被重定向到一个文件,他仍然会输出"Hello console!"
  • 还是不行。 myprog &gt; xxx.txt 应该输出 `"Hello file!",例如。 (这就是我所说的重定向,它非常非常普遍。)
  • @Nawaz 不,但这就是重点。否则,您可以只使用全局标志,或传递额外的参数。以这种方式重定向输出非常非常普遍;比std::cout.rdbuf( &amp;aFilebuf ); 之类的东西更常见。
  • 我认为最初的提问者需要权衡解决方案的通用性。如果他们只想涵盖简单、常见的情况,而不真正关心重定向,那么上面的方法看起来很合理。如果他们需要一些健壮的东西,那么你需要为你正在使用的任何操作系统开发 API 并编写特定于操作系统的代码。
【解决方案2】:

您可以(ab)使用tellp(),如果流没有位置,则返回-1

bool isConsoleStream(ostream const& stream)
{
    return stream.tellp() == -1;
}

当然,可能还有其他流为此函数返回-1,因此请谨慎使用。

【讨论】:

  • 我认为@kokan 方法是精确。而这种方法不是,因为它取决于实现!
  • 它没有。如果流不支持定位,tellp 需要返回 -1。控制台流不支持定位,而文件流始终支持。
  • 也可以有其他流,不支持定位。所以这个检查将无法区分std::cout 和其他此类流。
  • @TobiasBrandt 管道不支持定位,但也不是终端。没有获得系统级别的 fd 就没有准确的答案(对于某些系统来说甚至可能没有)。 (虽然不准确,但这可能相当可靠。除非代码是通过不支持搜索的过滤流缓冲区输出的。就像我大部分时间一样。)
  • 是的,你们都说得对。但是为了从控制台中区分文件,这将起作用。
【解决方案3】:

没有便携的方法。在 Unix 下,你可以这样做:

if ( (&os == &std::cout && isatty( STDOUT ))
        || (&os == &std::cerr && isatty( STDERR ))
        || (&os == &std::clog && isatty( STDERR )) ) }
    //  is a terminal...
}

在 Windows 下,isatty 变为 _isatty,我不确定 宏存在(但我怀疑它们确实存在)。

当然,这假设你不做任何事情来混淆它 在你的代码中。类似的东西:

std::ostream s( std::cout.rdbuf() );

例如,或者:

std::cout.rdbuf( &someFileBuf );

甚至:

std::ofstream s( "/dev/tty" );  //  (or "CONS" under Windows).

但在没有实际 fd 的情况下,它几乎是最接近的 来自filebuf

【讨论】:

    【解决方案4】:

    一个是ofstream,另一个是ostream。只要有两种方法。

    #include <iostream>
    #include <string>
    #include <fstream>
    
    class A {
        std::string s;
    public:
        A(const std::string& s) : s(s){}
        std::string str() const {return s;}
    };
    
    
    ostream & operator << (std::ostream & os, const A & a)
    {
        return os << "console: " << a.str() << std::endl;
    }
    
    ofstream & operator << (std::ofstream & os, const A & a)
    {
        return os << "file: " << a.str() << std::endl;
    }
    
    int main()
    {
        A a("hello world");
        std::cout << a << endl;
    }
    

    【讨论】:

    • 我无法测试此 atm 的文件版本。
    • 这是(或可能)完全错误的。 ostream 可以指向文件,ofstream 可以指向交互式设备(当然,std::cout 可以是 std::ofstream)。
    • ostream os = ofstream("some file"); os &lt;&lt; a &lt;&lt; endl; 会将“console: ...”写入文件。
    • 我知道coutextern ostream cout,但我从来不知道它可能是std::ofstream。但是转换ostream os = ofstream("some file"); 的可能性很大。由 OP 决定此解决方案是否适合他当前和未来的系统需求。
    • @TobiasBrandt ostream os = ofstream( "some file" ); 不应该编译。但是,std::filebuf fb( "some file", std::ios_base::out ); std::ostream os( &amp;fb ); 将是您试图提出的问题的完美示例。这也是一个非常非常常见的习惯用法,因为如果给出了文件名,它允许输出到文件,如果没有给出则输出到std::cout
    【解决方案5】:

    这适用于 Visual Studio 2012

    if (typeid(os) == typeid(ofstream)) //this is line 17
    

    ostream 可能不是 ofstream 或控制台,因此您必须小心。

    【讨论】:

    • 这在任何地方都不起作用,因为它不考虑重定向,也不考虑使用filebuf 初始化的ostream
    • 在我的辩护中,它确实在某处工作,即在提问者实际提交的代码中。但我同意其他答案更好。
    【解决方案6】:

    检查 C++ 字符流是否连接到终端/控制台/tty 的功能。

    理想情况下,我们将使用位于 C++ stdio 流(cin、cout、cerr 或 clog)的流缓冲区之下的文件描述符。 但是,无法检索底层文件描述符。 因此,我们使用了这样一个事实,即在程序启动时,stdio 流缓冲区连接到程序的标准输入和输出。

    此功能仅在以下情况下有效:

    1. 启动 C++ stdio 流的流缓冲区不得更改。 因为启动 C++ stdio 流的流缓冲区的地址被用作标识符。 例如,删除它们,然后分配一个新的流缓冲区,该缓冲区与启动 C++ 标准输出流的这些流缓冲区之一具有相同的地址。

    2. 程序启动后程序的标准输出不能改变。 因为 stdio 流缓冲区的 TTY 状态是在程序启动时存储的。 例如,如果在启动时 std. out 连接到一个终端,然后它被程序外部的东西重定向到管道或文件。 [您可以在运行时检索它们,而不是在启动时存储 TTY 状态,但是您必须确保您的程序(以及它使用的所有库)不会更改 stdio 文件描述符(0、1 和 2) .请记住,stdio 流缓冲区很可能使用其他(重复的)文件描述符。]

    代码:

    #include <iostream>
    extern "C" {
    #ifdef _WIN32
    # include <io.h>        // for: _isatty()
    #else
    # include <unistd.h>    // for: isatty()
    #endif
    }
    
    // Stdio file descriptors.
    #ifndef STDIN_FILENO
    # define STDIN_FILENO   0
    # define STDOUT_FILENO  1
    # define STDERR_FILENO  2 
    #endif
    
    
    // Store start-up addresses of C++ stdio stream buffers as identifiers.
    // These addresses differ per process and must be statically linked in.
    // Assume that the stream buffers at these stored addresses
    //  are always connected to their underlaying stdio files.
    static const  streambuf* const StdioBufs[] = {
        std::cin.rdbuf(),  std::cout.rdbuf(),  std::cerr.rdbuf(),  std::clog.rdbuf()
    };
    static const wstreambuf* const StdioWBufs[sizeof(StdioBufs)/sizeof(StdioBufs[0])] = {
        std::wcin.rdbuf(), std::wcout.rdbuf(), std::wcerr.rdbuf(), std::wclog.rdbuf()
    };
    
    // Store start-up terminal/TTY statuses of C++ stdio stream buffers.
    // These statuses differ per process and must be statically linked in.
    // Assume that the statuses don't change during the process life-time.
    static const bool StdioTtys[sizeof(StdioBufs)/sizeof(StdioBufs[0])] = {
    #ifdef _WIN32
        _isatty(STDIN_FILENO), _isatty(STDOUT_FILENO), _isatty(STDERR_FILENO), _isatty(STDERR_FILENO)
    #else
         isatty(STDIN_FILENO),  isatty(STDOUT_FILENO),  isatty(STDERR_FILENO),  isatty(STDERR_FILENO)
    #endif
    };
    
    // Is a Terminal/Console/TTY connected to the C++ stream?
    // Use on C++ stdio chararacter streams: cin, cout, cerr and clog.
    bool isTTY(const ios& strm)
    {
        for(unsigned int i = 0; i < sizeof(StdioBufs)/sizeof(StdioBufs[0]); ++i) {
            if(strm.rdbuf() == StdioBufs[i])
                return StdioTtys[i];
        }
        return false;
    }
    
    // Is a Terminal/Console/TTY connected to the C++ stream?
    // Use on C++ stdio wide-chararacter streams: wcin, wcout, wcerr and wclog.
    bool isTTY(const wios& strm)
    {
        for(unsigned int i = 0; i < sizeof(StdioWBufs)/sizeof(StdioWBufs[0]); ++i) {
            if(strm.rdbuf() == StdioWBufs[i])
                return StdioTtys[i];
        }
        return false;
    }
    

    注意:我只在 Linux 上测试过。

    【讨论】:

      猜你喜欢
      • 2011-12-15
      • 2015-02-28
      • 2011-12-30
      • 1970-01-01
      • 1970-01-01
      • 2017-05-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多