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