|
"""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. |
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import subprocess |
|
import shlex |
|
import sys |
|
import os |
|
|
|
from IPython.utils import py3compat |
|
|
|
|
|
|
|
|
|
|
|
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 as 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 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. |
|
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() |
|
|
|
close_fds = sys.platform != 'win32' |
|
|
|
shell = isinstance(cmd, str) |
|
|
|
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, |
|
stdin=subprocess.PIPE, |
|
stdout=subprocess.PIPE, |
|
stderr=stderr, |
|
close_fds=close_fds) |
|
|
|
try: |
|
out = callback(p) |
|
except KeyboardInterrupt: |
|
print('^C') |
|
sys.stdout.flush() |
|
sys.stderr.flush() |
|
out = None |
|
finally: |
|
|
|
|
|
|
|
|
|
if p.returncode is None: |
|
try: |
|
p.terminate() |
|
p.poll() |
|
except OSError: |
|
pass |
|
|
|
if p.returncode is None: |
|
try: |
|
p.kill() |
|
except OSError: |
|
pass |
|
|
|
return out |
|
|
|
|
|
def getoutput(cmd): |
|
"""Run a command and return its stdout/stderr as a string. |
|
|
|
Parameters |
|
---------- |
|
cmd : str or list |
|
A command to be executed in the system shell. |
|
|
|
Returns |
|
------- |
|
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). |
|
""" |
|
out = process_handler(cmd, lambda p: p.communicate()[0], subprocess.STDOUT) |
|
if out is None: |
|
return '' |
|
return py3compat.decode(out) |
|
|
|
|
|
def getoutputerror(cmd): |
|
"""Return (standard output, standard error) of executing cmd in a shell. |
|
|
|
Accepts the same arguments as os.system(). |
|
|
|
Parameters |
|
---------- |
|
cmd : str or list |
|
A command to be executed in the system shell. |
|
|
|
Returns |
|
------- |
|
stdout : str |
|
stderr : str |
|
""" |
|
return get_output_error_code(cmd)[:2] |
|
|
|
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 |
|
---------- |
|
cmd : str or list |
|
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)) |
|
if out_err is None: |
|
return '', '', p.returncode |
|
out, err = out_err |
|
return py3compat.decode(out), py3compat.decode(err), p.returncode |
|
|
|
def arg_split(s, posix=False, strict=True): |
|
"""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 |
|
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. |
|
""" |
|
|
|
lex = shlex.shlex(s, posix=posix) |
|
lex.whitespace_split = True |
|
|
|
|
|
|
|
|
|
|
|
|
|
lex.commenters='' |
|
tokens = [] |
|
while True: |
|
try: |
|
tokens.append(next(lex)) |
|
except StopIteration: |
|
break |
|
except ValueError: |
|
if strict: |
|
raise |
|
|
|
tokens.append(lex.token) |
|
break |
|
|
|
return tokens |
|
|