Reputation: 761
How do I get the output from a command using subprocess.Popen and have separate callbacks for stdout and stderr, but ensure that those callbacks are called in the order that the lines came from the process?
If I didn't care about separating out STDOUT and STDERR then I could do:
fd = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT )
line = fd.stdout.readline()
while line :
callback( line )
line = fd.stdout.readline()
However, if I have stdoutCallback
and stderrCallback
, and want them called on the appropriate outputs, but in the same order as the above code would call callback
on, how would I go about doing this?
Upvotes: 5
Views: 2407
Reputation: 414875
It is impossible. There is no order defined if write
s are performed to different files.
You can get the correct order if the write
s to stdout, stderr go to the same place (like in your stdout=PIPE, stderr=STDOUT
case).
If "approximate" order is enough; here's a simple code example with threads and here's single-threaded version with a select
-loop.
Upvotes: 2
Reputation: 761
So I think I've rolled my own with a couple of threads.
For the example below, test.py
is this:
#!/usr/bin/python -u
import sys
import time
sys.stdout.write("stdout 1\n")
time.sleep(1)
sys.stderr.write("stderr 2\n")
time.sleep(1)
sys.stdout.write("stdout 3\n")
time.sleep(1)
sys.stderr.write("stderr 4\n")
time.sleep(1)
My code for getting the right output is:
#!/usr/bin/env python
import subprocess
from threading import Thread, Lock
cmdOutput = []
cmdOutputLock = Lock()
STDOUT = 1
STDERR = 2
def _outputLoop( fd, identifier ) :
line = fd.readline()
while line :
cmdOutputLock.acquire()
cmdOutput.append( ( line, identifier ) )
cmdOutputLock.release()
line = fd.readline()
p = subprocess.Popen( "test.py",
stdout = subprocess.PIPE,
stderr = subprocess.PIPE )
Thread( target=_outputLoop, args=( p.stdout, STDOUT ) ).start()
Thread( target=_outputLoop, args=( p.stderr, STDERR ) ).start()
while fd.poll() is None or cmdOutput :
output = None
cmdOutputLock.acquire()
if cmdOutput :
output = cmdOutput[0]
del cmdOutput[0]
cmdOutputLock.release()
if output :
if output[1] == STDOUT :
print "STDOUT : {}".format( output[0].rstrip() )
elif output[1] == STDERR :
print "STDERR : {}".format( output[0].rstrip() )
I can definitely imagine a time when an stderr line is mixed up with an stdout line, but for the sake of what I wanted it for, it certainly works. (I was putting this as part of a logging module which would run a command and use different log levels for stdout and stderr.)
Upvotes: 1
Reputation: 67988
fd = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE )
output,error = fd.communicate()
Use communicate
Upvotes: 0