Maybe there's someone out in the ether that can help me with this one. (I have seen a number of similar questions to this on SO, but none deal with both standard out and standard error or deal with a situation quite like mine, hence this new question.)
I have a python function that opens a subprocess, waits for it to complete, then outputs the return code, as well as the contents of the standard out and standard error pipes. While the process is running, I'd like to also display the output of both pipes as they are populated. My first attempt has resulted in something like this:
process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout = str()
stderr = str()
returnCode = None
while True:
# collect return code and pipe info
stdoutPiece = process.stdout.read()
stdout = stdout + stdoutPiece
stderrPiece = process.stderr.read()
stderr = stderr + stderrPiece
returnCode = process.poll()
# check for the end of pipes and return code
if stdoutPiece == '' and stderrPiece == '' and returnCode != None:
return returnCode, stdout, stderr
if stdoutPiece != '': print(stdoutPiece)
if stderrPiece != '': print(stderrPiece)
There's a couple problems with this though. Because read()
reads until an EOF
, the first line of the while
loop will not return until the subprocess closes the pipe.
I could replace the read()
in favor of read(int)
but the printed output is distorted, cut off at the end of the read characters. I could readline()
as a replacement, but the printed output is distorted with alternating lines of output and errors when there are many of both that occur at the same time.
Perhaps there's a read-until-end-of-buffer()
variant that I'm not aware of? Or maybe it can be implemented?
Maybe it's best to implement a sys.stdout
wrapper as suggested in this answer to another post? I would only want to use the wrapper in this function, however.
Any other ideas from the community?
I appreciate the help! :)
EDIT: The solution really should be cross-platform, but if you have ideas that aren't, please share them away to keep the brainstorming going.
For another one of my python subprocess head scratchers, take a look at another of my questions on accounting for subprocess overhead in timing.
Combining this answer with this, the following code works for me:
Make the pipes non-blocking by using
fcntl.fcntl
, and useselect.select
to wait for data to become available in either pipe. For example:Note that
fcntl
is only available on Unix-like platforms, including Cygwin.If you need it to work on Windows without Cygwin, it's doable, but it's much, much tougher. You'll have to:
SetNamedPipeHandleState
withPIPE_NOWAIT
to make the stdout and stderr pipes non-blockingWaitForMultipleObjects
instead ofselect
to wait for data to become availableReadFile
to read the dataWhen I tested it, it seemed readline() is blocking. However I was able to access stdout and stderr separately using threads. Code sample as follows:
However, I am not certain that this is thread-safe.