"""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