Sending input and chaining commands

subprocess · Practical · 9 min

Many tools (jq, sort, grep, compilers) read from stdin, and pipelines are common in scripts.

input= sends data to the child's standard input and closes it, so the program sees end-of-file. Use a str with text=True, or bytes otherwise.

r = subprocess.run(["sort"], input="b\na\n", capture_output=True, text=True)

If you don't pass input or stdin, the child inherits your stdin. A command that unexpectedly waits for input (a password prompt, cat with no file) then hangs your script. In scripts and services, pass stdin=subprocess.DEVNULL so such reads see EOF immediately.

stdin= also accepts an open file, so stdin=open("data.csv") streams a file without loading it into memory.

To build a pipeline like ps aux | grep python without shell=True, the simplest way is to pass the first command's output as the second's input:

ps = subprocess.run(["ps", "aux"], capture_output=True, text=True, check=True)
hits = subprocess.run(["grep", "python"], input=ps.stdout, capture_output=True, text=True)

This holds the intermediate data in memory, which is fine for most outputs. For large streams, connect processes directly with Popen (a later lesson). Often the simplest pipeline is no pipeline at all: filter the output in Python.

Example

import subprocess, sys

r = subprocess.run(["sort", "-r"], input="apple\ncherry\nbanana\n",
                   capture_output=True, text=True, check=True)
print(r.stdout.split())                   # ['cherry', 'banana', 'apple']

words = subprocess.run(["printf", "a b\nc\n"], capture_output=True, text=True).stdout
count = subprocess.run(["wc", "-w"], input=words, capture_output=True, text=True)
print(count.stdout.strip())               # 3

r = subprocess.run([sys.executable, "-c", "print(repr(input('name? ')))"],
                   stdin=subprocess.DEVNULL, capture_output=True, text=True)
print(r.stderr.strip().splitlines()[-1])  # EOFError: EOF when reading a line

Remember

  • input= writes data to stdin and closes it
  • stdin=DEVNULL prevents hangs on unexpected prompts
  • Chain commands by passing one result's stdout as the next input

Watch out for

  • Letting a child inherit stdin in a cron job or service, where a prompt then blocks forever.

Check yourself

  1. A script calls a tool that sometimes asks 'Continue? [y/N]'. How do you keep it from hanging?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/subprocess.html#frequently-used-arguments