Skip to main content

A python CLI app which accepts input from stdin or a file

This excellent guide on Command Line Interface Guidelines has been shared widely over the last few days and it includes a huge variety of advice on writing elegant and well-behaved command line tools. The whole thing is well worth a read, but I'm going to pick out one quote to focus on in this post:

If your command is expecting to have something piped to it and stdin is an interactive terminal, display help immediately and quit. This means it doesn't just hang, like cat.

Tools that can optionally accept input via pipe are very useful and this pattern is entirely possible with python and argparse, but not completely obvious. Here's a simple example of a python program which:

  • Can accept input as a file: ./myscript.py /path/to/file.txt
  • Can accept input via a pipe: echo 'foo' | ./myscript.py
  • Will print help and exit if invoked interactively with no arguments: ./myscript.py (instead of just hanging and waiting for input)
#!/usr/bin/env python3

import argparse
import sys

def main():
    parser = argparse.ArgumentParser(
        description='A well-behaved CLI app which accepts input from stdin or a file'
    )
    parser.add_argument(
        'file',
        nargs='?',
        help='Input file, if empty stdin is used',
        type=argparse.FileType('r'),
        default=sys.stdin,
    )
    args = parser.parse_args()

    if args.file.isatty():
        parser.print_help()
        return 0

    sys.stdout.write(args.file.read())
    return 0

if __name__ == '__main__':
    sys.exit(main())