|
|
"""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.
|
|
|
"""
|
|
|
|
|
|
#-----------------------------------------------------------------------------
|
|
|
# Copyright (C) 2010-2011 The IPython Development Team
|
|
|
#
|
|
|
# Distributed under the terms of the BSD License. The full license is in
|
|
|
# the file COPYING, distributed as part of this software.
|
|
|
#-----------------------------------------------------------------------------
|
|
|
|
|
|
#-----------------------------------------------------------------------------
|
|
|
# Imports
|
|
|
#-----------------------------------------------------------------------------
|
|
|
import os
|
|
|
import shlex
|
|
|
import subprocess
|
|
|
import sys
|
|
|
from typing import IO, Any, Callable, List, Union
|
|
|
|
|
|
from IPython.utils import py3compat
|
|
|
|
|
|
#-----------------------------------------------------------------------------
|
|
|
# Function definitions
|
|
|
#-----------------------------------------------------------------------------
|
|
|
|
|
|
def read_no_interrupt(stream: IO[Any]) -> bytes:
|
|
|
"""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 stream.read()
|
|
|
except IOError as err:
|
|
|
if err.errno != errno.EINTR:
|
|
|
raise
|
|
|
|
|
|
|
|
|
def process_handler(
|
|
|
cmd: Union[str, List[str]],
|
|
|
callback: Callable[[subprocess.Popen], int],
|
|
|
stderr=subprocess.PIPE,
|
|
|
) -> int:
|
|
|
"""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()
|
|
|
# On win32, close_fds can't be true when using pipes for stdin/out/err
|
|
|
if sys.platform == "win32" and stderr != subprocess.PIPE:
|
|
|
close_fds = False
|
|
|
else:
|
|
|
close_fds = True
|
|
|
# Determine if cmd should be run with system shell.
|
|
|
shell = isinstance(cmd, str)
|
|
|
# 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,
|
|
|
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:
|
|
|
# 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
|
|
|
|
|
|
|
|
|
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
|
|
|
# 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.
|
|
|
lex.commenters='' #fix for GH-1269
|
|
|
tokens = []
|
|
|
while True:
|
|
|
try:
|
|
|
tokens.append(next(lex))
|
|
|
except StopIteration:
|
|
|
break
|
|
|
except ValueError:
|
|
|
if strict:
|
|
|
raise
|
|
|
# couldn't parse, get remaining blob as last token
|
|
|
tokens.append(lex.token)
|
|
|
break
|
|
|
|
|
|
return tokens
|
|
|
|