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