1"""Common utilities for the various process_* implementations.
2
3This file is only meant to be imported by the platform-specific implementations
4of subprocess utilities, and it contains tools that are common to all of them.
5"""
6
7#-----------------------------------------------------------------------------
8# Copyright (C) 2010-2011 The IPython Development Team
9#
10# Distributed under the terms of the BSD License. The full license is in
11# the file COPYING, distributed as part of this software.
12#-----------------------------------------------------------------------------
13
14#-----------------------------------------------------------------------------
15# Imports
16#-----------------------------------------------------------------------------
17import os
18import shlex
19import subprocess
20import sys
21from typing import IO, Any, Callable, List, Union
22
23from IPython.utils import py3compat
24
25#-----------------------------------------------------------------------------
26# Function definitions
27#-----------------------------------------------------------------------------
28
29def read_no_interrupt(stream: IO[Any]) -> bytes:
30 """Read from a pipe ignoring EINTR errors.
31
32 This is necessary because when reading from pipes with GUI event loops
33 running in the background, often interrupts are raised that stop the
34 command from completing."""
35 import errno
36
37 try:
38 return stream.read()
39 except IOError as err:
40 if err.errno != errno.EINTR:
41 raise
42
43
44def process_handler(
45 cmd: Union[str, List[str]],
46 callback: Callable[[subprocess.Popen], int | str | bytes],
47 stderr=subprocess.PIPE,
48) -> int | str | bytes:
49 """Open a command in a shell subprocess and execute a callback.
50
51 This function provides common scaffolding for creating subprocess.Popen()
52 calls. It creates a Popen object and then calls the callback with it.
53
54 Parameters
55 ----------
56 cmd : str or list
57 A command to be executed by the system, using :class:`subprocess.Popen`.
58 If a string is passed, it will be run in the system shell. If a list is
59 passed, it will be used directly as arguments.
60 callback : callable
61 A one-argument function that will be called with the Popen object.
62 stderr : file descriptor number, optional
63 By default this is set to ``subprocess.PIPE``, but you can also pass the
64 value ``subprocess.STDOUT`` to force the subprocess' stderr to go into
65 the same file descriptor as its stdout. This is useful to read stdout
66 and stderr combined in the order they are generated.
67
68 Returns
69 -------
70 The return value of the provided callback is returned.
71 """
72 sys.stdout.flush()
73 sys.stderr.flush()
74 # On win32, close_fds can't be true when using pipes for stdin/out/err
75 if sys.platform == "win32" and stderr != subprocess.PIPE:
76 close_fds = False
77 else:
78 close_fds = True
79 # Determine if cmd should be run with system shell.
80 shell = isinstance(cmd, str)
81 # On POSIX systems run shell commands with user-preferred shell.
82 executable = None
83 if shell and os.name == 'posix' and 'SHELL' in os.environ:
84 executable = os.environ['SHELL']
85 p = subprocess.Popen(cmd, shell=shell,
86 executable=executable,
87 stdin=subprocess.PIPE,
88 stdout=subprocess.PIPE,
89 stderr=stderr,
90 close_fds=close_fds)
91
92 try:
93 out = callback(p)
94 except KeyboardInterrupt:
95 print('^C')
96 sys.stdout.flush()
97 sys.stderr.flush()
98 out = None
99 finally:
100 # Make really sure that we don't leave processes behind, in case the
101 # call above raises an exception
102 # We start by assuming the subprocess finished (to avoid NameErrors
103 # later depending on the path taken)
104 if p.returncode is None:
105 try:
106 p.terminate()
107 p.poll()
108 except OSError:
109 pass
110 # One last try on our way out
111 if p.returncode is None:
112 try:
113 p.kill()
114 except OSError:
115 pass
116
117 return out
118
119
120def getoutput(cmd):
121 """Run a command and return its stdout/stderr as a string.
122
123 Parameters
124 ----------
125 cmd : str or list
126 A command to be executed in the system shell.
127
128 Returns
129 -------
130 output : str
131 A string containing the combination of stdout and stderr from the
132 subprocess, in whatever order the subprocess originally wrote to its
133 file descriptors (so the order of the information in this string is the
134 correct order as would be seen if running the command in a terminal).
135 """
136 out = process_handler(cmd, lambda p: p.communicate()[0], subprocess.STDOUT)
137 if out is None:
138 return ''
139 assert isinstance(out, bytes)
140 return py3compat.decode(out)
141
142
143def getoutputerror(cmd):
144 """Return (standard output, standard error) of executing cmd in a shell.
145
146 Accepts the same arguments as os.system().
147
148 Parameters
149 ----------
150 cmd : str or list
151 A command to be executed in the system shell.
152
153 Returns
154 -------
155 stdout : str
156 stderr : str
157 """
158 return get_output_error_code(cmd)[:2]
159
160def get_output_error_code(cmd):
161 """Return (standard output, standard error, return code) of executing cmd
162 in a shell.
163
164 Accepts the same arguments as os.system().
165
166 Parameters
167 ----------
168 cmd : str or list
169 A command to be executed in the system shell.
170
171 Returns
172 -------
173 stdout : str
174 stderr : str
175 returncode: int
176 """
177
178 out_err, p = process_handler(cmd, lambda p: (p.communicate(), p))
179 if out_err is None:
180 return '', '', p.returncode
181 out, err = out_err
182 return py3compat.decode(out), py3compat.decode(err), p.returncode
183
184def arg_split(s, posix=False, strict=True):
185 """Split a command line's arguments in a shell-like manner.
186
187 This is a modified version of the standard library's shlex.split()
188 function, but with a default of posix=False for splitting, so that quotes
189 in inputs are respected.
190
191 if strict=False, then any errors shlex.split would raise will result in the
192 unparsed remainder being the last element of the list, rather than raising.
193 This is because we sometimes use arg_split to parse things other than
194 command-line args.
195 """
196
197 lex = shlex.shlex(s, posix=posix)
198 lex.whitespace_split = True
199 # Extract tokens, ensuring that things like leaving open quotes
200 # does not cause this to raise. This is important, because we
201 # sometimes pass Python source through this (e.g. %timeit f(" ")),
202 # and it shouldn't raise an exception.
203 # It may be a bad idea to parse things that are not command-line args
204 # through this function, but we do, so let's be safe about it.
205 lex.commenters='' #fix for GH-1269
206 tokens = []
207 while True:
208 try:
209 tokens.append(next(lex))
210 except StopIteration:
211 break
212 except ValueError:
213 if strict:
214 raise
215 # couldn't parse, get remaining blob as last token
216 tokens.append(lex.token)
217 break
218
219 return tokens