Detect if stdin is a terminal or pipe?

C++CQtPipeStdin

C++ Problem Overview


When I execute "python" from the terminal with no arguments it brings up the Python interactive shell.

When I execute "cat | python" from the terminal it doesn't launch the interactive mode. Somehow, without getting any input, it has detected that it is connected to a pipe.

How would I do a similar detection in C or C++ or Qt?

C++ Solutions


Solution 1 - C++

Use isatty:

#include <stdio.h>
#include <io.h>
...    
if (isatty(fileno(stdin)))
    printf( "stdin is a terminal\n" );
else
    printf( "stdin is a file or a pipe\n");

(On windows they're prefixed with underscores: _isatty, _fileno)

Solution 2 - C++

Summary

For many use cases the POSIX function isatty() is all what it is needed to detect if stdin is connected to a terminal. A minimal example:

#include <unistd.h>
#include <stdio.h>

int main(int argc, char **argv)
{
  if (isatty(fileno(stdin)))
    puts("stdin is connected to a terminal");
  else
    puts("stdin is NOT connected to a terminal");
  return 0;
}

The following section compares different methods that can be used if different degrees of interactivity have to be tested.

Methods in Detail

There are several methods to detect if a program is running interactively. Following table shows an overview:

cmd\method             ctermid    open   isatty   fstat
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
./test                 /dev/tty   OK     YES      S_ISCHR
./test < test.cc       /dev/tty   OK     NO       S_ISREG
cat test.cc | ./test   /dev/tty   OK     NO       S_ISFIFO
echo ./test | at now   /dev/tty   FAIL   NO       S_ISREG

The results are from a Ubuntu Linux 11.04 system using the following program:

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
int main() {
  char tty[L_ctermid+1];
  ctermid(tty);
  printf("ID: %s\n", tty);
  int fd = open(tty, O_RDONLY);
  if (fd < 0) perror("Could not open terminal");
  else {
    printf("Opened terminal\n");
    struct termios term;
    int r = tcgetattr(fd, &term);
    if (r < 0) perror("Could not get attributes");
    else printf("Got attributes\n");
  }
  if (isatty(fileno(stdin))) printf("Is a terminal\n");
  else printf("Is not a terminal\n");
  struct stat stats;
  int r = fstat(fileno(stdin), &stats);
  if (r < 0) perror("fstat failed");
  else {
    if (S_ISCHR(stats.st_mode)) printf("S_ISCHR\n");
    else if (S_ISFIFO(stats.st_mode)) printf("S_ISFIFO\n");
    else if (S_ISREG(stats.st_mode)) printf("S_ISREG\n");
    else printf("unknown stat mode\n");
  }
  return 0;
}

Terminal device

If the interactive session needs certain capabilities, you can open the terminal device and (temporarily) set terminal attributes you need via tcsetattr().

Python Example

The Python code that decides whether the interpreter runs interactively uses isatty(). The Function PyRun_AnyFileExFlags()

/* Parse input from a file and execute it */

int
PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,
                     PyCompilerFlags *flags)
{
    if (filename == NULL)
        filename = "???";
    if (Py_FdIsInteractive(fp, filename)) {
        int err = PyRun_InteractiveLoopFlags(fp, filename, flags);

calls Py_FdIsInteractive()

/*
 * The file descriptor fd is considered ``interactive'' if either
 *   a) isatty(fd) is TRUE, or
 *   b) the -i flag was given, and the filename associated with
 *      the descriptor is NULL or "<stdin>" or "???".
 */
int
Py_FdIsInteractive(FILE *fp, const char *filename)
{
    if (isatty((int)fileno(fp)))
        return 1;

which calls isatty().

Conclusion

There are different degrees of interactivity. For checking if stdin is connected to a pipe/file or a real terminal isatty() is a natural method to do that.

Solution 3 - C++

Probably they are checking the type of file that "stdin" is with fstat, something like this:

struct stat stats;
fstat(0, &stats);
if (S_ISCHR(stats.st_mode)) {
    // Looks like a tty, so we're in interactive mode.
} else if (S_ISFIFO(stats.st_mode)) {
    // Looks like a pipe, so we're in non-interactive mode.
}

Of course Python is open source, so you can just look at what they do and know for sure:

<http://www.python.org/ftp/python/2.6.2/Python-2.6.2.tar.bz2>

Solution 4 - C++

On Windows you can use GetFileType.

HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
DWORD type = GetFileType(hIn);
switch (type) {
case FILE_TYPE_CHAR: 
    // it's from a character device, almost certainly the console
case FILE_TYPE_DISK:
    // redirected from a file
case FILE_TYPE_PIPE:
    // piped from another program, a la "echo hello | myprog"
case FILE_TYPE_UNKNOWN:
    // this shouldn't be happening...
}

Solution 5 - C++

Call stat() or fstat() and see if S_IFIFO is set in st_mode.

Solution 6 - C++

You can call stat(0, &result) and check for !S_ISREG( result.st_mode ). That's Posix, not C/C++, though.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMike McQuaidView Question on Stackoverflow
Solution 1 - C++RichieHindleView Answer on Stackoverflow
Solution 2 - C++maxschlepzigView Answer on Stackoverflow
Solution 3 - C++Eric MelskiView Answer on Stackoverflow
Solution 4 - C++Glen KnowlesView Answer on Stackoverflow
Solution 5 - C++sigjuiceView Answer on Stackoverflow
Solution 6 - C++Marc Mutz - mmutzView Answer on Stackoverflow