I'm experimenting with subprocess.run in Python 3.5. To chain two commands together, I would have thought that the following should work:
import subprocess
ps1 = subprocess.run(['ls'], universal_newlines=True, stdout=subprocess.PIPE)
ps2 = subprocess.run(['cowsay'], stdin=ps1.stdout)
However, this fails with:
AttributeError: 'str' object has no attribute 'fileno'
ps2 was expecting a file-like object, but the output of ps1 is a simple string.
Is there a way to chain commands together with subprocess.run?
2 Answers 2
Turns out that subprocess.run has an input argument to handle this:
ps1 = subprocess.run(['ls'], universal_newlines=True, stdout=subprocess.PIPE)
ps2 = subprocess.run(['cowsay'], universal_newlines=True, input=ps1.stdout)
Also, the following works as well, which doesn't use input:
ps1 = subprocess.run(['ls'], universal_newlines=True, stdout=subprocess.PIPE)
ps2 = subprocess.run(['cowsay', ps1.stdout], universal_newlines=True)
Comments
subprocess.run() can't be used to implement ls | cowsay without the shell because it doesn't allow to run the individual commands concurrently: each subprocess.run() call waits for the process to finish that is why it returns CompletedProcess object (notice the word "completed" there). ps1.stdout in your code is a string that is why you have to pass it as input and not the stdin parameter that expects a file/pipe (valid .fileno()).
Either use the shell:
subprocess.run('ls | cowsay', shell=True)
Or use subprocess.Popen, to run the child processes concurrently:
from subprocess import Popen, PIPE
cowsay = Popen('cowsay', stdin=PIPE)
ls = Popen('ls', stdout=cowsay.stdin)
cowsay.communicate()
ls.wait()
See How do I use subprocess.Popen to connect multiple processes by pipes?
2 Comments
subprocess.run in Python 3.5+. And it is indeed possible to chain commands using the input argument to subprocess.run.ls | cowsay and output=$(ls); cowsay <<< "$output"?