Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct incantation of subprocess with shell=True to get output and not hang

Inside a subprocess call, I want to use shell=True so that it does globbing on pathnames (code below), however this has the annoying side-effect of making subprocess spawn a child process (which must then be `communicate()d/ poll()ed/ wait()ed/ terminate()d/ kill()ed/ whatevah).

(Yes I am aware the globbing can also be done with fnmatch/glob, but please show me the 'correct' use of subprocess on this, i.e. the minimal incantation to both get the stdout and stop the child process.)

This works fine (returns output):

subprocess.check_output(['/usr/bin/wc','-l','[A-Z]*/[A-Z]*.F*'], shell=False)

but this hangs

subprocess.check_output(['/usr/bin/wc','-l','[A-Z]*/[A-Z]*.F*'], shell=True)

(PS: It's seriously aggravating that you can't tell subprocess you want some but not all shell functionality e.g. globbing but not spawning. I think there's a worthy PEP in that, if anyone cares to comment, i.e. pass in a tuple of Boolean, or an or of binary flags)

(PPS: the idiom of whether you pass subprocess...(cmdstring.split() or [...]) is just a trivial idiomatic difference. I say tomato, you say tomay-to. In my case, the motivation is the command is fixed but I may want to call it more than once with a difference filespec.)

like image 859
smci Avatar asked Mar 26 '12 22:03

smci


People also ask

How do I get output to run from subprocess?

To capture the output of the subprocess. run method, use an additional argument named “capture_output=True”. You can individually access stdout and stderr values by using “output. stdout” and “output.

What is shell true in subprocess?

After reading the docs, I came to know that shell=True means executing the code through the shell. So that means in absence, the process is directly started.

How can we avoid shell true in subprocess?

From the docs: args is required for all calls and should be a string, or a sequence of program arguments. Providing a sequence of arguments is generally preferred, as it allows the module to take care of any required escaping and quoting of arguments (e.g. to permit spaces in file names).


1 Answers

First off -- there's very little point to passing an array to:

subprocess.check_output(['/usr/bin/wc','-l','A-Z*/A-Z*.F*'], shell=True)

...as this simply runs wc with no arguments, in a shell also passed arguments -l and A-Z*/A-Z*.F* as arguments (to the shell, not to wc). Instead, you want:

subprocess.check_output('/usr/bin/wc -l A-Z*/A-Z*.F*', shell=True)

Before being corrected, this would hang because wc had no arguments and was reading from stdin. I would suggest ensuring that stdin is passed in closed, rather than passing along your Python program's stdin (as is the default behavior).

An easy way to do this, since you have shell=True:

subprocess.check_output(
    '/usr/bin/wc -l A-Z*/A-Z*.F* </dev/null',
    shell=True)

...alternately:

p = subprocess.Popen('/usr/bin/wc -l A-Z*/A-Z*.F*', shell=True,
                     stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None)
(output, _) = p.communicate(input='')

...which will ensure an empty stdin from Python code rather than relying on the shell.

like image 189
Charles Duffy Avatar answered Sep 30 '22 11:09

Charles Duffy