_process_common.py
223 lines
| 7.4 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 | ||
Pavol Juhas
|
r22413 | import os | ||
Fernando Perez
|
r2908 | |||
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() | ||||
Matthias BUSSONNIER
|
r7787 | except IOError as err: | ||
Fernando Perez
|
r2908 | 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 | ||||
---------- | ||||
Thomas Kluyver
|
r13738 | cmd : str or list | ||
A command to be executed by the system, using :class:`subprocess.Popen`. | ||||
If a string is passed, it will be run in the system shell. If a list is | ||||
passed, it will be used directly as arguments. | ||||
Fernando Perez
|
r2908 | |||
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' | ||||
Pavol Juhas
|
r22414 | # Determine if cmd should be run with system shell. | ||
shell = isinstance(cmd, py3compat.string_types) | ||||
# On POSIX systems run shell commands with user-preferred shell. | ||||
executable = None | ||||
if shell and os.name == 'posix' and 'SHELL' in os.environ: | ||||
executable = os.environ['SHELL'] | ||||
p = subprocess.Popen(cmd, shell=shell, | ||||
executable=executable, | ||||
Fernando Perez
|
r2908 | 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): | ||
Julian Taylor
|
r10606 | """Run a command and return its stdout/stderr as a string. | ||
Fernando Perez
|
r3002 | |||
Parameters | ||||
---------- | ||||
Thomas Kluyver
|
r13738 | cmd : str or list | ||
Fernando Perez
|
r3002 | A command to be executed in the system shell. | ||
Returns | ||||
------- | ||||
Julian Taylor
|
r10606 | output : str | ||
A string containing the combination of stdout and stderr from the | ||||
subprocess, in whatever order the subprocess originally wrote to its | ||||
file descriptors (so the order of the information in this string is the | ||||
correct order as would be seen if running the command in a terminal). | ||||
Fernando Perez
|
r3002 | """ | ||
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 | ||||
---------- | ||||
Thomas Kluyver
|
r13738 | cmd : str or list | ||
Fernando Perez
|
r2908 | A command to be executed in the system shell. | ||
Returns | ||||
------- | ||||
stdout : str | ||||
stderr : str | ||||
""" | ||||
Paul Ivanov
|
r11825 | return get_output_error_code(cmd)[:2] | ||
Fernando Perez
|
r2908 | |||
Paul Ivanov
|
r11825 | def get_output_error_code(cmd): | ||
"""Return (standard output, standard error, return code) of executing cmd | ||||
in a shell. | ||||
Accepts the same arguments as os.system(). | ||||
Parameters | ||||
---------- | ||||
Thomas Kluyver
|
r13738 | cmd : str or list | ||
Paul Ivanov
|
r11825 | A command to be executed in the system shell. | ||
Returns | ||||
------- | ||||
stdout : str | ||||
stderr : str | ||||
returncode: int | ||||
""" | ||||
out_err, p = process_handler(cmd, lambda p: (p.communicate(), p)) | ||||
Fernando Perez
|
r2908 | if out_err is None: | ||
Paul Ivanov
|
r11825 | return '', '', p.returncode | ||
Thomas Kluyver
|
r4731 | out, err = out_err | ||
Paul Ivanov
|
r11825 | return py3compat.bytes_to_str(out), py3compat.bytes_to_str(err), p.returncode | ||
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. | ||||
Paul Ivanov
|
r5902 | lex.commenters='' #fix for GH-1269 | ||
MinRK
|
r5672 | tokens = [] | ||
while True: | ||||
try: | ||||
Bradley M. Froehle
|
r7847 | tokens.append(next(lex)) | ||
MinRK
|
r5672 | 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 | ||||