38

I want a program to do one thing if executed like this:

cat something | my_program.py

and do another thing if run like this

my_program.py

But if I read from stdin, then it will wait for user input, so I want to see if there is anything to read before trying to read from stdin.

Josh Gibson
  • 21,808
  • 28
  • 67
  • 63
  • What does "shouldn't be as hard as it seems" mean? Would you mind updating the question with a description of your goal, any code you've tried so far that hasn't worked, and what you expected to have happened? – Jarret Hardie Mar 30 '09 at 23:03

4 Answers4

70

If you want to detect if someone is piping data into your program, or running it interactively you can use isatty to see if stdin is a terminal:

$ python -c 'import sys; print sys.stdin.isatty()'
True
$ echo | python -c 'import sys; print sys.stdin.isatty()'
False
brian-brazil
  • 31,678
  • 6
  • 93
  • 86
  • If the script could be executed remotely, does this isatty() approach still work ? For example `ssh somehost.com ' bash -c " python -c \"import sys; print sys.stdin.isatty()\" " '` returns `False` – Hakan Baba Oct 10 '17 at 23:01
  • 1
    Using @Trey Stout 's answer the script behaves as expected over ssh. `ssh somehost.com ' bash -c " python -c \"import sys; import select; r, w, x = select.select([sys.stdin], [], [], 0); print(r) \" " '` Prints `[]`. The same command with pipe is: `ssh somehost.com ' bash -c " echo something | python -c \"import sys; import select; r, w, x = select.select([sys.stdin], [], [], 0); print(r) \" " '` and that prints `[', mode 'r' at 0x7ff2aad250c0>]` – Hakan Baba Oct 10 '17 at 23:09
10

You want the select module (man select on unix) It will allow you to test if there is anything readable on stdin. Note that select won't work on Window with file objects. But from your pipe-laden question I'm assuming you're on a unix based os :)

http://docs.python.org/library/select.html

root::2832 jobs:0 [~] # cat stdin_test.py
#!/usr/bin/env python
import sys
import select

r, w, x = select.select([sys.stdin], [], [], 0)
if r:
    print "READABLES:", r
else:
    print "no pipe"

root::2832 jobs:0 [~] # ./stdin_test.py
no pipe

root::2832 jobs:0 [~] # echo "foo" | ./stdin_test.py
READABLES: [<open file '<stdin>', mode 'r' at 0xb7d79020>]
Trey Stout
  • 6,231
  • 3
  • 24
  • 27
  • 2
    This does not tell you if input is a pipe, only if input is available. Consider: ( sleep 1 && echo "foo" ) | ./stdin_test.py On my machine this reports "no pipe". – Andrew Dalke Apr 01 '09 at 14:22
  • 2
    Interesting note. I think that's just because the select is not blocking. When I change the 0 to a 5 your example properly shows the open file. It should really be put into a loop that could then cycle until data was available. Good point :) – Trey Stout Apr 01 '09 at 20:29
  • Do you know how I can do sth similar to this in bash or zsh? – HappyFace Aug 21 '19 at 19:39
4

Bad news. From a Unix command-line perspective those two invocations of your program are identical.

Unix can't easily distinguish them. What you're asking for isn't really sensible, and you need to think of another way of using your program.

In the case where it's not in a pipeline, what's it supposed to read if it doesn't read stdin?

Is it supposed to launch a GUI? If so, you might want to have a "-i" (--interactive) option to indicate you want a GUI, not reading of stdin.

You can, sometimes, distinguish pipes from the console because the console device is "/dev/tty", but this is not portable.

S.Lott
  • 384,516
  • 81
  • 508
  • 779
  • +1: Anyone who has ever fought an application that tried cute tricks to get around this will appreciate any extra effort spent avoid such cute tricks (or at least making them explicit, as S.Lott suggests). – Jarret Hardie Mar 30 '09 at 23:41
  • Those invocations aren't identical, as "unknown (google)" pointed out using sys.stdin.isatty(). "isatty" is portable under Unix, and used for example in 'ls' (http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/ls.c ). But yes, it's a trick to be wary about using. – Andrew Dalke Mar 31 '09 at 00:02
  • The invocations are identical -- indistinguishable. The devices connected to stdin are slightly different. Depending on /dev/tty is workable, but an option is trivial and obvious. – S.Lott Mar 31 '09 at 01:29
  • Absolutely +1, I often use --stdin to tell the app to do this, lots of unixy programs just use - – Ali Afshar Mar 31 '09 at 14:25
  • Hmmm. Okay, nuanced differences in how we use "invoke". I think of the invocation as including the "|" to use a pipe for stdin, while for you it's what's in the exec call. In any case, 'isatty' doesn't check for /dev/tty, it does fstat of stdin and checks if st_mode is/isn't a character special file – Andrew Dalke Apr 01 '09 at 13:51
  • The nuance IS the point -- one can try to depend on isatty and hope the user didn't do something screwy, or one can provide an explicit command-line option that says "--interactive" vs. the default assumption, which is read from stdin. No mystery. No obscure dependency on "isatty". – S.Lott Apr 01 '09 at 13:54
  • If you run `grep -R stuff` (forgetting to include at least one path), some greps will tell you "warning: recursive search of stdin" or something similar. I think that's a good use of TTY detection. Apparently GNU grep 2.12 goes one step further, if it detects the -R flag on stdin, it will search the current working directory instead of stdin. – Mark E. Haase May 21 '12 at 19:35
-2

I do not know the Python commands off the top of my head, but you should be able to do something with poll or select to look for data ready to read on standard input.

That might be Unix OS specific and different on Windows Python.

Zan Lynx
  • 53,022
  • 10
  • 79
  • 131
  • Something like: select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []) And, yes, it is unix-specific. – John Fouhy Mar 31 '09 at 00:28