_process_common.py
194 lines
| 6.2 KiB
| text/x-python
|
PythonLexer
Fernando Perez
|
r2908 | """Common utilities for the various process_* implementations. | ||
This file is only meant to be imported by the platform-specific implementations | ||||
of subprocess utilities, and it contains tools that are common to all of them. | ||||
""" | ||||
#----------------------------------------------------------------------------- | ||||
Matthias BUSSONNIER
|
r5390 | # Copyright (C) 2010-2011 The IPython Development Team | ||
Fernando Perez
|
r2908 | # | ||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#----------------------------------------------------------------------------- | ||||
#----------------------------------------------------------------------------- | ||||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
import subprocess | ||||
Jörgen Stenarson
|
r5537 | import shlex | ||
Fernando Perez
|
r2908 | import sys | ||
Thomas Kluyver
|
r4731 | from IPython.utils import py3compat | ||
Fernando Perez
|
r2908 | #----------------------------------------------------------------------------- | ||
# Function definitions | ||||
#----------------------------------------------------------------------------- | ||||
def read_no_interrupt(p): | ||||
"""Read from a pipe ignoring EINTR errors. | ||||
This is necessary because when reading from pipes with GUI event loops | ||||
running in the background, often interrupts are raised that stop the | ||||
command from completing.""" | ||||
import errno | ||||
try: | ||||
return p.read() | ||||
except IOError, err: | ||||
if err.errno != errno.EINTR: | ||||
raise | ||||
def process_handler(cmd, callback, stderr=subprocess.PIPE): | ||||
"""Open a command in a shell subprocess and execute a callback. | ||||
This function provides common scaffolding for creating subprocess.Popen() | ||||
calls. It creates a Popen object and then calls the callback with it. | ||||
Parameters | ||||
---------- | ||||
cmd : str | ||||
A string to be executed with the underlying system shell (by calling | ||||
:func:`Popen` with ``shell=True``. | ||||
callback : callable | ||||
A one-argument function that will be called with the Popen object. | ||||
stderr : file descriptor number, optional | ||||
By default this is set to ``subprocess.PIPE``, but you can also pass the | ||||
value ``subprocess.STDOUT`` to force the subprocess' stderr to go into | ||||
the same file descriptor as its stdout. This is useful to read stdout | ||||
and stderr combined in the order they are generated. | ||||
Returns | ||||
------- | ||||
The return value of the provided callback is returned. | ||||
""" | ||||
sys.stdout.flush() | ||||
sys.stderr.flush() | ||||
Fernando Perez
|
r2926 | # On win32, close_fds can't be true when using pipes for stdin/out/err | ||
close_fds = sys.platform != 'win32' | ||||
Fernando Perez
|
r2908 | p = subprocess.Popen(cmd, shell=True, | ||
stdin=subprocess.PIPE, | ||||
stdout=subprocess.PIPE, | ||||
stderr=stderr, | ||||
Fernando Perez
|
r2921 | close_fds=close_fds) | ||
Fernando Perez
|
r2908 | |||
try: | ||||
out = callback(p) | ||||
except KeyboardInterrupt: | ||||
print('^C') | ||||
sys.stdout.flush() | ||||
sys.stderr.flush() | ||||
out = None | ||||
finally: | ||||
# Make really sure that we don't leave processes behind, in case the | ||||
# call above raises an exception | ||||
# We start by assuming the subprocess finished (to avoid NameErrors | ||||
# later depending on the path taken) | ||||
if p.returncode is None: | ||||
try: | ||||
p.terminate() | ||||
p.poll() | ||||
except OSError: | ||||
pass | ||||
# One last try on our way out | ||||
if p.returncode is None: | ||||
try: | ||||
p.kill() | ||||
except OSError: | ||||
pass | ||||
return out | ||||
Fernando Perez
|
r3002 | def getoutput(cmd): | ||
"""Return standard output of executing cmd in a shell. | ||||
Accepts the same arguments as os.system(). | ||||
Parameters | ||||
---------- | ||||
cmd : str | ||||
A command to be executed in the system shell. | ||||
Returns | ||||
------- | ||||
stdout : str | ||||
""" | ||||
out = process_handler(cmd, lambda p: p.communicate()[0], subprocess.STDOUT) | ||||
if out is None: | ||||
Thomas Kluyver
|
r4731 | return '' | ||
return py3compat.bytes_to_str(out) | ||||
Fernando Perez
|
r3002 | |||
Fernando Perez
|
r2908 | def getoutputerror(cmd): | ||
"""Return (standard output, standard error) of executing cmd in a shell. | ||||
Accepts the same arguments as os.system(). | ||||
Parameters | ||||
---------- | ||||
cmd : str | ||||
A command to be executed in the system shell. | ||||
Returns | ||||
------- | ||||
stdout : str | ||||
stderr : str | ||||
""" | ||||
out_err = process_handler(cmd, lambda p: p.communicate()) | ||||
if out_err is None: | ||||
Thomas Kluyver
|
r4731 | return '', '' | ||
out, err = out_err | ||||
return py3compat.bytes_to_str(out), py3compat.bytes_to_str(err) | ||||
Jörgen Stenarson
|
r5537 | |||
MinRK
|
r5672 | def arg_split(s, posix=False, strict=True): | ||
Jörgen Stenarson
|
r5537 | """Split a command line's arguments in a shell-like manner. | ||
This is a modified version of the standard library's shlex.split() | ||||
function, but with a default of posix=False for splitting, so that quotes | ||||
MinRK
|
r5672 | in inputs are respected. | ||
if strict=False, then any errors shlex.split would raise will result in the | ||||
unparsed remainder being the last element of the list, rather than raising. | ||||
This is because we sometimes use arg_split to parse things other than | ||||
command-line args. | ||||
""" | ||||
Jörgen Stenarson
|
r5537 | |||
# Unfortunately, python's shlex module is buggy with unicode input: | ||||
# http://bugs.python.org/issue1170 | ||||
# At least encoding the input when it's unicode seems to help, but there | ||||
# may be more problems lurking. Apparently this is fixed in python3. | ||||
is_unicode = False | ||||
if (not py3compat.PY3) and isinstance(s, unicode): | ||||
is_unicode = True | ||||
s = s.encode('utf-8') | ||||
lex = shlex.shlex(s, posix=posix) | ||||
lex.whitespace_split = True | ||||
MinRK
|
r5672 | # Extract tokens, ensuring that things like leaving open quotes | ||
# does not cause this to raise. This is important, because we | ||||
# sometimes pass Python source through this (e.g. %timeit f(" ")), | ||||
# and it shouldn't raise an exception. | ||||
# It may be a bad idea to parse things that are not command-line args | ||||
# through this function, but we do, so let's be safe about it. | ||||
tokens = [] | ||||
while True: | ||||
try: | ||||
tokens.append(lex.next()) | ||||
except StopIteration: | ||||
break | ||||
except ValueError: | ||||
if strict: | ||||
raise | ||||
# couldn't parse, get remaining blob as last token | ||||
tokens.append(lex.token) | ||||
break | ||||
Jörgen Stenarson
|
r5537 | if is_unicode: | ||
# Convert the tokens back to unicode. | ||||
tokens = [x.decode('utf-8') for x in tokens] | ||||
return tokens | ||||