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?
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
)
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 following program:
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <iostream>
using namespace std;
int main() {
char tty[L_ctermid+1] = {0};
ctermid(tty);
cout << \"ID: \" << tty << \'\\n\';
int fd = ::open(tty, O_RDONLY);
if (fd < 0) perror(\"Could not open terminal\");
else {
cout << \"Opened terminal\\n\";
struct termios term;
int r = tcgetattr(fd, &term);
if (r < 0) perror(\"Could not get attributes\");
else cout << \"Got attributes\\n\";
}
if (isatty(fileno(stdin))) cout << \"Is a terminal\\n\";
else cout << \"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)) cout << \"S_ISCHR\\n\";
else if (S_ISFIFO(stats.st_mode)) cout << \"S_ISFIFO\\n\";
else if (S_ISREG(stats.st_mode)) cout << \"S_ISREG\\n\";
else cout << \"unknown stat mode\\n\";
}
return 0;
}
Termimal 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.
Call stat() or fstat() and see if S_IFIFO is set in st_mode.
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.
}
But why ask us? Python is open source. You can just go look at what they do and know for sure:
http://www.python.org/ftp/python/2.6.2/Python-2.6.2.tar.bz2
Hope that helps,
Eric Melski
You can call stat(0, &result)
and check for !S_ISREG( result.st_mode )
. That\'s Posix, not C/C++, though.
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...
}