views:

336

answers:

3

I'm not a Perl user, but from this question deduced that it's exceedingly easy to retrieve the standard output of a program executed through a Perl script using something akin to:

$version = `java -version`;  

How would I go about getting the same end result in Python? Does the above line retrieve standard error (equivalent to C++ std::cerr) and standard log (std::clog) output as well? If not, how can I retrieve those output streams as well?

Thanks, Geoff

+7  A: 

For python 2.5: sadly, no. You need to use subprocess:

import subprocess
proc = subprocess.Popen(['java', '-version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = proc.communicate()

Docs are at http://docs.python.org/library/subprocess.html

pkh
I should have specified Python 3, but it doesn't seem to make a difference in this case. :) Thanks!
Geoff
I don't know why you think this is so sad. I generally find that Perl code which is littered with backtick expressions is of very poor quality and dubious security and robustness.
Jim Dennis
Because it shouldn't require ~100 characters of pure boilerplate to do something as absolutely ubiquitous in a scripting language as making a system call and getting the output, and I shouldn't have to roll my own.(That being said, python *has* replaced perl as my this-just-got-too-complicated-for-grep-and-sed language in the past couple of years. Typically, I'm entirely unconcerned about security and robustness when using it, which makes your take -- while probably useful to someone doing production work with it -- unconvincing for me.)
pkh
+4  A: 

As others have mentioned you want to use the Python subprocess module for this.

If you really want something that's more succinct you can create a function like:

#!/usr/bin/env python 
import subprocess, shlex

def captcmd(cmd):
    proc = subprocess.Popen(shlex.split(cmd), \
      stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False)

    out, err = proc.communicate()
    ret = proc.returncode
    return (ret, out, err)

... then you can call that as:

ok, o, e = captcmd('ls -al /foo /bar ...')
print o
if not ok:
    print >> sys.stderr, "There was an error (%d):\n" % ok
    print >> sys.stderr, e

... or whatever.

Note: I'm using shlex.split() as a vastly safer alternative to shell=True

Naturally you could write this to suit your own tastes. Of course for every call you have to either provide three names into which it can unpack the result tuple or you'd have to pull the desired output from the result using normal indexing (captcmd(...)[1] for the output, for example). Naturally you could write a variation of this function to combine stdout and stderr and to discard the result code. Those "features" would make it more like the Perl backtick expressions. (Do that and take out the shlex.split() call and you have something that's as crude and unsafe as what Perl does, in fact).

Jim Dennis
Could you elaborate on the safety implications of not using `shlex.split()`?
Tim Pietzcker
@Tim: +1, good question.
Geoff
@Tim: Passing unparsed strings to a shell running in a subprocess leaves open a number of possible attacks (and parsing bugs) which are not exploitable when passing a vector of command + arguments to the execve system call. shlex.split() parses a normal shell command into a vector for you.For example consider the string: 'uname -r $(touch ~/flag)' ... pass it to Popen with shell=True and you've been p0wn3d, split it into a vector and pass it to Popen with shell=False and you get an error (extra argument to the uname command.There are many ways to trick side effects into a shell command.
Jim Dennis
+5  A: 

In Python 2.7+

from subprocess import check_output as qx

output = qx(['java', '-version'])

The answer to Capturing system command output as a string question has implementation for Python < 2.7.

J.F. Sebastian