##// END OF EJS Templates
`IPython.utils._process_posix`: import `pexpect` only when needed (#14312)...
M Bussonnier -
r28604:4eab6081 merge
parent child Browse files
Show More
@@ -1,216 +1,219 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
17 17 # Stdlib
18 18 import errno
19 19 import os
20 20 import subprocess as sp
21 21 import sys
22 22
23 import pexpect
24
25 23 # Our own
26 24 from ._process_common import getoutput, arg_split
27 25 from IPython.utils.encoding import DEFAULT_ENCODING
28 26
29 27 #-----------------------------------------------------------------------------
30 28 # Function definitions
31 29 #-----------------------------------------------------------------------------
32 30
33 31 class ProcessHandler(object):
34 32 """Execute subprocesses under the control of pexpect.
35 33 """
36 34 # Timeout in seconds to wait on each reading of the subprocess' output.
37 35 # This should not be set too low to avoid cpu overusage from our side,
38 36 # since we read in a loop whose period is controlled by this timeout.
39 37 read_timeout = 0.05
40 38
41 39 # Timeout to give a process if we receive SIGINT, between sending the
42 40 # SIGINT to the process and forcefully terminating it.
43 41 terminate_timeout = 0.2
44 42
45 43 # File object where stdout and stderr of the subprocess will be written
46 44 logfile = None
47 45
48 46 # Shell to call for subprocesses to execute
49 47 _sh = None
50 48
51 49 @property
52 50 def sh(self):
53 51 if self._sh is None:
52 import pexpect
54 53 shell_name = os.environ.get("SHELL", "sh")
55 54 self._sh = pexpect.which(shell_name)
56 55 if self._sh is None:
57 56 raise OSError('"{}" shell not found'.format(shell_name))
58 57
59 58 return self._sh
60 59
61 60 def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None):
62 61 """Arguments are used for pexpect calls."""
63 62 self.read_timeout = (ProcessHandler.read_timeout if read_timeout is
64 63 None else read_timeout)
65 64 self.terminate_timeout = (ProcessHandler.terminate_timeout if
66 65 terminate_timeout is None else
67 66 terminate_timeout)
68 67 self.logfile = sys.stdout if logfile is None else logfile
69 68
70 69 def getoutput(self, cmd):
71 70 """Run a command and return its stdout/stderr as a string.
72 71
73 72 Parameters
74 73 ----------
75 74 cmd : str
76 75 A command to be executed in the system shell.
77 76
78 77 Returns
79 78 -------
80 79 output : str
81 80 A string containing the combination of stdout and stderr from the
82 81 subprocess, in whatever order the subprocess originally wrote to its
83 82 file descriptors (so the order of the information in this string is the
84 83 correct order as would be seen if running the command in a terminal).
85 84 """
85 import pexpect
86 86 try:
87 87 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
88 88 except KeyboardInterrupt:
89 89 print('^C', file=sys.stderr, end='')
90 90
91 91 def getoutput_pexpect(self, cmd):
92 92 """Run a command and return its stdout/stderr as a string.
93 93
94 94 Parameters
95 95 ----------
96 96 cmd : str
97 97 A command to be executed in the system shell.
98 98
99 99 Returns
100 100 -------
101 101 output : str
102 102 A string containing the combination of stdout and stderr from the
103 103 subprocess, in whatever order the subprocess originally wrote to its
104 104 file descriptors (so the order of the information in this string is the
105 105 correct order as would be seen if running the command in a terminal).
106 106 """
107 import pexpect
107 108 try:
108 109 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
109 110 except KeyboardInterrupt:
110 111 print('^C', file=sys.stderr, end='')
111 112
112 113 def system(self, cmd):
113 114 """Execute a command in a subshell.
114 115
115 116 Parameters
116 117 ----------
117 118 cmd : str
118 119 A command to be executed in the system shell.
119 120
120 121 Returns
121 122 -------
122 123 int : child's exitstatus
123 124 """
125 import pexpect
126
124 127 # Get likely encoding for the output.
125 128 enc = DEFAULT_ENCODING
126 129
127 130 # Patterns to match on the output, for pexpect. We read input and
128 131 # allow either a short timeout or EOF
129 132 patterns = [pexpect.TIMEOUT, pexpect.EOF]
130 133 # the index of the EOF pattern in the list.
131 134 # even though we know it's 1, this call means we don't have to worry if
132 135 # we change the above list, and forget to change this value:
133 136 EOF_index = patterns.index(pexpect.EOF)
134 137 # The size of the output stored so far in the process output buffer.
135 138 # Since pexpect only appends to this buffer, each time we print we
136 139 # record how far we've printed, so that next time we only print *new*
137 140 # content from the buffer.
138 141 out_size = 0
139 142 try:
140 143 # Since we're not really searching the buffer for text patterns, we
141 144 # can set pexpect's search window to be tiny and it won't matter.
142 145 # We only search for the 'patterns' timeout or EOF, which aren't in
143 146 # the text itself.
144 147 #child = pexpect.spawn(pcmd, searchwindowsize=1)
145 148 if hasattr(pexpect, 'spawnb'):
146 149 child = pexpect.spawnb(self.sh, args=['-c', cmd]) # Pexpect-U
147 150 else:
148 151 child = pexpect.spawn(self.sh, args=['-c', cmd]) # Vanilla Pexpect
149 152 flush = sys.stdout.flush
150 153 while True:
151 154 # res is the index of the pattern that caused the match, so we
152 155 # know whether we've finished (if we matched EOF) or not
153 156 res_idx = child.expect_list(patterns, self.read_timeout)
154 157 print(child.before[out_size:].decode(enc, 'replace'), end='')
155 158 flush()
156 159 if res_idx==EOF_index:
157 160 break
158 161 # Update the pointer to what we've already printed
159 162 out_size = len(child.before)
160 163 except KeyboardInterrupt:
161 164 # We need to send ^C to the process. The ascii code for '^C' is 3
162 165 # (the character is known as ETX for 'End of Text', see
163 166 # curses.ascii.ETX).
164 167 child.sendline(chr(3))
165 168 # Read and print any more output the program might produce on its
166 169 # way out.
167 170 try:
168 171 out_size = len(child.before)
169 172 child.expect_list(patterns, self.terminate_timeout)
170 173 print(child.before[out_size:].decode(enc, 'replace'), end='')
171 174 sys.stdout.flush()
172 175 except KeyboardInterrupt:
173 176 # Impatient users tend to type it multiple times
174 177 pass
175 178 finally:
176 179 # Ensure the subprocess really is terminated
177 180 child.terminate(force=True)
178 181 # add isalive check, to ensure exitstatus is set:
179 182 child.isalive()
180 183
181 184 # We follow the subprocess pattern, returning either the exit status
182 185 # as a positive number, or the terminating signal as a negative
183 186 # number.
184 187 # on Linux, sh returns 128+n for signals terminating child processes on Linux
185 188 # on BSD (OS X), the signal code is set instead
186 189 if child.exitstatus is None:
187 190 # on WIFSIGNALED, pexpect sets signalstatus, leaving exitstatus=None
188 191 if child.signalstatus is None:
189 192 # this condition may never occur,
190 193 # but let's be certain we always return an integer.
191 194 return 0
192 195 return -child.signalstatus
193 196 if child.exitstatus > 128:
194 197 return -(child.exitstatus - 128)
195 198 return child.exitstatus
196 199
197 200
198 201 # Make system() with a functional interface for outside use. Note that we use
199 202 # getoutput() from the _common utils, which is built on top of popen(). Using
200 203 # pexpect to get subprocess output produces difficult to parse output, since
201 204 # programs think they are talking to a tty and produce highly formatted output
202 205 # (ls is a good example) that makes them hard.
203 206 system = ProcessHandler().system
204 207
205 208 def check_pid(pid):
206 209 try:
207 210 os.kill(pid, 0)
208 211 except OSError as err:
209 212 if err.errno == errno.ESRCH:
210 213 return False
211 214 elif err.errno == errno.EPERM:
212 215 # Don't have permission to signal the process - probably means it exists
213 216 return True
214 217 raise
215 218 else:
216 219 return True
General Comments 0
You need to be logged in to leave comments. Login now