#!/usr/bin/python import subprocess import sys import fcntl import os import select import StringIO """ This module contains a single function run() that spawns a process with arguments, and returns it's errcode, stdout, and stderr. It currently has a number of deficiencies, including the lack of a way to kill the child. """ def _make_non_blocking(fd): """ Takes a file descriptor and sets it to non-blocking IO mode, allowing us to use the posix select() on it. Used by this module to allow us to read from a process as it writes to stderr and stdout @type fd: number @param fd: file descriptor number to set to nonblocking """ fl = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) def run(cmd, **kwargs): """ Runs a command using the subprocess.Popen class, and returns the err, stdout, and stderr of the process. Optionally it can print to stdout the process's stderr and stdout, and can also call callbacks when stdout and stderr is available from the process. The following kwargs are useful: - print_stdout - if set to True will print process stdout to stdout as data arrives - print_stderr - if set to True will print process stderr to stdout as data arrives - stdin - Text to feed to process's stdin - stdout_callback - function to call with stdout data - stderr_callback - function to call with stderr data @type cmd: text or list @param cmd: the command to run, use a list if args are passed @type kwargs: keyword args @param kwargs: optional keyword arguments (see above) @rtype: tuple @return: Tuple of error code, stdout text, stderr text """ # Set flags depending on kwargs if "print_stdout" in kwargs: print_stdout=True else: print_stdout=False if "print_stderr" in kwargs: print_stderr=True else: print_stderr=False if "stdout_callback" in kwargs: stdout_callback=kwargs["stdout_callback"] print "setting stdout callback" else: stdout_callback=None if "stdout_callback" in kwargs: stderr_callback=kwargs["stderr_callback"] print "setting stderr callback" else: stderr_callback=None if "shell" in kwargs: shell=kwargs["shell"] else: shell=False # create process object, set up pipes child = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0, shell=shell ) # give the process any stdin that we might have, then # close stdin to prevent deadlocks if "stdin" in kwargs: child.stdin.write(kwargs["stdin"]) child.stdin.close() # get output pipes, make them non-blocking fo=child.stdout fe=child.stderr _make_non_blocking(fo.fileno()) eof=False stdout=StringIO.StringIO() stderr=StringIO.StringIO() # Loop, checking for data on process's stdout and stderr # until we've reached an eof condition to_check = [fo,fe] * (not eof) while True: # check for data ready = select.select(to_check,[],[],.05) # process any stdout if fo in ready[0]: data=fo.read() if data == '': eof =True else: if print_stdout: sys.stdout.write(data) sys.stdout.flush() #print "got data" #print data if stdout_callback: stdout_callback(data) stdout.write(data) stdout.flush() # process any stderr if fe in ready[0]: data=fe.read() if not data == '': if print_stderr: sys.stdout.write(data) sys.stdout.flush() #print data, if stderr_callback: stderr_callback(data) stderr.write(data) stderr.flush() if eof: break # clean up child and get its errcode err=child.wait() # Get the data in string form stdout=stdout.getvalue() stderr=stderr.getvalue() return (err, stdout, stderr) if __name__=="__main__": """ Random test cases. I think you pass a command as the first arg and the second arg is used as stdin. """ lines=sys.stdin.readlines() if lines: err, stdout, stderr = run(sys.argv[1:],stdin=''.join(lines), print_stdout=True) else: err, stdout, stderr = run(sys.argv[1:], print_stdout=True) print "Process ended with %d" % err print "Standard out:" print stdout print stdout.split('\n') print "Standard err:" print stderr