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