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