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