6.7 Capturing the Output and Error Streams from a Unix Shell Command


Credit: Brent Burley

6.7.1 Problem

You need to run an external process in a Unix-like environment and capture both the output and error streams from the external process.

6.7.2 Solution

The popen2 module lets you capture both streams, but you also need help from fcntl to make the streams nonblocking and thus avoid deadlocks:

import os, popen2, fcntl, FCNTL, select def makeNonBlocking(fd):     fl = fcntl.fcntl(fd, FCNTL.F_GETFL)     try:       fcntl.fcntl(fd, FCNTL.F_SETFL, fl | FCNTL.O_NDELAY)     except AttributeError:       fcntl.fcntl(fd, FCNTL.F_SETFL, fl | FCNTL.FNDELAY) def getCommandOutput(command):     child = popen2.Popen3(command, 1) # Capture stdout and stderr from command     child.tochild.close(  )             # don't need to write to child's stdin     outfile = child.fromchild     outfd = outfile.fileno(  )     errfile = child.childerr     errfd = errfile.fileno(  )     makeNonBlocking(outfd)            # Don't deadlock! Make fd's nonblocking.     makeNonBlocking(errfd)     outdata = errdata = ''     outeof = erreof = 0     while 1:         ready = select.select([outfd,errfd],[],[]) # Wait for input         if outfd in ready[0]:             outchunk = outfile.read(  )             if outchunk == '': outeof = 1             outdata = outdata + outchunk         if errfd in ready[0]:             errchunk = errfile.read(  )             if errchunk == '': erreof = 1             errdata = errdata + errchunk         if outeof and erreof: break         select.select([],[],[],.1) # Allow a little time for buffers to fill     err = child.wait(  )     if err != 0:         raise RuntimeError, '%s failed with exit code %d\n%s' % (             command, err, errdata)     return outdata def getCommandOutput2(command):     child = os.popen(command)     data = child.read(  )     err = child.close(  )     if err:         raise RuntimeError, '%s failed with exit code %d' % (command, err)     return data

6.7.3 Discussion

This recipe shows how to execute a Unix shell command and capture the output and error streams in Python. By contrast, os.system sends both streams directly to the terminal. The presented getCommandOutput(command) function executes a command and returns the command's output. If the command fails, an exception is raised, using the text captured from the command's stderr as part of the exception's arguments.

Most of complexity of this code is due to the difficulty of capturing both the output and error streams of the child process at the same time. Normal (blocking) read calls may deadlock if the child is trying to write to one stream, and the parent is waiting for data on the other stream, so the streams must be set to nonblocking, and select must be used to wait for data on the streams.

Note that the second select call adds a 0.1-second sleep after each read. Counterintuitively, this allows the code to run much faster, since it gives the child time to put more data in the buffer. Without this, the parent may try to read only a few bytes at a time, which can be very expensive.

If you want to capture only the output, and don't mind the error stream going to the terminal, you can use the much simpler code presented in getCommandOutput2. If you want to suppress the error stream altogether, that's easy, too. You can append 2>/dev/null to the command. For example:

ls -1 2>/dev/null

Since Version 2.0, Python includes the os.popen4 function, which combines the output and error streams of the child process. However, the streams are combined in a potentially messy way, depending on how they are buffered in the child process, so this recipe can still help.

6.7.4 See Also

Documentation of the standard library modules os, popen2, fcntl, and select in the Library Reference.



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2005
Pages: 346

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net