Your standard input is a tty in a number of surprising cases

July 17, 2015

Every once in a while, someone writing a program decides that checking to see whether standard input is a tty (via isatty()) is a great way of determining 'am I being run interactively or not?'. This certainly sounds like a good way to do this check if you aren't familiar with Unix and don't actually test any number of situations, but in fact it is wrong almost all of the time.

For a start, this is wrong if your command is just being run in a shell script. Commands run from a shell script inherit the script's standard input; if you just ran the script itself from a shell command line, well, that's your tty. No Unix shell can behave differently because passing stdin to script commands is what lets shell scripts work in the middle of pipelines. But plain commands are the obvious case, so let's go for an odder one:

var=$(/some/command ....)

You guessed it: /some/command inherits the shell's standard input and thus may have its standard input connected to your tty. Its standard output is not a tty, of course; it's being collected by the shell instead.

Now let's talk about GNU Make. Plain commands in Makefiles are like plain commands in shell scripts; make gets your standard input and passes it to commands being run. In my opinion this is far less defensible than with shell scripts, although I'm sure someone has a setup that uses make and a Makefile in the middle of a pipeline and counts on the commands run from the Makefile being able to read standard input. Still, I suspect a certain number of people would be surprised by that.

GNU Make has a feature where it can run a shell command as it parses the Makefile in order to do things like set up the value of Makefile variables. This looks like (in the simple version):

AVAR := $(shell /some/command ...)

This too can have isatty(stdin) be true. Like the shell, GNU Make passes its standard input down even to things being run via command substitution.

The short form version of this is almost anything that's run even indirectly by a user from their shell prompt may have standard input being a tty. Run from a shell script that's run from three levels of Makefiles (and makes) that are started from a shell script that's spawned from a C program that does a system()? Unless there's a pipeline somewhere in there, you probably still have standard input connected to the user's tty.

It follows that checking isatty(stdin) is a terrible way of seeing whether or not your program is being run interactively, unless the version of 'interactively' you care about is whether you're being run from something that's totally detached from the user, like a crontab or a ssh remote command execution (possibly an automated one). Standard input not being a tty doesn't guarantee this, of course, but if standard input is a tty you can be pretty sure that you aren't being run from crontab et al.

(The corollary of this is that if you're writing shell scripts and so on, you may sometimes want to deliberately disconnect standard input from what it normally would be. This doesn't totally stop people from talking to the user (they can always explicitly open /dev/tty), but at least it makes it less likely to happen by more or less accident.)

Written on 17 July 2015.
« Eating memory in Python the easy way
Some data on how long it is between fetches of my Atom feed »

Page tools: View Source, Add Comment.
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Fri Jul 17 00:58:36 2015
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.