weix.us

Reading stdinput with python

I was recently trying to set up automations that require piping data from one command to another. I’m inexperienced with command line interfaces, but I’ve been meaning to sit down and read the sys library documentation.

In python, None by default also acts like False, allowing the nice trick

if var:
    # do something that requires var != None

I expected sys.stdin to act like this, but it didn’t. sys.stdin delivers a _io.TextIOWrapper object. The wrapper has a value regardless of whether the stdinput stream is empty. Instead, the isatty() function returns True if sys.stdin is interactive. For my use, this has been a good proxy for sys.stdin having content in the buffer.

For bonus points, one can allow both interactive and non-interactive file processing:

import sys
from argparse import ArgumentParser

def main(params):
    if sys.stdin.isatty():
        it = (Path(f.rstrip()) for f in sys.stdin)
    else:
        it = (Path(f) for f in params.input)
	
    for f in it:
        ...

if __name__ == "__main__":
    parser = ArgumentParser()
    parser.add_argument("-i", "--input", nargs="*")
    if not sys.stdin.isatty() and not params.input:
        parser.print_usage(sys.stderr)
        sys.exit(1)
    main(parser.parse_args())

Combining this with uv tool install has made building extra shell commands a lot easier. I mean, the ones I make are probably not that good, but they’re still useful to me. I’m thinking of trying to get nicer ways to interact with yaml and markdown files, since that’s how I keep my collection of book notes together. Of course, at a certain point it just becomes reverse-engineering calibre, and that’s not a useful activity for me.

Posted by Elliott Weix.