views:

12061

answers:

5

If I do the following:

import subprocess
from cStringIO import StringIO
subprocess.Popen(['grep','f'],stdout=subprocess.PIPE,stdin=StringIO('one\ntwo\nthree\nfour\nfive\nsix\n')).communicate()[0]

I get:

Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "/build/toolchain/mac32/python-2.4.3/lib/python2.4/subprocess.py", line 533, in __init__
    (p2cread, p2cwrite,
  File "/build/toolchain/mac32/python-2.4.3/lib/python2.4/subprocess.py", line 830, in _get_handles
    p2cread = stdin.fileno()
AttributeError: 'cStringIO.StringI' object has no attribute 'fileno'

Apparently a cStringIO.StringIO object doesn't quack close enough to a file duck to suit subprocess.Popen. How do I work around this?

+4  A: 

I figured out this workaround:

>>> p = subprocess.Popen(['grep','f'],stdout=subprocess.PIPE,stdin=subprocess.PIPE)
>>> p.stdin.write('one\ntwo\nthree\nfour\nfive\nsix\n')
>>> p.communicate()[0]
'four\nfive\n'
>>> p.stdin.close()

Is there a better one?

Daryl Spitzer
That's not a workaround - that's the correct way to do it!
Moe
@Moe: `stdin.write()` usage is discouraged, `p.communicate()` should be used. See my answer.
J.F. Sebastian
Per the subprocess documentation:Warning - Use communicate() rather than .stdin.write, .stdout.read or .stderr.read to avoid deadlocks due to any of the other OS pipe buffers filling up and blocking the child process.
Jason Mock
+1  A: 

"Apparently a cStringIO.StringIO object doesn't quack close enough to a file duck to suit subprocess.Popen"

:-)

I'm afraid not. The pipe is a low-level OS concept, so it absolutely requires a file object that is represented by an OS-level file descriptor. Your workaround is the right one.

Dan
+18  A: 

Popen.communicate() documentation:

Note that if you want to send data to the process’s stdin, you need to create the Popen object with stdin=PIPE. Similarly, to get anything other than None in the result tuple, you need to give stdout=PIPE and/or stderr=PIPE too.

Replacing os.popen*

    pipe = os.popen(cmd, 'w', bufsize)
    # ==>
    pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin

Warning Use communicate() rather than stdin.write(), stdout.read() or stderr.read() to avoid deadlocks due to any of the other OS pipe buffers filling up and blocking the child process.

So your example could be written as follows:

from subprocess import Popen, PIPE, STDOUT

p = Popen(['grep', 'f'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)

grep_stdout = p.communicate(input='one\ntwo\nthree\nfour\nfive\nsix\n')[0]
print(grep_stdout)
# -> four
# -> five
# ->
J.F. Sebastian
I missed that warning. I'm glad I asked (even though I thought I had the answer).
Daryl Spitzer
This is NOT a good solution. In particular, you cannot asynchronously process p.stdout.readline output if you do this since you'd have to wait for the entire stdout to arrive. It's is also memory-inefficient.
OTZ
+2  A: 

Recommended reading: Doug Hellmann's Python Module of the Week blog post on subprocess.

Daryl Spitzer
A: 

p = Popen(['grep', 'f'], stdout=PIPE, stdin=PIPE, stderr=STDOUT)

p.stdin.write('one\n')

time.sleep(0.5)

p.stdin.write('two\n')

time.sleep(0.5)

p.stdin.write('three\n')

time.sleep(0.5)

testresult = p.communicate()[0]

time.sleep(0.5)

print(testresult)