##// END OF EJS Templates
new util that also passes back the returncode
Paul Ivanov -
Show More
@@ -1,196 +1,214 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 20
21 21 from IPython.utils import py3compat
22 22
23 23 #-----------------------------------------------------------------------------
24 24 # Function definitions
25 25 #-----------------------------------------------------------------------------
26 26
27 27 def read_no_interrupt(p):
28 28 """Read from a pipe ignoring EINTR errors.
29 29
30 30 This is necessary because when reading from pipes with GUI event loops
31 31 running in the background, often interrupts are raised that stop the
32 32 command from completing."""
33 33 import errno
34 34
35 35 try:
36 36 return p.read()
37 37 except IOError as err:
38 38 if err.errno != errno.EINTR:
39 39 raise
40 40
41 41
42 42 def process_handler(cmd, callback, stderr=subprocess.PIPE):
43 43 """Open a command in a shell subprocess and execute a callback.
44 44
45 45 This function provides common scaffolding for creating subprocess.Popen()
46 46 calls. It creates a Popen object and then calls the callback with it.
47 47
48 48 Parameters
49 49 ----------
50 50 cmd : str
51 51 A string to be executed with the underlying system shell (by calling
52 52 :func:`Popen` with ``shell=True``.
53 53
54 54 callback : callable
55 55 A one-argument function that will be called with the Popen object.
56 56
57 57 stderr : file descriptor number, optional
58 58 By default this is set to ``subprocess.PIPE``, but you can also pass the
59 59 value ``subprocess.STDOUT`` to force the subprocess' stderr to go into
60 60 the same file descriptor as its stdout. This is useful to read stdout
61 61 and stderr combined in the order they are generated.
62 62
63 63 Returns
64 64 -------
65 65 The return value of the provided callback is returned.
66 66 """
67 67 sys.stdout.flush()
68 68 sys.stderr.flush()
69 69 # On win32, close_fds can't be true when using pipes for stdin/out/err
70 70 close_fds = sys.platform != 'win32'
71 71 p = subprocess.Popen(cmd, shell=True,
72 72 stdin=subprocess.PIPE,
73 73 stdout=subprocess.PIPE,
74 74 stderr=stderr,
75 75 close_fds=close_fds)
76 76
77 77 try:
78 78 out = callback(p)
79 79 except KeyboardInterrupt:
80 80 print('^C')
81 81 sys.stdout.flush()
82 82 sys.stderr.flush()
83 83 out = None
84 84 finally:
85 85 # Make really sure that we don't leave processes behind, in case the
86 86 # call above raises an exception
87 87 # We start by assuming the subprocess finished (to avoid NameErrors
88 88 # later depending on the path taken)
89 89 if p.returncode is None:
90 90 try:
91 91 p.terminate()
92 92 p.poll()
93 93 except OSError:
94 94 pass
95 95 # One last try on our way out
96 96 if p.returncode is None:
97 97 try:
98 98 p.kill()
99 99 except OSError:
100 100 pass
101 101
102 102 return out
103 103
104 104
105 105 def getoutput(cmd):
106 106 """Run a command and return its stdout/stderr as a string.
107 107
108 108 Parameters
109 109 ----------
110 110 cmd : str
111 111 A command to be executed in the system shell.
112 112
113 113 Returns
114 114 -------
115 115 output : str
116 116 A string containing the combination of stdout and stderr from the
117 117 subprocess, in whatever order the subprocess originally wrote to its
118 118 file descriptors (so the order of the information in this string is the
119 119 correct order as would be seen if running the command in a terminal).
120 120 """
121 121 out = process_handler(cmd, lambda p: p.communicate()[0], subprocess.STDOUT)
122 122 if out is None:
123 123 return ''
124 124 return py3compat.bytes_to_str(out)
125 125
126 126
127 127 def getoutputerror(cmd):
128 128 """Return (standard output, standard error) of executing cmd in a shell.
129 129
130 130 Accepts the same arguments as os.system().
131 131
132 132 Parameters
133 133 ----------
134 134 cmd : str
135 135 A command to be executed in the system shell.
136 136
137 137 Returns
138 138 -------
139 139 stdout : str
140 140 stderr : str
141 141 """
142 return get_output_error_code(cmd)[:2]
142 143
143 out_err = process_handler(cmd, lambda p: p.communicate())
144 def get_output_error_code(cmd):
145 """Return (standard output, standard error, return code) of executing cmd
146 in a shell.
147
148 Accepts the same arguments as os.system().
149
150 Parameters
151 ----------
152 cmd : str
153 A command to be executed in the system shell.
154
155 Returns
156 -------
157 stdout : str
158 stderr : str
159 returncode: int
160 """
161
162 out_err, p = process_handler(cmd, lambda p: (p.communicate(), p))
144 163 if out_err is None:
145 return '', ''
164 return '', '', p.returncode
146 165 out, err = out_err
147 return py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
148
166 return py3compat.bytes_to_str(out), py3compat.bytes_to_str(err), p.returncode
149 167
150 168 def arg_split(s, posix=False, strict=True):
151 169 """Split a command line's arguments in a shell-like manner.
152 170
153 171 This is a modified version of the standard library's shlex.split()
154 172 function, but with a default of posix=False for splitting, so that quotes
155 173 in inputs are respected.
156 174
157 175 if strict=False, then any errors shlex.split would raise will result in the
158 176 unparsed remainder being the last element of the list, rather than raising.
159 177 This is because we sometimes use arg_split to parse things other than
160 178 command-line args.
161 179 """
162 180
163 181 # Unfortunately, python's shlex module is buggy with unicode input:
164 182 # http://bugs.python.org/issue1170
165 183 # At least encoding the input when it's unicode seems to help, but there
166 184 # may be more problems lurking. Apparently this is fixed in python3.
167 185 is_unicode = False
168 186 if (not py3compat.PY3) and isinstance(s, unicode):
169 187 is_unicode = True
170 188 s = s.encode('utf-8')
171 189 lex = shlex.shlex(s, posix=posix)
172 190 lex.whitespace_split = True
173 191 # Extract tokens, ensuring that things like leaving open quotes
174 192 # does not cause this to raise. This is important, because we
175 193 # sometimes pass Python source through this (e.g. %timeit f(" ")),
176 194 # and it shouldn't raise an exception.
177 195 # It may be a bad idea to parse things that are not command-line args
178 196 # through this function, but we do, so let's be safe about it.
179 197 lex.commenters='' #fix for GH-1269
180 198 tokens = []
181 199 while True:
182 200 try:
183 201 tokens.append(next(lex))
184 202 except StopIteration:
185 203 break
186 204 except ValueError:
187 205 if strict:
188 206 raise
189 207 # couldn't parse, get remaining blob as last token
190 208 tokens.append(lex.token)
191 209 break
192 210
193 211 if is_unicode:
194 212 # Convert the tokens back to unicode.
195 213 tokens = [x.decode('utf-8') for x in tokens]
196 214 return tokens
@@ -1,122 +1,122 b''
1 1 # encoding: utf-8
2 2 """
3 3 Utilities for working with external processes.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2008-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 # Stdlib
19 19 import os
20 20 import sys
21 21 import shlex
22 22
23 23 # Our own
24 24 if sys.platform == 'win32':
25 25 from ._process_win32 import _find_cmd, system, getoutput, AvoidUNCPath, arg_split
26 26 else:
27 27 from ._process_posix import _find_cmd, system, getoutput, arg_split
28 28
29 29
30 from ._process_common import getoutputerror
30 from ._process_common import getoutputerror, get_output_error_code
31 31
32 32 #-----------------------------------------------------------------------------
33 33 # Code
34 34 #-----------------------------------------------------------------------------
35 35
36 36
37 37 class FindCmdError(Exception):
38 38 pass
39 39
40 40
41 41 def find_cmd(cmd):
42 42 """Find absolute path to executable cmd in a cross platform manner.
43 43
44 44 This function tries to determine the full path to a command line program
45 45 using `which` on Unix/Linux/OS X and `win32api` on Windows. Most of the
46 46 time it will use the version that is first on the users `PATH`.
47 47
48 48 Warning, don't use this to find IPython command line programs as there
49 49 is a risk you will find the wrong one. Instead find those using the
50 50 following code and looking for the application itself::
51 51
52 52 from IPython.utils.path import get_ipython_module_path
53 53 from IPython.utils.process import pycmd2argv
54 54 argv = pycmd2argv(get_ipython_module_path('IPython.terminal.ipapp'))
55 55
56 56 Parameters
57 57 ----------
58 58 cmd : str
59 59 The command line program to look for.
60 60 """
61 61 try:
62 62 path = _find_cmd(cmd).rstrip()
63 63 except OSError:
64 64 raise FindCmdError('command could not be found: %s' % cmd)
65 65 # which returns empty if not found
66 66 if path == '':
67 67 raise FindCmdError('command could not be found: %s' % cmd)
68 68 return os.path.abspath(path)
69 69
70 70
71 71 def is_cmd_found(cmd):
72 72 """Check whether executable `cmd` exists or not and return a bool."""
73 73 try:
74 74 find_cmd(cmd)
75 75 return True
76 76 except FindCmdError:
77 77 return False
78 78
79 79
80 80 def pycmd2argv(cmd):
81 81 r"""Take the path of a python command and return a list (argv-style).
82 82
83 83 This only works on Python based command line programs and will find the
84 84 location of the ``python`` executable using ``sys.executable`` to make
85 85 sure the right version is used.
86 86
87 87 For a given path ``cmd``, this returns [cmd] if cmd's extension is .exe,
88 88 .com or .bat, and [, cmd] otherwise.
89 89
90 90 Parameters
91 91 ----------
92 92 cmd : string
93 93 The path of the command.
94 94
95 95 Returns
96 96 -------
97 97 argv-style list.
98 98 """
99 99 ext = os.path.splitext(cmd)[1]
100 100 if ext in ['.exe', '.com', '.bat']:
101 101 return [cmd]
102 102 else:
103 103 return [sys.executable, cmd]
104 104
105 105
106 106 def abbrev_cwd():
107 107 """ Return abbreviated version of cwd, e.g. d:mydir """
108 108 cwd = os.getcwdu().replace('\\','/')
109 109 drivepart = ''
110 110 tail = cwd
111 111 if sys.platform == 'win32':
112 112 if len(cwd) < 4:
113 113 return cwd
114 114 drivepart,tail = os.path.splitdrive(cwd)
115 115
116 116
117 117 parts = tail.split('/')
118 118 if len(parts) > 2:
119 119 tail = '/'.join(parts[-2:])
120 120
121 121 return (drivepart + (
122 122 cwd == '/' and '/' or tail))
General Comments 0
You need to be logged in to leave comments. Login now