I was writing a small Python script and wanted it to handle three types of inputs:

  1. Standard input by user inputing from keyboard
  2. Standard input by pipeline
  3. File input

I wanted to make my script can do as follow:

% program # Sometimes, implying using standard input but depends on the purpose of program
% program - # Indicates using standard input
% program filename
% prog2 | program # Pipe prog2's standard output to program's standard input

We should all have no problem with file, you just check the argument to see if we need to open a file as input.

The first two input types are not hard to do, we only need to use file.isatty() to identify current state of standard input:

file.isatty()
Return True if the file is connected to a tty(-like) device, else False.

We can decide based on the following:

sys.stdin.isatty()

If the program’s standard input is piped in with another program’s standard output, then it returns False; True if not.

You can quickly test:

python -c "import sys ; print sys.stdin.isatty()"
echo blah | python -c "import sys ; print sys.stdin.isatty()"

You should get True then False as results.

So, now, here is a snippet stdinput.py:

#!/usr/bin/env python


import sys


def main():

  args = sys.argv[1:]

  print 'Usage: %s [-|filename] or using pipe.\n' % sys.argv[0]

  if sys.stdin.isatty():
  if len(args) == 1 and args[0] != '-':
    print '=== from file ==='
    text = ['* %s *' % args[0]]
  else:
    if sys.platform == 'win32':
    print '( Press Control+Z, then Return at new line to finish )'
    else:
    print '( Press Control+D, then Return at new line to finish )'
    text = sys.stdin.readlines()
    print '=== from manual input ==='
  else:
  if len(args) == 1 and args[0] != '-':
    print >> sys.stderr, 'Argument should be -, if you intend to pipe in your text.'
    sys.exit(1)
  text = sys.stdin.readlines()
  print '=== from pipe ==='

  print ''.join(text)


if __name__ == '__main__':
  main()

This snippet isn’t so special, only two things to note:

  1. It use different keystroke to close the stream (to signal End-of-file) on Windows Ctrl+Z and Linux Ctrl+D.
  2. The script load data at once. You need to check empty string if you use read() or readline() for EOF.

It should do what I want, you can test with the followings:

./stdinput.py
./stdinput.py -
./stdinput.py filename
echo blah | ./stdinput.py
echo blah | ./stdinput.py -
echo blah | ./stdinput.py filename

Note that this is the behavior I want, you may need to adjust them for your need.

What about standard output? It’s the same story, just switching the roles.

python -c "import sys ; print sys.stdout.isatty()"
python -c "import sys ; print sys.stdout.isatty()" | cat
# On Windows
python -c "import sys ; print sys.stdout.isatty()" | more
# On Windows' PowerShell
python -c "import sys ; print sys.stdout.isatty()" | echo

You should get True then False as results.

And what about redirection, prog.py > file or prog.py < file? Still same story.