On 11/08/2010 11:32 AM, Von Fugal wrote:
> I still love ruby plenty! :) However, I would never recommend it as a
> scripting language. I've tried that numerous times myself when I was
> still in ruby honeymoon. It's more trouble than it's worth. Use
> something scritpy like perl or bash. I'm not sure I'd even recommend
> python for scripting. No, interpreted != scripting language.
I find that Python works pretty well for scripting, but it was missing
an easy way to run a command and then get the return code, the standard
out and standard-err from the process. I can run a command a get stdout
with the os.system() call, which is, IIRC, like the backticks in perl.
But that's not enough sometimes. So I wrote some wrapper code that
makes it easy to spawn a process, feed it input, and get the
above-mentioned output. I've attached it here if anyone wants it. If
you combine this simple module with generators you won't miss piping
processes together; it's not needed 90% of the time. If you do need to
do some piping, you can always run your processes in a sub-shell (one of
the options in my function).
Note that this will give you all the output in one chunk so if you want
to capture a lot of output, use the subprocess module directly and
operate on the pipe. And of course with pipes you can set up generators
which is pretty awesome and almost as fast as bash piping.
Anyway, it's not as quick and dirty as bash by any means, but it's
sufficient to pretty cleanly do what I need in a shell script most of
the time.
#!/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
/*
PLUG: http://plug.org, #utah on irc.freenode.net
Unsubscribe: http://plug.org/mailman/options/plug
Don't fear the penguin.
*/