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