##// END OF EJS Templates
Allow process_handler to accept a list of arguments
Thomas Kluyver -
Show More
@@ -1,214 +1,215 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 cmd : str
51 A string to be executed with the underlying system shell (by calling
52 :func:`Popen` with ``shell=True``.
50 cmd : str or list
51 A command to be executed by the system, using :class:`subprocess.Popen`.
52 If a string is passed, it will be run in the system shell. If a list is
53 passed, it will be used directly as arguments.
53 54
54 55 callback : callable
55 56 A one-argument function that will be called with the Popen object.
56 57
57 58 stderr : file descriptor number, optional
58 59 By default this is set to ``subprocess.PIPE``, but you can also pass the
59 60 value ``subprocess.STDOUT`` to force the subprocess' stderr to go into
60 61 the same file descriptor as its stdout. This is useful to read stdout
61 62 and stderr combined in the order they are generated.
62 63
63 64 Returns
64 65 -------
65 66 The return value of the provided callback is returned.
66 67 """
67 68 sys.stdout.flush()
68 69 sys.stderr.flush()
69 70 # On win32, close_fds can't be true when using pipes for stdin/out/err
70 71 close_fds = sys.platform != 'win32'
71 p = subprocess.Popen(cmd, shell=True,
72 p = subprocess.Popen(cmd, shell=isinstance(cmd, py3compat.string_types),
72 73 stdin=subprocess.PIPE,
73 74 stdout=subprocess.PIPE,
74 75 stderr=stderr,
75 76 close_fds=close_fds)
76 77
77 78 try:
78 79 out = callback(p)
79 80 except KeyboardInterrupt:
80 81 print('^C')
81 82 sys.stdout.flush()
82 83 sys.stderr.flush()
83 84 out = None
84 85 finally:
85 86 # Make really sure that we don't leave processes behind, in case the
86 87 # call above raises an exception
87 88 # We start by assuming the subprocess finished (to avoid NameErrors
88 89 # later depending on the path taken)
89 90 if p.returncode is None:
90 91 try:
91 92 p.terminate()
92 93 p.poll()
93 94 except OSError:
94 95 pass
95 96 # One last try on our way out
96 97 if p.returncode is None:
97 98 try:
98 99 p.kill()
99 100 except OSError:
100 101 pass
101 102
102 103 return out
103 104
104 105
105 106 def getoutput(cmd):
106 107 """Run a command and return its stdout/stderr as a string.
107 108
108 109 Parameters
109 110 ----------
110 cmd : str
111 cmd : str or list
111 112 A command to be executed in the system shell.
112 113
113 114 Returns
114 115 -------
115 116 output : str
116 117 A string containing the combination of stdout and stderr from the
117 118 subprocess, in whatever order the subprocess originally wrote to its
118 119 file descriptors (so the order of the information in this string is the
119 120 correct order as would be seen if running the command in a terminal).
120 121 """
121 122 out = process_handler(cmd, lambda p: p.communicate()[0], subprocess.STDOUT)
122 123 if out is None:
123 124 return ''
124 125 return py3compat.bytes_to_str(out)
125 126
126 127
127 128 def getoutputerror(cmd):
128 129 """Return (standard output, standard error) of executing cmd in a shell.
129 130
130 131 Accepts the same arguments as os.system().
131 132
132 133 Parameters
133 134 ----------
134 cmd : str
135 cmd : str or list
135 136 A command to be executed in the system shell.
136 137
137 138 Returns
138 139 -------
139 140 stdout : str
140 141 stderr : str
141 142 """
142 143 return get_output_error_code(cmd)[:2]
143 144
144 145 def get_output_error_code(cmd):
145 146 """Return (standard output, standard error, return code) of executing cmd
146 147 in a shell.
147 148
148 149 Accepts the same arguments as os.system().
149 150
150 151 Parameters
151 152 ----------
152 cmd : str
153 cmd : str or list
153 154 A command to be executed in the system shell.
154 155
155 156 Returns
156 157 -------
157 158 stdout : str
158 159 stderr : str
159 160 returncode: int
160 161 """
161 162
162 163 out_err, p = process_handler(cmd, lambda p: (p.communicate(), p))
163 164 if out_err is None:
164 165 return '', '', p.returncode
165 166 out, err = out_err
166 167 return py3compat.bytes_to_str(out), py3compat.bytes_to_str(err), p.returncode
167 168
168 169 def arg_split(s, posix=False, strict=True):
169 170 """Split a command line's arguments in a shell-like manner.
170 171
171 172 This is a modified version of the standard library's shlex.split()
172 173 function, but with a default of posix=False for splitting, so that quotes
173 174 in inputs are respected.
174 175
175 176 if strict=False, then any errors shlex.split would raise will result in the
176 177 unparsed remainder being the last element of the list, rather than raising.
177 178 This is because we sometimes use arg_split to parse things other than
178 179 command-line args.
179 180 """
180 181
181 182 # Unfortunately, python's shlex module is buggy with unicode input:
182 183 # http://bugs.python.org/issue1170
183 184 # At least encoding the input when it's unicode seems to help, but there
184 185 # may be more problems lurking. Apparently this is fixed in python3.
185 186 is_unicode = False
186 187 if (not py3compat.PY3) and isinstance(s, unicode):
187 188 is_unicode = True
188 189 s = s.encode('utf-8')
189 190 lex = shlex.shlex(s, posix=posix)
190 191 lex.whitespace_split = True
191 192 # Extract tokens, ensuring that things like leaving open quotes
192 193 # does not cause this to raise. This is important, because we
193 194 # sometimes pass Python source through this (e.g. %timeit f(" ")),
194 195 # and it shouldn't raise an exception.
195 196 # It may be a bad idea to parse things that are not command-line args
196 197 # through this function, but we do, so let's be safe about it.
197 198 lex.commenters='' #fix for GH-1269
198 199 tokens = []
199 200 while True:
200 201 try:
201 202 tokens.append(next(lex))
202 203 except StopIteration:
203 204 break
204 205 except ValueError:
205 206 if strict:
206 207 raise
207 208 # couldn't parse, get remaining blob as last token
208 209 tokens.append(lex.token)
209 210 break
210 211
211 212 if is_unicode:
212 213 # Convert the tokens back to unicode.
213 214 tokens = [x.decode('utf-8') for x in tokens]
214 215 return tokens
@@ -1,187 +1,187 b''
1 1 """Windows-specific implementation of process utilities.
2 2
3 3 This file is only meant to be imported by process.py, not by end-users.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2010-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 ctypes
22 22
23 23 from ctypes import c_int, POINTER
24 24 from ctypes.wintypes import LPCWSTR, HLOCAL
25 25 from subprocess import STDOUT
26 26
27 27 # our own imports
28 28 from ._process_common import read_no_interrupt, process_handler, arg_split as py_arg_split
29 29 from . import py3compat
30 30 from .encoding import DEFAULT_ENCODING
31 31
32 32 #-----------------------------------------------------------------------------
33 33 # Function definitions
34 34 #-----------------------------------------------------------------------------
35 35
36 36 class AvoidUNCPath(object):
37 37 """A context manager to protect command execution from UNC paths.
38 38
39 39 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
40 40 This context manager temporarily changes directory to the 'C:' drive on
41 41 entering, and restores the original working directory on exit.
42 42
43 43 The context manager returns the starting working directory *if* it made a
44 44 change and None otherwise, so that users can apply the necessary adjustment
45 45 to their system calls in the event of a change.
46 46
47 47 Examples
48 48 --------
49 49 ::
50 50 cmd = 'dir'
51 51 with AvoidUNCPath() as path:
52 52 if path is not None:
53 53 cmd = '"pushd %s &&"%s' % (path, cmd)
54 54 os.system(cmd)
55 55 """
56 56 def __enter__(self):
57 57 self.path = py3compat.getcwd()
58 58 self.is_unc_path = self.path.startswith(r"\\")
59 59 if self.is_unc_path:
60 60 # change to c drive (as cmd.exe cannot handle UNC addresses)
61 61 os.chdir("C:")
62 62 return self.path
63 63 else:
64 64 # We return None to signal that there was no change in the working
65 65 # directory
66 66 return None
67 67
68 68 def __exit__(self, exc_type, exc_value, traceback):
69 69 if self.is_unc_path:
70 70 os.chdir(self.path)
71 71
72 72
73 73 def _find_cmd(cmd):
74 74 """Find the full path to a .bat or .exe using the win32api module."""
75 75 try:
76 76 from win32api import SearchPath
77 77 except ImportError:
78 78 raise ImportError('you need to have pywin32 installed for this to work')
79 79 else:
80 80 PATH = os.environ['PATH']
81 81 extensions = ['.exe', '.com', '.bat', '.py']
82 82 path = None
83 83 for ext in extensions:
84 84 try:
85 85 path = SearchPath(PATH, cmd, ext)[0]
86 86 except:
87 87 pass
88 88 if path is None:
89 89 raise OSError("command %r not found" % cmd)
90 90 else:
91 91 return path
92 92
93 93
94 94 def _system_body(p):
95 95 """Callback for _system."""
96 96 enc = DEFAULT_ENCODING
97 97 for line in read_no_interrupt(p.stdout).splitlines():
98 98 line = line.decode(enc, 'replace')
99 99 print(line, file=sys.stdout)
100 100 for line in read_no_interrupt(p.stderr).splitlines():
101 101 line = line.decode(enc, 'replace')
102 102 print(line, file=sys.stderr)
103 103
104 104 # Wait to finish for returncode
105 105 return p.wait()
106 106
107 107
108 108 def system(cmd):
109 109 """Win32 version of os.system() that works with network shares.
110 110
111 111 Note that this implementation returns None, as meant for use in IPython.
112 112
113 113 Parameters
114 114 ----------
115 cmd : str
115 cmd : str or list
116 116 A command to be executed in the system shell.
117 117
118 118 Returns
119 119 -------
120 120 None : we explicitly do NOT return the subprocess status code, as this
121 121 utility is meant to be used extensively in IPython, where any return value
122 122 would trigger :func:`sys.displayhook` calls.
123 123 """
124 124 # The controller provides interactivity with both
125 125 # stdin and stdout
126 126 #import _process_win32_controller
127 127 #_process_win32_controller.system(cmd)
128 128
129 129 with AvoidUNCPath() as path:
130 130 if path is not None:
131 131 cmd = '"pushd %s &&"%s' % (path, cmd)
132 132 return process_handler(cmd, _system_body)
133 133
134 134 def getoutput(cmd):
135 135 """Return standard output of executing cmd in a shell.
136 136
137 137 Accepts the same arguments as os.system().
138 138
139 139 Parameters
140 140 ----------
141 cmd : str
141 cmd : str or list
142 142 A command to be executed in the system shell.
143 143
144 144 Returns
145 145 -------
146 146 stdout : str
147 147 """
148 148
149 149 with AvoidUNCPath() as path:
150 150 if path is not None:
151 151 cmd = '"pushd %s &&"%s' % (path, cmd)
152 152 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
153 153
154 154 if out is None:
155 155 out = b''
156 156 return py3compat.bytes_to_str(out)
157 157
158 158 try:
159 159 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
160 160 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
161 161 CommandLineToArgvW.restype = POINTER(LPCWSTR)
162 162 LocalFree = ctypes.windll.kernel32.LocalFree
163 163 LocalFree.res_type = HLOCAL
164 164 LocalFree.arg_types = [HLOCAL]
165 165
166 166 def arg_split(commandline, posix=False, strict=True):
167 167 """Split a command line's arguments in a shell-like manner.
168 168
169 169 This is a special version for windows that use a ctypes call to CommandLineToArgvW
170 170 to do the argv splitting. The posix paramter is ignored.
171 171
172 172 If strict=False, process_common.arg_split(...strict=False) is used instead.
173 173 """
174 174 #CommandLineToArgvW returns path to executable if called with empty string.
175 175 if commandline.strip() == "":
176 176 return []
177 177 if not strict:
178 178 # not really a cl-arg, fallback on _process_common
179 179 return py_arg_split(commandline, posix=posix, strict=strict)
180 180 argvn = c_int()
181 181 result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn))
182 182 result_array_type = LPCWSTR * argvn.value
183 183 result = [arg for arg in result_array_type.from_address(ctypes.addressof(result_pointer.contents))]
184 184 retval = LocalFree(result_pointer)
185 185 return result
186 186 except AttributeError:
187 187 arg_split = py_arg_split
General Comments 0
You need to be logged in to leave comments. Login now