4

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?

Chris Clark
  • 1,439
  • 2
  • 17
  • 25

2 Answers2

6

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?

Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • Thanks. I was aware of the Popen syntax but was specifically looking for how to chain commands with `subprocess.run` in Python 3.5+. And it is indeed possible to chain commands using the `input` argument to `subprocess.run`. – Chris Clark Dec 31 '15 at 02:36
  • 2
    @ChrisClark: do you understand the difference between: `ls | cowsay` and `output=$(ls); cowsay <<< "$output"`? – jfs Dec 31 '15 at 02:50
4

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)
Chris Clark
  • 1,439
  • 2
  • 17
  • 25