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