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