mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-02 19:39:49 +09:00
514 lines
17 KiB
Python
Vendored
514 lines
17 KiB
Python
Vendored
# subprocess - Subprocesses with accessible I/O streams
|
|
#
|
|
# For more information about this module, see PEP 324.
|
|
#
|
|
# Copyright (c) 2003-2005 by Peter Astrand <astrand@lysator.liu.se>
|
|
#
|
|
# Licensed to PSF under a Contributor Agreement.
|
|
# See http://www.python.org/2.4/license for licensing details.
|
|
|
|
r"""Subprocesses with accessible I/O streams
|
|
|
|
This module allows you to spawn processes, connect to their
|
|
input/output/error pipes, and obtain their return codes.
|
|
|
|
For a complete description of this module see the Python documentation.
|
|
|
|
Main API
|
|
========
|
|
run(...): Runs a command, waits for it to complete, then returns a
|
|
CompletedProcess instance.
|
|
Popen(...): A class for flexibly executing a command in a new process
|
|
|
|
Constants
|
|
---------
|
|
DEVNULL: Special value that indicates that os.devnull should be used
|
|
PIPE: Special value that indicates a pipe should be created
|
|
STDOUT: Special value that indicates that stderr should go to stdout
|
|
|
|
|
|
Older API
|
|
=========
|
|
call(...): Runs a command, waits for it to complete, then returns
|
|
the return code.
|
|
check_call(...): Same as call() but raises CalledProcessError()
|
|
if return code is not 0
|
|
check_output(...): Same as check_call() but returns the contents of
|
|
stdout instead of a return code
|
|
getoutput(...): Runs a command in the shell, waits for it to complete,
|
|
then returns the output
|
|
getstatusoutput(...): Runs a command in the shell, waits for it to complete,
|
|
then returns a (exitcode, output) tuple
|
|
"""
|
|
|
|
import sys
|
|
|
|
import io
|
|
import os
|
|
import time
|
|
import signal
|
|
import builtins
|
|
import warnings
|
|
import errno
|
|
from time import monotonic as _time
|
|
|
|
from _subprocess import *
|
|
|
|
# TODO: use these classes instead of the _subprocess ones
|
|
|
|
# Exception classes used by this module.
|
|
# class SubprocessError(Exception): pass
|
|
|
|
|
|
# class CalledProcessError(SubprocessError):
|
|
# """Raised when run() is called with check=True and the process
|
|
# returns a non-zero exit status.
|
|
|
|
# Attributes:
|
|
# cmd, returncode, stdout, stderr, output
|
|
# """
|
|
# def __init__(self, returncode, cmd, output=None, stderr=None):
|
|
# self.returncode = returncode
|
|
# self.cmd = cmd
|
|
# self.output = output
|
|
# self.stderr = stderr
|
|
|
|
# def __str__(self):
|
|
# if self.returncode and self.returncode < 0:
|
|
# try:
|
|
# return "Command '%s' died with %r." % (
|
|
# self.cmd, signal.Signals(-self.returncode))
|
|
# except ValueError:
|
|
# return "Command '%s' died with unknown signal %d." % (
|
|
# self.cmd, -self.returncode)
|
|
# else:
|
|
# return "Command '%s' returned non-zero exit status %d." % (
|
|
# self.cmd, self.returncode)
|
|
|
|
# @property
|
|
# def stdout(self):
|
|
# """Alias for output attribute, to match stderr"""
|
|
# return self.output
|
|
|
|
# @stdout.setter
|
|
# def stdout(self, value):
|
|
# # There's no obvious reason to set this, but allow it anyway so
|
|
# # .stdout is a transparent alias for .output
|
|
# self.output = value
|
|
|
|
|
|
# class TimeoutExpired(SubprocessError):
|
|
# """This exception is raised when the timeout expires while waiting for a
|
|
# child process.
|
|
|
|
# Attributes:
|
|
# cmd, output, stdout, stderr, timeout
|
|
# """
|
|
# def __init__(self, cmd, timeout, output=None, stderr=None):
|
|
# self.cmd = cmd
|
|
# self.timeout = timeout
|
|
# self.output = output
|
|
# self.stderr = stderr
|
|
|
|
# def __str__(self):
|
|
# return ("Command '%s' timed out after %s seconds" %
|
|
# (self.cmd, self.timeout))
|
|
|
|
# @property
|
|
# def stdout(self):
|
|
# return self.output
|
|
|
|
# @stdout.setter
|
|
# def stdout(self, value):
|
|
# # There's no obvious reason to set this, but allow it anyway so
|
|
# # .stdout is a transparent alias for .output
|
|
# self.output = value
|
|
|
|
|
|
__all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", "getstatusoutput",
|
|
"getoutput", "check_output", "run", "CalledProcessError", "DEVNULL",
|
|
"SubprocessError", "TimeoutExpired", "CompletedProcess"]
|
|
# NOTE: We intentionally exclude list2cmdline as it is
|
|
# considered an internal implementation detail. issue10838.
|
|
|
|
# This lists holds Popen instances for which the underlying process had not
|
|
# exited at the time its __del__ method got called: those processes are wait()ed
|
|
# for synchronously from _cleanup() when a new Popen object is created, to avoid
|
|
# zombie processes.
|
|
_active = []
|
|
|
|
def _cleanup():
|
|
for inst in _active[:]:
|
|
res = inst._internal_poll(_deadstate=sys.maxsize)
|
|
if res is not None:
|
|
try:
|
|
_active.remove(inst)
|
|
except ValueError:
|
|
# This can happen if two threads create a new Popen instance.
|
|
# It's harmless that it was already removed, so ignore.
|
|
pass
|
|
|
|
PIPE = -1
|
|
STDOUT = -2
|
|
DEVNULL = -3
|
|
|
|
|
|
# XXX This function is only used by multiprocessing and the test suite,
|
|
# but it's here so that it can be imported when Python is compiled without
|
|
# threads.
|
|
|
|
def _optim_args_from_interpreter_flags():
|
|
"""Return a list of command-line arguments reproducing the current
|
|
optimization settings in sys.flags."""
|
|
args = []
|
|
value = sys.flags.optimize
|
|
if value > 0:
|
|
args.append('-' + 'O' * value)
|
|
return args
|
|
|
|
|
|
def _args_from_interpreter_flags():
|
|
"""Return a list of command-line arguments reproducing the current
|
|
settings in sys.flags, sys.warnoptions and sys._xoptions."""
|
|
flag_opt_map = {
|
|
'debug': 'd',
|
|
# 'inspect': 'i',
|
|
# 'interactive': 'i',
|
|
'dont_write_bytecode': 'B',
|
|
'no_user_site': 's',
|
|
'no_site': 'S',
|
|
'ignore_environment': 'E',
|
|
'verbose': 'v',
|
|
'bytes_warning': 'b',
|
|
'quiet': 'q',
|
|
# -O is handled in _optim_args_from_interpreter_flags()
|
|
}
|
|
args = _optim_args_from_interpreter_flags()
|
|
for flag, opt in flag_opt_map.items():
|
|
v = getattr(sys.flags, flag)
|
|
if v > 0:
|
|
args.append('-' + opt * v)
|
|
|
|
# -W options
|
|
warnopts = sys.warnoptions[:]
|
|
bytes_warning = sys.flags.bytes_warning
|
|
xoptions = getattr(sys, '_xoptions', {})
|
|
dev_mode = ('dev' in xoptions)
|
|
|
|
if bytes_warning > 1:
|
|
warnopts.remove("error::BytesWarning")
|
|
elif bytes_warning:
|
|
warnopts.remove("default::BytesWarning")
|
|
if dev_mode:
|
|
warnopts.remove('default')
|
|
for opt in warnopts:
|
|
args.append('-W' + opt)
|
|
|
|
# -X options
|
|
if dev_mode:
|
|
args.extend(('-X', 'dev'))
|
|
for opt in ('faulthandler', 'tracemalloc', 'importtime',
|
|
'showalloccount', 'showrefcount', 'utf8'):
|
|
if opt in xoptions:
|
|
value = xoptions[opt]
|
|
if value is True:
|
|
arg = opt
|
|
else:
|
|
arg = '%s=%s' % (opt, value)
|
|
args.extend(('-X', arg))
|
|
|
|
return args
|
|
|
|
|
|
def call(*popenargs, timeout=None, **kwargs):
|
|
"""Run command with arguments. Wait for command to complete or
|
|
timeout, then return the returncode attribute.
|
|
|
|
The arguments are the same as for the Popen constructor. Example:
|
|
|
|
retcode = call(["ls", "-l"])
|
|
"""
|
|
with Popen(*popenargs, **kwargs) as p:
|
|
try:
|
|
return p.wait(timeout=timeout)
|
|
except: # Including KeyboardInterrupt, wait handled that.
|
|
p.kill()
|
|
# We don't call p.wait() again as p.__exit__ does that for us.
|
|
raise
|
|
|
|
|
|
def check_call(*popenargs, **kwargs):
|
|
"""Run command with arguments. Wait for command to complete. If
|
|
the exit code was zero then return, otherwise raise
|
|
CalledProcessError. The CalledProcessError object will have the
|
|
return code in the returncode attribute.
|
|
|
|
The arguments are the same as for the call function. Example:
|
|
|
|
check_call(["ls", "-l"])
|
|
"""
|
|
retcode = call(*popenargs, **kwargs)
|
|
if retcode:
|
|
cmd = kwargs.get("args")
|
|
if cmd is None:
|
|
cmd = popenargs[0]
|
|
raise CalledProcessError(retcode, cmd)
|
|
return 0
|
|
|
|
|
|
def check_output(*popenargs, timeout=None, **kwargs):
|
|
r"""Run command with arguments and return its output.
|
|
|
|
If the exit code was non-zero it raises a CalledProcessError. The
|
|
CalledProcessError object will have the return code in the returncode
|
|
attribute and output in the output attribute.
|
|
|
|
The arguments are the same as for the Popen constructor. Example:
|
|
|
|
>>> check_output(["ls", "-l", "/dev/null"])
|
|
b'crw-rw-rw- 1 root root 1, 3 Oct 18 2007 /dev/null\n'
|
|
|
|
The stdout argument is not allowed as it is used internally.
|
|
To capture standard error in the result, use stderr=STDOUT.
|
|
|
|
>>> check_output(["/bin/sh", "-c",
|
|
... "ls -l non_existent_file ; exit 0"],
|
|
... stderr=STDOUT)
|
|
b'ls: non_existent_file: No such file or directory\n'
|
|
|
|
There is an additional optional argument, "input", allowing you to
|
|
pass a string to the subprocess's stdin. If you use this argument
|
|
you may not also use the Popen constructor's "stdin" argument, as
|
|
it too will be used internally. Example:
|
|
|
|
>>> check_output(["sed", "-e", "s/foo/bar/"],
|
|
... input=b"when in the course of fooman events\n")
|
|
b'when in the course of barman events\n'
|
|
|
|
By default, all communication is in bytes, and therefore any "input"
|
|
should be bytes, and the return value wil be bytes. If in text mode,
|
|
any "input" should be a string, and the return value will be a string
|
|
decoded according to locale encoding, or by "encoding" if set. Text mode
|
|
is triggered by setting any of text, encoding, errors or universal_newlines.
|
|
"""
|
|
if 'stdout' in kwargs:
|
|
raise ValueError('stdout argument not allowed, it will be overridden.')
|
|
|
|
if 'input' in kwargs and kwargs['input'] is None:
|
|
# Explicitly passing input=None was previously equivalent to passing an
|
|
# empty string. That is maintained here for backwards compatibility.
|
|
kwargs['input'] = '' if kwargs.get('universal_newlines', False) else b''
|
|
|
|
return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
|
|
**kwargs).stdout
|
|
|
|
|
|
class CompletedProcess(object):
|
|
"""A process that has finished running.
|
|
|
|
This is returned by run().
|
|
|
|
Attributes:
|
|
args: The list or str args passed to run().
|
|
returncode: The exit code of the process, negative for signals.
|
|
stdout: The standard output (None if not captured).
|
|
stderr: The standard error (None if not captured).
|
|
"""
|
|
def __init__(self, args, returncode, stdout=None, stderr=None):
|
|
self.args = args
|
|
self.returncode = returncode
|
|
self.stdout = stdout
|
|
self.stderr = stderr
|
|
|
|
def __repr__(self):
|
|
args = ['args={!r}'.format(self.args),
|
|
'returncode={!r}'.format(self.returncode)]
|
|
if self.stdout is not None:
|
|
args.append('stdout={!r}'.format(self.stdout))
|
|
if self.stderr is not None:
|
|
args.append('stderr={!r}'.format(self.stderr))
|
|
return "{}({})".format(type(self).__name__, ', '.join(args))
|
|
|
|
def check_returncode(self):
|
|
"""Raise CalledProcessError if the exit code is non-zero."""
|
|
if self.returncode:
|
|
raise CalledProcessError(self.returncode, self.args, self.stdout,
|
|
self.stderr)
|
|
|
|
|
|
def run(*popenargs,
|
|
input=None, capture_output=False, timeout=None, check=False, **kwargs):
|
|
"""Run command with arguments and return a CompletedProcess instance.
|
|
|
|
The returned instance will have attributes args, returncode, stdout and
|
|
stderr. By default, stdout and stderr are not captured, and those attributes
|
|
will be None. Pass stdout=PIPE and/or stderr=PIPE in order to capture them.
|
|
|
|
If check is True and the exit code was non-zero, it raises a
|
|
CalledProcessError. The CalledProcessError object will have the return code
|
|
in the returncode attribute, and output & stderr attributes if those streams
|
|
were captured.
|
|
|
|
If timeout is given, and the process takes too long, a TimeoutExpired
|
|
exception will be raised.
|
|
|
|
There is an optional argument "input", allowing you to
|
|
pass bytes or a string to the subprocess's stdin. If you use this argument
|
|
you may not also use the Popen constructor's "stdin" argument, as
|
|
it will be used internally.
|
|
|
|
By default, all communication is in bytes, and therefore any "input" should
|
|
be bytes, and the stdout and stderr will be bytes. If in text mode, any
|
|
"input" should be a string, and stdout and stderr will be strings decoded
|
|
according to locale encoding, or by "encoding" if set. Text mode is
|
|
triggered by setting any of text, encoding, errors or universal_newlines.
|
|
|
|
The other arguments are the same as for the Popen constructor.
|
|
"""
|
|
if input is not None:
|
|
if 'stdin' in kwargs:
|
|
raise ValueError('stdin and input arguments may not both be used.')
|
|
kwargs['stdin'] = PIPE
|
|
|
|
if capture_output:
|
|
if ('stdout' in kwargs) or ('stderr' in kwargs):
|
|
raise ValueError('stdout and stderr arguments may not be used '
|
|
'with capture_output.')
|
|
kwargs['stdout'] = PIPE
|
|
kwargs['stderr'] = PIPE
|
|
|
|
with Popen(*popenargs, **kwargs) as process:
|
|
try:
|
|
stdout, stderr = process.communicate(input, timeout=timeout)
|
|
except TimeoutExpired:
|
|
process.kill()
|
|
stdout, stderr = process.communicate()
|
|
raise TimeoutExpired(process.args, timeout, output=stdout,
|
|
stderr=stderr)
|
|
except: # Including KeyboardInterrupt, communicate handled that.
|
|
process.kill()
|
|
# We don't call process.wait() as .__exit__ does that for us.
|
|
raise
|
|
retcode = process.poll()
|
|
if check and retcode:
|
|
raise CalledProcessError(retcode, process.args,
|
|
output=stdout, stderr=stderr)
|
|
return CompletedProcess(process.args, retcode, stdout, stderr)
|
|
|
|
|
|
def list2cmdline(seq):
|
|
"""
|
|
Translate a sequence of arguments into a command line
|
|
string, using the same rules as the MS C runtime:
|
|
|
|
1) Arguments are delimited by white space, which is either a
|
|
space or a tab.
|
|
|
|
2) A string surrounded by double quotation marks is
|
|
interpreted as a single argument, regardless of white space
|
|
contained within. A quoted string can be embedded in an
|
|
argument.
|
|
|
|
3) A double quotation mark preceded by a backslash is
|
|
interpreted as a literal double quotation mark.
|
|
|
|
4) Backslashes are interpreted literally, unless they
|
|
immediately precede a double quotation mark.
|
|
|
|
5) If backslashes immediately precede a double quotation mark,
|
|
every pair of backslashes is interpreted as a literal
|
|
backslash. If the number of backslashes is odd, the last
|
|
backslash escapes the next double quotation mark as
|
|
described in rule 3.
|
|
"""
|
|
|
|
# See
|
|
# http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
|
|
# or search http://msdn.microsoft.com for
|
|
# "Parsing C++ Command-Line Arguments"
|
|
result = []
|
|
needquote = False
|
|
for arg in seq:
|
|
bs_buf = []
|
|
|
|
# Add a space to separate this argument from the others
|
|
if result:
|
|
result.append(' ')
|
|
|
|
needquote = (" " in arg) or ("\t" in arg) or not arg
|
|
if needquote:
|
|
result.append('"')
|
|
|
|
for c in arg:
|
|
if c == '\\':
|
|
# Don't know if we need to double yet.
|
|
bs_buf.append(c)
|
|
elif c == '"':
|
|
# Double backslashes.
|
|
result.append('\\' * len(bs_buf)*2)
|
|
bs_buf = []
|
|
result.append('\\"')
|
|
else:
|
|
# Normal char
|
|
if bs_buf:
|
|
result.extend(bs_buf)
|
|
bs_buf = []
|
|
result.append(c)
|
|
|
|
# Add remaining backslashes, if any.
|
|
if bs_buf:
|
|
result.extend(bs_buf)
|
|
|
|
if needquote:
|
|
result.extend(bs_buf)
|
|
result.append('"')
|
|
|
|
return ''.join(result)
|
|
|
|
|
|
# Various tools for executing commands and looking at their output and status.
|
|
#
|
|
|
|
def getstatusoutput(cmd):
|
|
"""Return (exitcode, output) of executing cmd in a shell.
|
|
|
|
Execute the string 'cmd' in a shell with 'check_output' and
|
|
return a 2-tuple (status, output). The locale encoding is used
|
|
to decode the output and process newlines.
|
|
|
|
A trailing newline is stripped from the output.
|
|
The exit status for the command can be interpreted
|
|
according to the rules for the function 'wait'. Example:
|
|
|
|
>>> import subprocess
|
|
>>> subprocess.getstatusoutput('ls /bin/ls')
|
|
(0, '/bin/ls')
|
|
>>> subprocess.getstatusoutput('cat /bin/junk')
|
|
(1, 'cat: /bin/junk: No such file or directory')
|
|
>>> subprocess.getstatusoutput('/bin/junk')
|
|
(127, 'sh: /bin/junk: not found')
|
|
>>> subprocess.getstatusoutput('/bin/kill $$')
|
|
(-15, '')
|
|
"""
|
|
try:
|
|
data = check_output(cmd, shell=True, text=True, stderr=STDOUT)
|
|
exitcode = 0
|
|
except CalledProcessError as ex:
|
|
data = ex.output
|
|
exitcode = ex.returncode
|
|
if data[-1:] == '\n':
|
|
data = data[:-1]
|
|
return exitcode, data
|
|
|
|
def getoutput(cmd):
|
|
"""Return output (stdout or stderr) of executing cmd in a shell.
|
|
|
|
Like getstatusoutput(), except the exit status is ignored and the return
|
|
value is a string containing the command's output. Example:
|
|
|
|
>>> import subprocess
|
|
>>> subprocess.getoutput('ls /bin/ls')
|
|
'/bin/ls'
|
|
"""
|
|
return getstatusoutput(cmd)[1]
|