##// END OF EJS Templates
Make ProcessHandler.sh a property
Thomas Kluyver -
Show More
@@ -1,197 +1,198
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 @property
59 60 def sh(self):
60 61 if self._sh is None:
61 62 self._sh = pexpect.which('sh')
62 63 if self._sh is None:
63 64 raise OSError('"sh" shell not found')
64 65
65 66 return self._sh
66 67
67 68 def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None):
68 69 """Arguments are used for pexpect calls."""
69 70 self.read_timeout = (ProcessHandler.read_timeout if read_timeout is
70 71 None else read_timeout)
71 72 self.terminate_timeout = (ProcessHandler.terminate_timeout if
72 73 terminate_timeout is None else
73 74 terminate_timeout)
74 75 self.logfile = sys.stdout if logfile is None else logfile
75 76
76 77 def getoutput(self, cmd):
77 78 """Run a command and return its stdout/stderr as a string.
78 79
79 80 Parameters
80 81 ----------
81 82 cmd : str
82 83 A command to be executed in the system shell.
83 84
84 85 Returns
85 86 -------
86 87 output : str
87 88 A string containing the combination of stdout and stderr from the
88 89 subprocess, in whatever order the subprocess originally wrote to its
89 90 file descriptors (so the order of the information in this string is the
90 91 correct order as would be seen if running the command in a terminal).
91 92 """
92 93 try:
93 94 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
94 95 except KeyboardInterrupt:
95 96 print('^C', file=sys.stderr, end='')
96 97
97 98 def getoutput_pexpect(self, cmd):
98 99 """Run a command and return its stdout/stderr as a string.
99 100
100 101 Parameters
101 102 ----------
102 103 cmd : str
103 104 A command to be executed in the system shell.
104 105
105 106 Returns
106 107 -------
107 108 output : str
108 109 A string containing the combination of stdout and stderr from the
109 110 subprocess, in whatever order the subprocess originally wrote to its
110 111 file descriptors (so the order of the information in this string is the
111 112 correct order as would be seen if running the command in a terminal).
112 113 """
113 114 try:
114 115 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
115 116 except KeyboardInterrupt:
116 117 print('^C', file=sys.stderr, end='')
117 118
118 119 def system(self, cmd):
119 120 """Execute a command in a subshell.
120 121
121 122 Parameters
122 123 ----------
123 124 cmd : str
124 125 A command to be executed in the system shell.
125 126
126 127 Returns
127 128 -------
128 129 int : child's exitstatus
129 130 """
130 131 # Get likely encoding for the output.
131 132 enc = DEFAULT_ENCODING
132 133
133 134 # Patterns to match on the output, for pexpect. We read input and
134 135 # allow either a short timeout or EOF
135 136 patterns = [pexpect.TIMEOUT, pexpect.EOF]
136 137 # the index of the EOF pattern in the list.
137 138 # even though we know it's 1, this call means we don't have to worry if
138 139 # we change the above list, and forget to change this value:
139 140 EOF_index = patterns.index(pexpect.EOF)
140 141 # The size of the output stored so far in the process output buffer.
141 142 # Since pexpect only appends to this buffer, each time we print we
142 143 # record how far we've printed, so that next time we only print *new*
143 144 # content from the buffer.
144 145 out_size = 0
145 146 try:
146 147 # Since we're not really searching the buffer for text patterns, we
147 148 # can set pexpect's search window to be tiny and it won't matter.
148 149 # We only search for the 'patterns' timeout or EOF, which aren't in
149 150 # the text itself.
150 151 #child = pexpect.spawn(pcmd, searchwindowsize=1)
151 152 if hasattr(pexpect, 'spawnb'):
152 153 child = pexpect.spawnb(self.sh, args=['-c', cmd]) # Pexpect-U
153 154 else:
154 155 child = pexpect.spawn(self.sh, args=['-c', cmd]) # Vanilla Pexpect
155 156 flush = sys.stdout.flush
156 157 while True:
157 158 # res is the index of the pattern that caused the match, so we
158 159 # know whether we've finished (if we matched EOF) or not
159 160 res_idx = child.expect_list(patterns, self.read_timeout)
160 161 print(child.before[out_size:].decode(enc, 'replace'), end='')
161 162 flush()
162 163 if res_idx==EOF_index:
163 164 break
164 165 # Update the pointer to what we've already printed
165 166 out_size = len(child.before)
166 167 except KeyboardInterrupt:
167 168 # We need to send ^C to the process. The ascii code for '^C' is 3
168 169 # (the character is known as ETX for 'End of Text', see
169 170 # curses.ascii.ETX).
170 171 child.sendline(chr(3))
171 172 # Read and print any more output the program might produce on its
172 173 # way out.
173 174 try:
174 175 out_size = len(child.before)
175 176 child.expect_list(patterns, self.terminate_timeout)
176 177 print(child.before[out_size:].decode(enc, 'replace'), end='')
177 178 sys.stdout.flush()
178 179 except KeyboardInterrupt:
179 180 # Impatient users tend to type it multiple times
180 181 pass
181 182 finally:
182 183 # Ensure the subprocess really is terminated
183 184 child.terminate(force=True)
184 185 # add isalive check, to ensure exitstatus is set:
185 186 child.isalive()
186 187 return child.exitstatus
187 188
188 189
189 190 # Make system() with a functional interface for outside use. Note that we use
190 191 # getoutput() from the _common utils, which is built on top of popen(). Using
191 192 # pexpect to get subprocess output produces difficult to parse output, since
192 193 # programs think they are talking to a tty and produce highly formatted output
193 194 # (ls is a good example) that makes them hard.
194 195 system = ProcessHandler().system
195 196
196 197
197 198
General Comments 0
You need to be logged in to leave comments. Login now