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