Discrimination between file and console streams
Asked Answered
E

6

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"
}
Eclosion answered 6/8, 2013 at 13:18 Comment(6)
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.Santiagosantillan
As Joe Z stated, this is OS dependent, Windows is a Little more harsh because of ist overwhelming API. Check this for a start.Larine
@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!Marleah
@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 ofstreams?", then the problem is much simpler and less OS dependent, and maybe sufficient for the purpose.Santiagosantillan
@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...Golda
@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.Santiagosantillan
L
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! )

Lora answered 6/8, 2013 at 13:28 Comment(19)
And also, do you need to include std::cerr in the test in addition to std::cout? ie. (&os == &std::cout || &os == &std::cerr)Santiagosantillan
I don't see any problem with this approach. Therefore, +1.Marleah
@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!".Anselmi
@JamesKanze: hehe. like this? 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!Marleah
@JamesKanze: I fixed that. See now. :-)Marleah
There is also clog and all the w... variants. Although they probably use the same stream buffers underneath.Amaras
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.)Anselmi
@TobiasBrandt They can't use the same streambuf, because in one case, the type is std::streambuf, and in the other std::wstreambuf.Anselmi
@JamesKanze: That is a different thing altogether. myprog > xxx.txt is NOT controlled by myprog anymore, not at least by operator<<.Marleah
@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 );.Anselmi
@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.Anselmi
@JamesKanze: The whole point is in the question's title which says "Discrimination between file and console streams" and this solution does that. :-)Marleah
@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.Golda
@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.Anselmi
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.Santiagosantillan
@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.)Anselmi
@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.Santiagosantillan
@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.)Anselmi
@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.)Anselmi
A
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.

Amaras answered 6/8, 2013 at 13:34 Comment(8)
I think @Lora approach is precise. and this approach is not, as it depends on implementation!Marleah
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.Amaras
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.Marleah
@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.)Anselmi
Yes, you're both correct. But for distinguishing files from the console, this will work.Amaras
@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.Anselmi
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<bool> would be a good idea, if there were some means of getting the information.)Anselmi
To my surprise, std::cout.tellp() appears to return zero, instead of -1, when using MSVC. As I tried at 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.Seeker
A
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.

Anselmi answered 6/8, 2013 at 13:50 Comment(0)
O
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;
}
Om answered 6/8, 2013 at 13:43 Comment(7)
I can't test the File version for this atm.Om
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).Anselmi
ostream os = ofstream("some file"); os << a << endl; will write "console: ..." to a file.Amaras
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.Om
@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.Anselmi
@Om 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.Anselmi
And of course, using two functions fails lamentably if the user writes something like somestream << "Message: " << a;.Anselmi
B
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.

Bareheaded answered 6/8, 2013 at 13:44 Comment(2)
This doesn't work anywhere, because it doesn't take into account redirection, nor ostream which are initialized with a filebuf.Anselmi
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.Bareheaded
V
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.

Valletta answered 2/8, 2018 at 18:29 Comment(1)
Note that in c++ we now can do std::size(<array>) 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...Dray

© 2022 - 2024 — McMap. All rights reserved.