5

Right now I have some code that uses Popen.communicate() from subprocess (setting stdin=PIPE and stderr=PIPE) to run a command and capture both stderr and stdout.

The problem is that communicate() not only waits for the command to exit, it waits for stdout and stderr to be closed. The command I'm running spawns a child process which keeps stderr open, so even though the command is finished running (and shows as "defunct" in ps) communicate() is still hung.

I want to only wait for the command to finish without waiting on stderr/stdout. But I still want to capture any stderr/stdout output given while the command was running. The documentation for wait() is accompanied by a red box with a disclaimer:

This will deadlock when using stdout=PIPE and/or stderr=PIPE and the child process generates enough output to a pipe such that it blocks waiting for the OS pipe buffer to accept more data. Use communicate() to avoid that.

Obviously, I also want to avoid deadlocks.

What is the right way to accomplish this task?

3

2 Answers 2

1

"shows as "defunct" in ps" implies that you might be on a system where select or fcntl would work i.e., you can read stdout/stderr without blocking easily.

Example: A starts B (cmd, the child), B starts C (grandchild), A reads output until B exits or EOF:

#!/usr/bin/env python import os from select import select from subprocess import Popen, PIPE p = Popen(cmd, stdout=PIPE, stderr=PIPE, bufsize=0) read_set = [p.stdout, p.stderr] pipename = {p.stdout: "stdout", p.stderr: "stderr"} timeout = 0.5 # ugly but it works while read_set and p.poll() is None: # while subprocess is running or until EOF for pipe in select(read_set, [], [], timeout)[0]: data = os.read(pipe.fileno(), 1<<30) if data: print("got from %s: %r" % (pipename[pipe], data)) else: # EOF pipe.close() read_set.remove(pipe) print("exit code %s" % (p.wait(),)) # child exited, wait for grandchild to print for pipe in read_set: print("read the rest of %s: %r" % (pipename[pipe], pipe.read())) pipe.close() 

where cmd:

import sys from textwrap import dedent cmd = [sys.executable, '-u', '-c', dedent(""" # inception style import os import sys from subprocess import Popen from textwrap import dedent Popen([sys.executable, '-u', '-c', dedent(''' import os import sys import time time.sleep(60) print("grandchild %d done" % os.getpid()) sys.stderr.write("grandchild stderr") sys.exit(20) ''')]) # stdout/stderr are not redirected print('child %d done' % os.getpid()) sys.stderr.write('child stderr') sys.exit(19) """)] 
Sign up to request clarification or add additional context in comments.

Comments

0

I've tried a simple example. And all I've got so far is capturing of the std.err I've created two files. The contents of the first is (example1.py):

import time import sys for i in xrange(1000): print >>sys.stderr, "hello", i time.sleep(1) 

The contents of the second one(example2.py):

import subprocess cmd = "python example1.py" print subprocess.check_output(cmd, shell=True) 

And that I've just called the example2.py script:

python example2.py And I've realtime output ( I assume it is true:) ):

hello 0 hello 1 hello 2 hello 3 hello 4 hello 5 hello 6 

Still, I have no idea, how to deal with standard output, but if I manage it, I will post the answer here

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.