##// END OF EJS Templates
Moving posix version of arg_split to _process_common.
Jörgen Stenarson -
Show More
@@ -1,145 +1,170 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 import shlex
18 19 import sys
19 20
20 21 from IPython.utils import py3compat
21 22
22 23 #-----------------------------------------------------------------------------
23 24 # Function definitions
24 25 #-----------------------------------------------------------------------------
25 26
26 27 def read_no_interrupt(p):
27 28 """Read from a pipe ignoring EINTR errors.
28 29
29 30 This is necessary because when reading from pipes with GUI event loops
30 31 running in the background, often interrupts are raised that stop the
31 32 command from completing."""
32 33 import errno
33 34
34 35 try:
35 36 return p.read()
36 37 except IOError, err:
37 38 if err.errno != errno.EINTR:
38 39 raise
39 40
40 41
41 42 def process_handler(cmd, callback, stderr=subprocess.PIPE):
42 43 """Open a command in a shell subprocess and execute a callback.
43 44
44 45 This function provides common scaffolding for creating subprocess.Popen()
45 46 calls. It creates a Popen object and then calls the callback with it.
46 47
47 48 Parameters
48 49 ----------
49 50 cmd : str
50 51 A string to be executed with the underlying system shell (by calling
51 52 :func:`Popen` with ``shell=True``.
52 53
53 54 callback : callable
54 55 A one-argument function that will be called with the Popen object.
55 56
56 57 stderr : file descriptor number, optional
57 58 By default this is set to ``subprocess.PIPE``, but you can also pass the
58 59 value ``subprocess.STDOUT`` to force the subprocess' stderr to go into
59 60 the same file descriptor as its stdout. This is useful to read stdout
60 61 and stderr combined in the order they are generated.
61 62
62 63 Returns
63 64 -------
64 65 The return value of the provided callback is returned.
65 66 """
66 67 sys.stdout.flush()
67 68 sys.stderr.flush()
68 69 # On win32, close_fds can't be true when using pipes for stdin/out/err
69 70 close_fds = sys.platform != 'win32'
70 71 p = subprocess.Popen(cmd, shell=True,
71 72 stdin=subprocess.PIPE,
72 73 stdout=subprocess.PIPE,
73 74 stderr=stderr,
74 75 close_fds=close_fds)
75 76
76 77 try:
77 78 out = callback(p)
78 79 except KeyboardInterrupt:
79 80 print('^C')
80 81 sys.stdout.flush()
81 82 sys.stderr.flush()
82 83 out = None
83 84 finally:
84 85 # Make really sure that we don't leave processes behind, in case the
85 86 # call above raises an exception
86 87 # We start by assuming the subprocess finished (to avoid NameErrors
87 88 # later depending on the path taken)
88 89 if p.returncode is None:
89 90 try:
90 91 p.terminate()
91 92 p.poll()
92 93 except OSError:
93 94 pass
94 95 # One last try on our way out
95 96 if p.returncode is None:
96 97 try:
97 98 p.kill()
98 99 except OSError:
99 100 pass
100 101
101 102 return out
102 103
103 104
104 105 def getoutput(cmd):
105 106 """Return standard output of executing cmd in a shell.
106 107
107 108 Accepts the same arguments as os.system().
108 109
109 110 Parameters
110 111 ----------
111 112 cmd : str
112 113 A command to be executed in the system shell.
113 114
114 115 Returns
115 116 -------
116 117 stdout : str
117 118 """
118 119
119 120 out = process_handler(cmd, lambda p: p.communicate()[0], subprocess.STDOUT)
120 121 if out is None:
121 122 return ''
122 123 return py3compat.bytes_to_str(out)
123 124
124 125
125 126 def getoutputerror(cmd):
126 127 """Return (standard output, standard error) of executing cmd in a shell.
127 128
128 129 Accepts the same arguments as os.system().
129 130
130 131 Parameters
131 132 ----------
132 133 cmd : str
133 134 A command to be executed in the system shell.
134 135
135 136 Returns
136 137 -------
137 138 stdout : str
138 139 stderr : str
139 140 """
140 141
141 142 out_err = process_handler(cmd, lambda p: p.communicate())
142 143 if out_err is None:
143 144 return '', ''
144 145 out, err = out_err
145 146 return py3compat.bytes_to_str(out), py3compat.bytes_to_str(err)
147
148
149 def arg_split(s, posix=False):
150 """Split a command line's arguments in a shell-like manner.
151
152 This is a modified version of the standard library's shlex.split()
153 function, but with a default of posix=False for splitting, so that quotes
154 in inputs are respected."""
155
156 # Unfortunately, python's shlex module is buggy with unicode input:
157 # http://bugs.python.org/issue1170
158 # At least encoding the input when it's unicode seems to help, but there
159 # may be more problems lurking. Apparently this is fixed in python3.
160 is_unicode = False
161 if (not py3compat.PY3) and isinstance(s, unicode):
162 is_unicode = True
163 s = s.encode('utf-8')
164 lex = shlex.shlex(s, posix=posix)
165 lex.whitespace_split = True
166 tokens = list(lex)
167 if is_unicode:
168 # Convert the tokens back to unicode.
169 tokens = [x.decode('utf-8') for x in tokens]
170 return tokens
@@ -1,221 +1,197 b''
1 1 """Posix-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 subprocess as sp
20 20 import sys
21 import shlex
22 21
23 22 from IPython.external import pexpect
24 23
25 24 # Our own
26 25 from .autoattr import auto_attr
27 from ._process_common import getoutput
26 from ._process_common import getoutput, arg_split
28 27 from IPython.utils import text
29 28 from IPython.utils import py3compat
30 29
31 30 #-----------------------------------------------------------------------------
32 31 # Function definitions
33 32 #-----------------------------------------------------------------------------
34 33
35 34 def _find_cmd(cmd):
36 35 """Find the full path to a command using which."""
37 36
38 37 path = sp.Popen(['/usr/bin/env', 'which', cmd],
39 38 stdout=sp.PIPE).communicate()[0]
40 39 return py3compat.bytes_to_str(path)
41 40
42 41
43 42 class ProcessHandler(object):
44 43 """Execute subprocesses under the control of pexpect.
45 44 """
46 45 # Timeout in seconds to wait on each reading of the subprocess' output.
47 46 # This should not be set too low to avoid cpu overusage from our side,
48 47 # since we read in a loop whose period is controlled by this timeout.
49 48 read_timeout = 0.05
50 49
51 50 # Timeout to give a process if we receive SIGINT, between sending the
52 51 # SIGINT to the process and forcefully terminating it.
53 52 terminate_timeout = 0.2
54 53
55 54 # File object where stdout and stderr of the subprocess will be written
56 55 logfile = None
57 56
58 57 # Shell to call for subprocesses to execute
59 58 sh = None
60 59
61 60 @auto_attr
62 61 def sh(self):
63 62 sh = pexpect.which('sh')
64 63 if sh is None:
65 64 raise OSError('"sh" shell not found')
66 65 return sh
67 66
68 67 def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None):
69 68 """Arguments are used for pexpect calls."""
70 69 self.read_timeout = (ProcessHandler.read_timeout if read_timeout is
71 70 None else read_timeout)
72 71 self.terminate_timeout = (ProcessHandler.terminate_timeout if
73 72 terminate_timeout is None else
74 73 terminate_timeout)
75 74 self.logfile = sys.stdout if logfile is None else logfile
76 75
77 76 def getoutput(self, cmd):
78 77 """Run a command and return its stdout/stderr as a string.
79 78
80 79 Parameters
81 80 ----------
82 81 cmd : str
83 82 A command to be executed in the system shell.
84 83
85 84 Returns
86 85 -------
87 86 output : str
88 87 A string containing the combination of stdout and stderr from the
89 88 subprocess, in whatever order the subprocess originally wrote to its
90 89 file descriptors (so the order of the information in this string is the
91 90 correct order as would be seen if running the command in a terminal).
92 91 """
93 92 try:
94 93 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
95 94 except KeyboardInterrupt:
96 95 print('^C', file=sys.stderr, end='')
97 96
98 97 def getoutput_pexpect(self, cmd):
99 98 """Run a command and return its stdout/stderr as a string.
100 99
101 100 Parameters
102 101 ----------
103 102 cmd : str
104 103 A command to be executed in the system shell.
105 104
106 105 Returns
107 106 -------
108 107 output : str
109 108 A string containing the combination of stdout and stderr from the
110 109 subprocess, in whatever order the subprocess originally wrote to its
111 110 file descriptors (so the order of the information in this string is the
112 111 correct order as would be seen if running the command in a terminal).
113 112 """
114 113 try:
115 114 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
116 115 except KeyboardInterrupt:
117 116 print('^C', file=sys.stderr, end='')
118 117
119 118 def system(self, cmd):
120 119 """Execute a command in a subshell.
121 120
122 121 Parameters
123 122 ----------
124 123 cmd : str
125 124 A command to be executed in the system shell.
126 125
127 126 Returns
128 127 -------
129 128 int : child's exitstatus
130 129 """
131 130 # Get likely encoding for the output.
132 131 enc = text.getdefaultencoding()
133 132
134 133 # Patterns to match on the output, for pexpect. We read input and
135 134 # allow either a short timeout or EOF
136 135 patterns = [pexpect.TIMEOUT, pexpect.EOF]
137 136 # the index of the EOF pattern in the list.
138 137 # even though we know it's 1, this call means we don't have to worry if
139 138 # we change the above list, and forget to change this value:
140 139 EOF_index = patterns.index(pexpect.EOF)
141 140 # The size of the output stored so far in the process output buffer.
142 141 # Since pexpect only appends to this buffer, each time we print we
143 142 # record how far we've printed, so that next time we only print *new*
144 143 # content from the buffer.
145 144 out_size = 0
146 145 try:
147 146 # Since we're not really searching the buffer for text patterns, we
148 147 # can set pexpect's search window to be tiny and it won't matter.
149 148 # We only search for the 'patterns' timeout or EOF, which aren't in
150 149 # the text itself.
151 150 #child = pexpect.spawn(pcmd, searchwindowsize=1)
152 151 if hasattr(pexpect, 'spawnb'):
153 152 child = pexpect.spawnb(self.sh, args=['-c', cmd]) # Pexpect-U
154 153 else:
155 154 child = pexpect.spawn(self.sh, args=['-c', cmd]) # Vanilla Pexpect
156 155 flush = sys.stdout.flush
157 156 while True:
158 157 # res is the index of the pattern that caused the match, so we
159 158 # know whether we've finished (if we matched EOF) or not
160 159 res_idx = child.expect_list(patterns, self.read_timeout)
161 160 print(child.before[out_size:].decode(enc, 'replace'), end='')
162 161 flush()
163 162 if res_idx==EOF_index:
164 163 break
165 164 # Update the pointer to what we've already printed
166 165 out_size = len(child.before)
167 166 except KeyboardInterrupt:
168 167 # We need to send ^C to the process. The ascii code for '^C' is 3
169 168 # (the character is known as ETX for 'End of Text', see
170 169 # curses.ascii.ETX).
171 170 child.sendline(chr(3))
172 171 # Read and print any more output the program might produce on its
173 172 # way out.
174 173 try:
175 174 out_size = len(child.before)
176 175 child.expect_list(patterns, self.terminate_timeout)
177 176 print(child.before[out_size:].decode(enc, 'replace'), end='')
178 177 sys.stdout.flush()
179 178 except KeyboardInterrupt:
180 179 # Impatient users tend to type it multiple times
181 180 pass
182 181 finally:
183 182 # Ensure the subprocess really is terminated
184 183 child.terminate(force=True)
185 184 # add isalive check, to ensure exitstatus is set:
186 185 child.isalive()
187 186 return child.exitstatus
188 187
189 188
190 189 # Make system() with a functional interface for outside use. Note that we use
191 190 # getoutput() from the _common utils, which is built on top of popen(). Using
192 191 # pexpect to get subprocess output produces difficult to parse output, since
193 192 # programs think they are talking to a tty and produce highly formatted output
194 193 # (ls is a good example) that makes them hard.
195 194 system = ProcessHandler().system
196 195
197 def arg_split(s, posix=False):
198 """Split a command line's arguments in a shell-like manner.
199
200 This is a modified version of the standard library's shlex.split()
201 function, but with a default of posix=False for splitting, so that quotes
202 in inputs are respected."""
203
204 # Unfortunately, python's shlex module is buggy with unicode input:
205 # http://bugs.python.org/issue1170
206 # At least encoding the input when it's unicode seems to help, but there
207 # may be more problems lurking. Apparently this is fixed in python3.
208 is_unicode = False
209 if (not py3compat.PY3) and isinstance(s, unicode):
210 is_unicode = True
211 s = s.encode('utf-8')
212 lex = shlex.shlex(s, posix=posix)
213 lex.whitespace_split = True
214 tokens = list(lex)
215 if is_unicode:
216 # Convert the tokens back to unicode.
217 tokens = [x.decode('utf-8') for x in tokens]
218 return tokens
219
220 196
221 197
@@ -1,201 +1,178 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
29 29 from . import py3compat
30 30 from . import text
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 Example
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 = os.getcwdu()
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 = text.getdefaultencoding()
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 115 cmd : str
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 with AvoidUNCPath() as path:
125 125 if path is not None:
126 126 cmd = '"pushd %s &&"%s' % (path, cmd)
127 127 return process_handler(cmd, _system_body)
128 128
129 129
130 130 def getoutput(cmd):
131 131 """Return standard output of executing cmd in a shell.
132 132
133 133 Accepts the same arguments as os.system().
134 134
135 135 Parameters
136 136 ----------
137 137 cmd : str
138 138 A command to be executed in the system shell.
139 139
140 140 Returns
141 141 -------
142 142 stdout : str
143 143 """
144 144
145 145 with AvoidUNCPath() as path:
146 146 if path is not None:
147 147 cmd = '"pushd %s &&"%s' % (path, cmd)
148 148 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
149 149
150 150 if out is None:
151 151 out = ''
152 152 return out
153 153
154 154 try:
155 155 CommandLineToArgvW = ctypes.windll.shell32.CommandLineToArgvW
156 156 CommandLineToArgvW.arg_types = [LPCWSTR, POINTER(c_int)]
157 157 CommandLineToArgvW.res_types = [POINTER(LPCWSTR)]
158 158 LocalFree = ctypes.windll.kernel32.LocalFree
159 159 LocalFree.res_type = HLOCAL
160 160 LocalFree.arg_types = [HLOCAL]
161 161
162 162 def arg_split(commandline, posix=False):
163 163 """Split a command line's arguments in a shell-like manner.
164 164
165 165 This is a special version for windows that use a ctypes call to CommandLineToArgvW
166 166 to do the argv splitting. The posix paramter is ignored.
167 167 """
168 168 #CommandLineToArgvW returns path to executable if called with empty string.
169 169 if commandline.strip() == "":
170 170 return []
171 171 argvn = c_int()
172 172 result_pointer = CommandLineToArgvW(py3compat.cast_unicode(commandline.lstrip()), ctypes.byref(argvn))
173 173 result_array_type = LPCWSTR * argvn.value
174 174 result = [arg for arg in result_array_type.from_address(result_pointer)]
175 175 retval = LocalFree(result_pointer)
176 176 return result
177 177 except AttributeError:
178 import shlex
179 #alternative if CommandLineToArgvW is not available
180 def arg_split(s, posix=False):
181 """Split a command line's arguments in a shell-like manner.
182
183 This is a modified version of the standard library's shlex.split()
184 function, but with a default of posix=False for splitting, so that quotes
185 in inputs are respected."""
186
187 # Unfortunately, python's shlex module is buggy with unicode input:
188 # http://bugs.python.org/issue1170
189 # At least encoding the input when it's unicode seems to help, but there
190 # may be more problems lurking. Apparently this is fixed in python3.
191 is_unicode = False
192 if (not py3compat.PY3) and isinstance(s, unicode):
193 is_unicode = True
194 s = s.encode('utf-8')
195 lex = shlex.shlex(s, posix=posix)
196 lex.whitespace_split = True
197 tokens = list(lex)
198 if is_unicode:
199 # Convert the tokens back to unicode.
200 tokens = [x.decode('utf-8') for x in tokens]
201 return tokens
178 from ._process_common import arg_split
General Comments 0
You need to be logged in to leave comments. Login now