10

How to determine weather ostream is a file or a console stream. In the following program I want to print "Hello file!" while writing to a file and "Hello console!" while writing to console. What condition should I specify at line 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"
}
cpp
  • 3,743
  • 3
  • 24
  • 38
  • 2
    The answer is certainly OS dependent. In UNIX and UNIX-like systems, you can, for example, use `isatty(2)` (where `2` is the fd corresponding to `stderr`) to detect if `stderr` points to a terminal. I have no idea what the Windows equivalent would be. – Joe Z Aug 06 '13 at 13:23
  • As Joe Z stated, this is OS dependent, Windows is a Little more harsh because of ist overwhelming API. Check [this](http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx) for a start. – bash.d Aug 06 '13 at 13:27
  • @JoeZ: Even if `stderr` points to a terminal, it doesn't mean that the stream object in `operator<<` points to console. It could be file as well: you can open ANY stream in a terminal program also! – Nawaz Aug 06 '13 at 13:27
  • @Nawaz: Agreed. You also need to get the `fd` associated with the `ofstream`. If you restate the problem as "How do I distinguish `cout` / `cerr` from other `ofstream`s?", then the problem is much simpler and less OS dependent, and maybe sufficient for the purpose. – Joe Z Aug 06 '13 at 13:29
  • @JoeZ: It might not be sufficient since typically you might want to detect a bash redirection (to avoid putting color control characters in a file, for example). So you do actually need both to detect whether the `ostream` points to either `cout` or `cerr` AND whether `stdout` or `stderr` is a TTY or a file. And of course, for adding fun, if it is a TTY you might want to check its properties to know if it actually supports colors... – Matthieu M. Aug 06 '13 at 14:21
  • @MatthieuM.: I mean "sufficient for the purposes of the person asking." The general problem of detecting console vs. file is very OS specific. But, the actual problem the person asking is trying to solve may not be that general. We won't know unless they tell us. It may be sufficient for their purposes just to detect whether the stream is `cout`/`cerr`, or they may really want to know if they're directed to a TTY. – Joe Z Aug 06 '13 at 14:50

6 Answers6

4

Maybe not pretty, but

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

We could use &os == &std::cout, but the Standard output might be redirected to file, so I think it is better to use the streambuf object instead. (See this answer for better understanding as to how the redirection works, and why comparing streambuf solves the problem safely! )

Community
  • 1
  • 1
kokan
  • 64
  • 3
  • And also, do you need to include `std::cerr` in the test in addition to `std::cout`? ie. `(&os == &std::cout || &os == &std::cerr)` – Joe Z Aug 06 '13 at 13:30
  • I don't see any problem with this approach. Therefore, +1. – Nawaz Aug 06 '13 at 13:33
  • 7
    @Nawaz "I don't see any problem with this approach": except, of course, that it doesn't work. If standard out has been redirected to a file, for example, he will still output `"Hello console!"`. – James Kanze Aug 06 '13 at 13:40
  • @JamesKanze: hehe. like [this](http://stackoverflow.com/questions/10150468/how-to-redirect-cin-and-cout-to-files)? I hope the OP does not do that. Also, this can be fixed: store the stdout's buffer in a global *hidden* variable and compare that instead! – Nawaz Aug 06 '13 at 13:42
  • @JamesKanze: I fixed that. See now. :-) – Nawaz Aug 06 '13 at 13:49
  • There is also `clog` and all the `w...` variants. Although they probably use the same stream buffers underneath. – Tobias Brandt Aug 06 '13 at 13:57
  • 2
    It still doesn't work. `myprog > xxx.txt` should output `"Hello file!", for example. (That's what I meant by redirection, and it's very, very common.) – James Kanze Aug 06 '13 at 13:57
  • @TobiasBrandt They can't use the same streambuf, because in one case, the type is `std::streambuf`, and in the other `std::wstreambuf`. – James Kanze Aug 06 '13 at 13:58
  • @JamesKanze: That is a different thing altogether. `myprog > xxx.txt` is NOT controlled by `myprog` anymore, not at least by `operator<<`. – Nawaz Aug 06 '13 at 13:58
  • 3
    @Nawaz No, but that's the whole point. Otherwise, you can just use a global flag, or pass an extra argument. And it's very, very common to redirect output this way; far more common than things like `std::cout.rdbuf( &aFilebuf );`. – James Kanze Aug 06 '13 at 14:08
  • @TobiasBrandt Yes and no. An `std::ostream` cannot point to one of them, so you don't have to worry about them. The real problem is that `std::cout` and `std::cerr` can output to files, in case of redirection, and that `std::ofstream` can output to the terminal window, e.g. if the file name was `"/dev/tty"` (under Unix). The test simply doesn't work. – James Kanze Aug 06 '13 at 14:10
  • @JamesKanze: The whole point is in the question's title which says *"Discrimination between file and console **streams**"* and this solution does that. :-) – Nawaz Aug 06 '13 at 14:16
  • @Nawaz: While this is the verbatim question, it may not be what the OP expects. Detection of TTY vs file is meaningless in itself, in general it is used to know whether to put formatting characters in the stream (bold/normal, colors, ...) which have meaning in TTY (OS dependent) but clutter a file. – Matthieu M. Aug 06 '13 at 14:24
  • @Nawaz And an `std::ofstream` opened with `"/dev/tty"` is a console stream, an `std::istream` initialized with a `std::filebuf` is a file stream (or might be---it might also be a named pipe stream). And `std::cout` can be a console stream, a file stream, a pipe stream, or a lot of other things. So what's your point. – James Kanze Aug 06 '13 at 14:30
  • 2
    I think the original question-asker needs to weigh in on how general the solution needs to be. The approach above looks reasonable if they just want to cover simple, common cases, and don't really care about redirection. If they need something robust, then you need to bust out the API for whatever OS you're using and write OS-specific code. – Joe Z Aug 06 '13 at 14:54
  • @JoeZ "and don't really care about redirection": can you imagine a scenario where that would be the case? (At least under Windows and Unix. On an IBM mainframe, you can generally assume that everything is a file, and be done with it.) – James Kanze Aug 06 '13 at 15:09
  • @JamesKanze: In my experience, redirection doesn't really seem all that popular in Windows. Like I said, we're projecting assumptions about the use case. We need more information. – Joe Z Aug 08 '13 at 15:02
  • @JoeZ Console windows don't seem that popular in general in Windows. Most "console applications" are actually invoked in a `.bat` file, where redirection is frequent. (Of course, there _are_ Windows users like myself, you have installed CygWin and use bash. And we redirect like crazy.) – James Kanze Aug 08 '13 at 15:13
  • @JoeZ Not that the frequency matters. A non-interactive program which writes to `std::cout` is incorrect if it cannot output correctly to a file. (But of course, he's changing the output when it is to a file, so it's hard to say what he's up to.) – James Kanze Aug 08 '13 at 15:15
4

You could (ab)use tellp(), which returns -1 if the stream does not have a position:

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

Of course, there could be other streams that return -1 for this function, so use with caution.

Tobias Brandt
  • 3,393
  • 18
  • 28
  • I think @kokan approach is *precise*. and this approach is not, as it depends on implementation! – Nawaz Aug 06 '13 at 13:38
  • It does not. tellp is required to return -1 if the stream doesn not support positioning. Console streams cannot support positioning and file streams always do. – Tobias Brandt Aug 06 '13 at 13:52
  • There can be other streams also, not supporting positioning. So this check will not be able to distinguish between `std::cout` and other such streams. – Nawaz Aug 06 '13 at 13:54
  • @TobiasBrandt And pipes don't support positioning, but aren't terminals either. There is no precise answer without getting the system level fd (and perhaps not even then for some systems). (But while not precise, this is probably fairly reliable. Unless the code is outputting through a filtering streambuf which doesn't support seeking. As I do much of the time.) – James Kanze Aug 06 '13 at 13:55
  • Yes, you're both correct. But for distinguishing files from the console, this will work. – Tobias Brandt Aug 06 '13 at 13:56
  • @TobiasBrandt Well, it's certainly better than any of the other "portable" solutions. And without access to the system level fd, I don't think that there is a portable solution. I'm used to programming at the system level, so I'd use the `isatty`/`_isatty` solution I proposed in my answer, but this is just as good: it will fail sometimes, but so will mine, just in different cases. – James Kanze Aug 06 '13 at 14:00
  • Of course, the real problem is something like `myprog | someOtherProg`. What should he output here (since whether the output ends up in in the terminal window, in a file, or is lost somewhere in `someOtherProg` is unknown). My solution using `isatty` would say not a terminal, your solution would say not a file; if your function were precise, it would return `maybe`, but C++ doesn't support that yet:-). (Although returning a `Fallible` would be a good idea, _if_ there were some means of getting the information.) – James Kanze Aug 06 '13 at 14:14
  • To my surprise, `std::cout.tellp()` appears to return zero, instead of `-1`, when using MSVC. As I tried at https://godbolt.org/z/5TcEY1586 (trying both v19.14 and v19.33) by doing const int pos = std::cout.tellp(); std::cout << "pos = " << pos; It may be a compiler bug (I don't know), but it would mean that `tellp()` cannot be used to discriminate between file and console streams, unfortunately. – Niels Dekker Nov 16 '22 at 21:08
3

There is no portable means. Under Unix, you can do:

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

Under Windows, the isatty becomes _isatty, and I'm not sure that the macros exist (but I suspect that they do).

Of course, this supposes that you don't do things to confuse it in your code. Something like:

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

for example, or:

std::cout.rdbuf( &someFileBuf );

Or even:

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

But it's about as close as you can get without the actual fd from the filebuf.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
2

One is a ofstream and the other is a ostream. Just have two methods.

#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;
}
andre
  • 7,018
  • 4
  • 43
  • 75
  • I can't test the File version for this atm. – andre Aug 06 '13 at 13:46
  • 1
    This is (or maybe) simply false. An `ostream` can point to a file, and an `ofstream` can point to an interactive device (and of course, `std::cout` could be an `std::ofstream`). – James Kanze Aug 06 '13 at 13:52
  • `ostream os = ofstream("some file"); os << a << endl;` will write "console: ..." to a file. – Tobias Brandt Aug 06 '13 at 13:54
  • I know `cout` is `extern ostream cout` but I never knew it could be a `std::ofstream`. But the conversion `ostream os = ofstream("some file");` is very likely. Its up to OP to determine if this solution would fit his current and future system needs. – andre Aug 06 '13 at 14:00
  • @TobiasBrandt `ostream os = ofstream( "some file" );` shouldn't compile. `std::filebuf fb( "some file", std::ios_base::out ); std::ostream os( &fb );` will, however, and is a perfect example of the problem you are trying to raise. It's also a very, very common idiom, since it allows outputting to a file, if the filename is given, and outputting to `std::cout` if it isn't. – James Kanze Aug 06 '13 at 14:03
  • @andre Well, the standard _does_ say that they have type `std::ostream`, but this is probably an example of over specification; historically, on most implementations, they had some type derived from `std::ostream`. – James Kanze Aug 06 '13 at 14:04
  • And of course, using two functions fails lamentably if the user writes something like `somestream << "Message: " << a;`. – James Kanze Aug 06 '13 at 14:15
0

This works on Visual Studio 2012

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

But an ostream could be something that isn't an ofstream or the console so you'd have to be careful.

Peter Hull
  • 6,683
  • 4
  • 39
  • 48
  • 2
    This doesn't work anywhere, because it doesn't take into account redirection, nor `ostream` which are initialized with a `filebuf`. – James Kanze Aug 06 '13 at 13:50
  • In my defence, it _does_ work somewhere, i.e. in the code actually submitted by the questioner. But I agree that other answers are better. – Peter Hull Aug 06 '13 at 14:56
0

Function to check if a C++ character stream is connected to a terminal/console/tty.

Ideally, we would use the file descriptor under-laying the stream buffer of the C++ stdio stream (cin, cout, cerr or clog). However, there is no way to retrieve the under-laying file descriptor. So, we use the fact that at program start-up the stdio stream buffers are connected to the program's standard input and output.

This function only works under the following conditions:

  1. The stream buffers of the start-up C++ stdio streams must not change. Because the addresses of the stream buffers of the start-up C++ stdio streams are used as identifiers. For instance by deleting them and then allocating a new stream buffer that has the same address as one of these stream buffers of the start-up C++ stdio streams.

  2. The program's stdio must not change after program start-up. Because the TTY statuses of the stdio stream buffers are stored at program start-up. For instance if at start-up the std. out is connected to a terminal and later it is redirected to a pipe or file by something external to the program. [Instead of storing the TTY statuses at start-up you could retrieve them at run-time, but then you must make sure that your program (and all libraries it uses) does not change the stdio file descriptors (0, 1 and 2). Rember that the stdio stream buffers most likely use other (duplicate) file descriptors.]

Code:

#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;
}

Note: I've only tested it on Linux.

Hacklin
  • 1
  • 1
  • Note that in c++ we now can do `std::size()` to get the size of the array without having to do a `sizeof() / sizeof([0])`. It does the same thing, it's shorter to write and more concise to read... – Alexis Wilke Jul 09 '22 at 23:43