##// END OF EJS Templates
Fully refactored subprocess handling on all platforms....
Fernando Perez -
Show More
@@ -0,0 +1,119 b''
1 """Common utilities for the various process_* implementations.
2
3 This file is only meant to be imported by the platform-specific implementations
4 of subprocess utilities, and it contains tools that are common to all of them.
5 """
6
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2010 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
13
14 #-----------------------------------------------------------------------------
15 # Imports
16 #-----------------------------------------------------------------------------
17 import subprocess
18 import sys
19
20 #-----------------------------------------------------------------------------
21 # Function definitions
22 #-----------------------------------------------------------------------------
23
24 def read_no_interrupt(p):
25 """Read from a pipe ignoring EINTR errors.
26
27 This is necessary because when reading from pipes with GUI event loops
28 running in the background, often interrupts are raised that stop the
29 command from completing."""
30 import errno
31
32 try:
33 return p.read()
34 except IOError, err:
35 if err.errno != errno.EINTR:
36 raise
37
38
39 def process_handler(cmd, callback, stderr=subprocess.PIPE):
40 """Open a command in a shell subprocess and execute a callback.
41
42 This function provides common scaffolding for creating subprocess.Popen()
43 calls. It creates a Popen object and then calls the callback with it.
44
45 Parameters
46 ----------
47 cmd : str
48 A string to be executed with the underlying system shell (by calling
49 :func:`Popen` with ``shell=True``.
50
51 callback : callable
52 A one-argument function that will be called with the Popen object.
53
54 stderr : file descriptor number, optional
55 By default this is set to ``subprocess.PIPE``, but you can also pass the
56 value ``subprocess.STDOUT`` to force the subprocess' stderr to go into
57 the same file descriptor as its stdout. This is useful to read stdout
58 and stderr combined in the order they are generated.
59
60 Returns
61 -------
62 The return value of the provided callback is returned.
63 """
64 sys.stdout.flush()
65 sys.stderr.flush()
66 p = subprocess.Popen(cmd, shell=True,
67 stdin=subprocess.PIPE,
68 stdout=subprocess.PIPE,
69 stderr=stderr,
70 close_fds=True)
71
72 try:
73 out = callback(p)
74 except KeyboardInterrupt:
75 print('^C')
76 sys.stdout.flush()
77 sys.stderr.flush()
78 out = None
79 finally:
80 # Make really sure that we don't leave processes behind, in case the
81 # call above raises an exception
82 # We start by assuming the subprocess finished (to avoid NameErrors
83 # later depending on the path taken)
84 if p.returncode is None:
85 try:
86 p.terminate()
87 p.poll()
88 except OSError:
89 pass
90 # One last try on our way out
91 if p.returncode is None:
92 try:
93 p.kill()
94 except OSError:
95 pass
96
97 return out
98
99
100 def getoutputerror(cmd):
101 """Return (standard output, standard error) of executing cmd in a shell.
102
103 Accepts the same arguments as os.system().
104
105 Parameters
106 ----------
107 cmd : str
108 A command to be executed in the system shell.
109
110 Returns
111 -------
112 stdout : str
113 stderr : str
114 """
115
116 out_err = process_handler(cmd, lambda p: p.communicate())
117 if out_err is None:
118 out_err = '', ''
119 return out_err
@@ -0,0 +1,169 b''
1 """Posix-specific implementation of process utilities.
2
3 This file is only meant to be imported by process.py, not by end-users.
4 """
5
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2010 The IPython Development Team
8 #
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
12
13 #-----------------------------------------------------------------------------
14 # Imports
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
17
18 # Stdlib
19 import subprocess as sp
20 import sys
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
29
30 # Our own
31 from .autoattr import auto_attr
32
33 #-----------------------------------------------------------------------------
34 # Function definitions
35 #-----------------------------------------------------------------------------
36
37 def _find_cmd(cmd):
38 """Find the full path to a command using which."""
39
40 return sp.Popen(['/usr/bin/env', 'which', cmd],
41 stdout=sp.PIPE).communicate()[0]
42
43
44 class ProcessHandler(object):
45 """Execute subprocesses under the control of pexpect.
46 """
47 # Timeout in seconds to wait on each reading of the subprocess' output.
48 # This should not be set too low to avoid cpu overusage from our side,
49 # since we read in a loop whose period is controlled by this timeout.
50 read_timeout = 0.05
51
52 # Timeout to give a process if we receive SIGINT, between sending the
53 # SIGINT to the process and forcefully terminating it.
54 terminate_timeout = 0.2
55
56 # File object where stdout and stderr of the subprocess will be written
57 logfile = None
58
59 # Shell to call for subprocesses to execute
60 sh = None
61
62 @auto_attr
63 def sh(self):
64 sh = pexpect.which('sh')
65 if sh is None:
66 raise OSError('"sh" shell not found')
67 return sh
68
69 def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None):
70 """Arguments are used for pexpect calls."""
71 self.read_timeout = (ProcessHandler.read_timeout if read_timeout is
72 None else read_timeout)
73 self.terminate_timeout = (ProcessHandler.terminate_timeout if
74 terminate_timeout is None else
75 terminate_timeout)
76 self.logfile = sys.stdout if logfile is None else logfile
77
78 def getoutput(self, cmd):
79 """Run a command and return its stdout/stderr as a string.
80
81 Parameters
82 ----------
83 cmd : str
84 A command to be executed in the system shell.
85
86 Returns
87 -------
88 output : str
89 A string containing the combination of stdout and stderr from the
90 subprocess, in whatever order the subprocess originally wrote to its
91 file descriptors (so the order of the information in this string is the
92 correct order as would be seen if running the command in a terminal).
93 """
94 pcmd = self._make_cmd(cmd)
95 try:
96 return pexpect.run(pcmd).replace('\r\n', '\n')
97 except KeyboardInterrupt:
98 print('^C', file=sys.stderr, end='')
99
100 def system(self, cmd):
101 """Execute a command in a subshell.
102
103 Parameters
104 ----------
105 cmd : str
106 A command to be executed in the system shell.
107
108 Returns
109 -------
110 None : we explicitly do NOT return the subprocess status code, as this
111 utility is meant to be used extensively in IPython, where any return
112 value would trigger :func:`sys.displayhook` calls.
113 """
114 pcmd = self._make_cmd(cmd)
115 # Patterns to match on the output, for pexpect. We read input and
116 # allow either a short timeout or EOF
117 patterns = [pexpect.TIMEOUT, pexpect.EOF]
118 # the index of the EOF pattern in the list.
119 EOF_index = 1 # Fix this index if you change the list!!
120 # The size of the output stored so far in the process output buffer.
121 # Since pexpect only appends to this buffer, each time we print we
122 # record how far we've printed, so that next time we only print *new*
123 # content from the buffer.
124 out_size = 0
125 try:
126 # Since we're not really searching the buffer for text patterns, we
127 # can set pexpect's search window to be tiny and it won't matter.
128 # We only search for the 'patterns' timeout or EOF, which aren't in
129 # the text itself.
130 child = pexpect.spawn(pcmd, searchwindowsize=1)
131 flush = sys.stdout.flush
132 while True:
133 # res is the index of the pattern that caused the match, so we
134 # know whether we've finished (if we matched EOF) or not
135 res_idx = child.expect_list(patterns, self.read_timeout)
136 print(child.before[out_size:], end='')
137 flush()
138 # Update the pointer to what we've already printed
139 out_size = len(child.before)
140 if res_idx==EOF_index:
141 break
142 except KeyboardInterrupt:
143 # We need to send ^C to the process. The ascii code for '^C' is 3
144 # (the character is known as ETX for 'End of Text', see
145 # curses.ascii.ETX).
146 child.sendline(chr(3))
147 # Read and print any more output the program might produce on its
148 # way out.
149 try:
150 out_size = len(child.before)
151 child.expect_list(patterns, self.terminate_timeout)
152 print(child.before[out_size:], end='')
153 except KeyboardInterrupt:
154 # Impatient users tend to type it multiple times
155 pass
156 finally:
157 # Ensure the subprocess really is terminated
158 child.terminate(force=True)
159
160 def _make_cmd(self, cmd):
161 return '%s -c "%s"' % (self.sh, cmd)
162
163
164
165 # Make objects with a functional interface for outside use
166 __ph = ProcessHandler()
167
168 system = __ph.system
169 getoutput = __ph.getoutput
@@ -0,0 +1,137 b''
1 """Windows-specific implementation of process utilities.
2
3 This file is only meant to be imported by process.py, not by end-users.
4 """
5
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2010 The IPython Development Team
8 #
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
12
13 #-----------------------------------------------------------------------------
14 # Imports
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
17
18 # stdlib
19 import os
20 import sys
21
22 from subprocess import STDOUT
23
24 # our own imports
25 from ._process_common import read_no_interrupt, process_handler
26
27 #-----------------------------------------------------------------------------
28 # Function definitions
29 #-----------------------------------------------------------------------------
30
31 class AvoidUNCPath(object):
32 """A context manager to protect command execution from UNC paths.
33
34 In the Win32 API, commands can't be invoked with the cwd being a UNC path.
35 This context manager temporarily changes directory to the 'C:' drive on
36 entering, and restores the original working directory on exit.
37
38 The context manager returns the starting working directory *if* it made a
39 change and None otherwise, so that users can apply the necessary adjustment
40 to their system calls in the event of a change.
41
42 Example
43 -------
44 ::
45 cmd = 'dir'
46 with AvoidUNCPath() as path:
47 if path is not None:
48 cmd = '"pushd %s &&"%s' % (path, cmd)
49 os.system(cmd)
50 """
51 def __enter__(self):
52 self.path = os.getcwd()
53 self.is_unc_path = self.path.startswith(r"\\")
54 if self.is_unc_path:
55 # change to c drive (as cmd.exe cannot handle UNC addresses)
56 os.chdir("C:")
57 return self.path
58
59 def __exit__(self, exc_type, exc_value, traceback):
60 if self.is_unc_path:
61 os.chdir(self.path)
62
63
64 def _find_cmd(cmd):
65 """Find the full path to a .bat or .exe using the win32api module."""
66 try:
67 from win32api import SearchPath
68 except ImportError:
69 raise ImportError('you need to have pywin32 installed for this to work')
70 else:
71 PATH = os.environ['PATH']
72 extensions = ['.exe', '.com', '.bat', '.py']
73 path = None
74 for ext in extensions:
75 try:
76 path = SearchPath(PATH, cmd + ext)[0]
77 except:
78 pass
79 if path is None:
80 raise OSError("command %r not found" % cmd)
81 else:
82 return path
83
84
85 def _system_body(p):
86 """Callback for _system."""
87 for line in read_no_interrupt(p.stdout).splitlines():
88 print(line, file=sys.stdout)
89 for line in read_no_interrupt(p.stderr).splitlines():
90 print(line, file=sys.stderr)
91
92
93 def system(cmd):
94 """Win32 version of os.system() that works with network shares.
95
96 Note that this implementation returns None, as meant for use in IPython.
97
98 Parameters
99 ----------
100 cmd : str
101 A command to be executed in the system shell.
102
103 Returns
104 -------
105 None : we explicitly do NOT return the subprocess status code, as this
106 utility is meant to be used extensively in IPython, where any return value
107 would trigger :func:`sys.displayhook` calls.
108 """
109 with AvoidUNCPath() as path:
110 if path is not None:
111 cmd = '"pushd %s &&"%s' % (path, cmd)
112 process_handler(cmd, _system_body)
113
114
115 def getoutput(cmd):
116 """Return standard output of executing cmd in a shell.
117
118 Accepts the same arguments as os.system().
119
120 Parameters
121 ----------
122 cmd : str
123 A command to be executed in the system shell.
124
125 Returns
126 -------
127 stdout : str
128 """
129
130 with AvoidUNCPath() as path:
131 if path is not None:
132 cmd = '"pushd %s &&"%s' % (path, cmd)
133 out = process_handler(cmd, lambda p: p.communicate()[0], STDOUT)
134
135 if out is None:
136 out = ''
137 return out
@@ -57,7 +57,7 b' from IPython.utils.doctestreload import doctest_reload'
57 from IPython.utils.io import ask_yes_no, rprint
57 from IPython.utils.io import ask_yes_no, rprint
58 from IPython.utils.ipstruct import Struct
58 from IPython.utils.ipstruct import Struct
59 from IPython.utils.path import get_home_dir, get_ipython_dir, HomeDirError
59 from IPython.utils.path import get_home_dir, get_ipython_dir, HomeDirError
60 from IPython.utils.process import system, getoutput, getoutputerror
60 from IPython.utils.process import system, getoutput
61 from IPython.utils.strdispatch import StrDispatch
61 from IPython.utils.strdispatch import StrDispatch
62 from IPython.utils.syspathcontext import prepended_to_syspath
62 from IPython.utils.syspathcontext import prepended_to_syspath
63 from IPython.utils.text import num_ini_spaces
63 from IPython.utils.text import num_ini_spaces
@@ -1667,12 +1667,6 b' class InteractiveShell(Configurable, Magic):'
1667 raise OSError("Background processes not supported.")
1667 raise OSError("Background processes not supported.")
1668 return getoutput(self.var_expand(cmd, depth=2))
1668 return getoutput(self.var_expand(cmd, depth=2))
1669
1669
1670 def getoutputerror(self, cmd):
1671 """Get stdout and stderr from a subprocess."""
1672 if cmd.endswith('&'):
1673 raise OSError("Background processes not supported.")
1674 return getoutputerror(self.var_expand(cmd, depth=2))
1675
1676 #-------------------------------------------------------------------------
1670 #-------------------------------------------------------------------------
1677 # Things related to aliases
1671 # Things related to aliases
1678 #-------------------------------------------------------------------------
1672 #-------------------------------------------------------------------------
@@ -3072,9 +3072,7 b' Defaulting color scheme to \'NoColor\'"""'
3072 except ValueError:
3072 except ValueError:
3073 var,cmd = '',''
3073 var,cmd = '',''
3074 # If all looks ok, proceed
3074 # If all looks ok, proceed
3075 out,err = self.shell.getoutputerror(cmd)
3075 out = self.shell.getoutput(cmd)
3076 if err:
3077 print >> IPython.utils.io.Term.cerr, err
3078 if opts.has_key('l'):
3076 if opts.has_key('l'):
3079 out = SList(out.split('\n'))
3077 out = SList(out.split('\n'))
3080 else:
3078 else:
@@ -3122,10 +3120,9 b' Defaulting color scheme to \'NoColor\'"""'
3122 system commands."""
3120 system commands."""
3123
3121
3124 if parameter_s:
3122 if parameter_s:
3125 out,err = self.shell.getoutputerror(parameter_s)
3123 out = self.shell.getoutput(parameter_s)
3126 if err:
3124 if out is not None:
3127 print >> IPython.utils.io.Term.cerr, err
3125 return SList(out.splitlines())
3128 return SList(out.split('\n'))
3129
3126
3130 def magic_r(self, parameter_s=''):
3127 def magic_r(self, parameter_s=''):
3131 """Repeat previous input.
3128 """Repeat previous input.
@@ -37,7 +37,7 b' from IPython.core.error import TryNext'
37 from IPython.utils.cursesimport import use_curses
37 from IPython.utils.cursesimport import use_curses
38 from IPython.utils.data import chop
38 from IPython.utils.data import chop
39 import IPython.utils.io
39 import IPython.utils.io
40 from IPython.utils.process import xsys
40 from IPython.utils.process import system
41 from IPython.utils.terminal import get_terminal_size
41 from IPython.utils.terminal import get_terminal_size
42
42
43
43
@@ -210,7 +210,7 b' def page_file(fname, start=0, pager_cmd=None):'
210 try:
210 try:
211 if os.environ['TERM'] in ['emacs','dumb']:
211 if os.environ['TERM'] in ['emacs','dumb']:
212 raise EnvironmentError
212 raise EnvironmentError
213 xsys(pager_cmd + ' ' + fname)
213 system(pager_cmd + ' ' + fname)
214 except:
214 except:
215 try:
215 try:
216 if start > 0:
216 if start > 0:
@@ -1,6 +1,4 b''
1 #!/usr/bin/env python
1 """Descriptor utilities.
2 # encoding: utf-8
3 """Descriptor support for NIPY.
4
2
5 Utilities to support special Python descriptors [1,2], in particular the use of
3 Utilities to support special Python descriptors [1,2], in particular the use of
6 a useful pattern for properties we call 'one time properties'. These are
4 a useful pattern for properties we call 'one time properties'. These are
@@ -24,6 +22,10 b' Notes'
24 -----
22 -----
25 This module is taken from the NiPy project
23 This module is taken from the NiPy project
26 (http://neuroimaging.scipy.org/site/index.html), and is BSD licensed.
24 (http://neuroimaging.scipy.org/site/index.html), and is BSD licensed.
25
26 Authors
27 -------
28 - Fernando Perez.
27 """
29 """
28
30
29 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
@@ -18,7 +18,7 b' import os'
18 import sys
18 import sys
19
19
20 import IPython
20 import IPython
21 from IPython.utils.process import xsys
21 from IPython.utils.process import system
22 from IPython.utils.importstring import import_item
22 from IPython.utils.importstring import import_item
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
@@ -341,5 +341,5 b' def target_update(target,deps,cmd):'
341 command if target is outdated."""
341 command if target is outdated."""
342
342
343 if target_outdated(target,deps):
343 if target_outdated(target,deps):
344 xsys(cmd)
344 system(cmd)
345
345
@@ -13,13 +13,20 b' Utilities for working with external processes.'
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16
17
18 # Stdlib
17 import os
19 import os
18 import sys
20 import sys
19 import shlex
21 import shlex
20 import subprocess
21
22
22 from IPython.utils.terminal import set_term_title
23 # Our own
24 if sys.platform == 'win32':
25 from ._process_win32 import _find_cmd, system, getoutput, AvoidUNCPath
26 else:
27 from ._process_posix import _find_cmd, system, getoutput
28
29 from ._process_common import getoutputerror
23
30
24 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
25 # Code
32 # Code
@@ -30,39 +37,6 b' class FindCmdError(Exception):'
30 pass
37 pass
31
38
32
39
33 def _find_cmd(cmd):
34 """Find the full path to a command using which."""
35 return os.popen('which %s' % cmd).read().strip()
36
37
38 if os.name == 'posix':
39 def _find_cmd(cmd):
40 """Find the full path to a command using which."""
41 return getoutputerror('/usr/bin/env which %s' % cmd)[0]
42
43
44 if sys.platform == 'win32':
45 def _find_cmd(cmd):
46 """Find the full path to a .bat or .exe using the win32api module."""
47 try:
48 from win32api import SearchPath
49 except ImportError:
50 raise ImportError('you need to have pywin32 installed for this to work')
51 else:
52 PATH = os.environ['PATH']
53 extensions = ['.exe', '.com', '.bat', '.py']
54 path = None
55 for ext in extensions:
56 try:
57 path = SearchPath(PATH,cmd + ext)[0]
58 except:
59 pass
60 if path is None:
61 raise OSError("command %r not found" % cmd)
62 else:
63 return path
64
65
66 def find_cmd(cmd):
40 def find_cmd(cmd):
67 """Find absolute path to executable cmd in a cross platform manner.
41 """Find absolute path to executable cmd in a cross platform manner.
68
42
@@ -87,7 +61,7 b' def find_cmd(cmd):'
87 if cmd == 'python':
61 if cmd == 'python':
88 return os.path.abspath(sys.executable)
62 return os.path.abspath(sys.executable)
89 try:
63 try:
90 path = _find_cmd(cmd)
64 path = _find_cmd(cmd).rstrip()
91 except OSError:
65 except OSError:
92 raise FindCmdError('command could not be found: %s' % cmd)
66 raise FindCmdError('command could not be found: %s' % cmd)
93 # which returns empty if not found
67 # which returns empty if not found
@@ -147,28 +121,6 b' def arg_split(s, posix=False):'
147 return list(lex)
121 return list(lex)
148
122
149
123
150 def system(cmd, verbose=0, debug=0, header=''):
151 """Execute a system command, return its exit status.
152
153 Options:
154
155 - verbose (0): print the command to be executed.
156
157 - debug (0): only print, do not actually execute.
158
159 - header (''): Header to print on screen prior to the executed command (it
160 is only prepended to the command, no newlines are added).
161
162 Note: a stateful version of this function is available through the
163 SystemExec class."""
164
165 stat = 0
166 if verbose or debug: print header+cmd
167 sys.stdout.flush()
168 if not debug: stat = os.system(cmd)
169 return stat
170
171
172 def abbrev_cwd():
124 def abbrev_cwd():
173 """ Return abbreviated version of cwd, e.g. d:mydir """
125 """ Return abbreviated version of cwd, e.g. d:mydir """
174 cwd = os.getcwd().replace('\\','/')
126 cwd = os.getcwd().replace('\\','/')
@@ -186,182 +138,3 b' def abbrev_cwd():'
186
138
187 return (drivepart + (
139 return (drivepart + (
188 cwd == '/' and '/' or tail))
140 cwd == '/' and '/' or tail))
189
190
191 # This function is used by ipython in a lot of places to make system calls.
192 # We need it to be slightly different under win32, due to the vagaries of
193 # 'network shares'. A win32 override is below.
194
195 def shell(cmd, verbose=0, debug=0, header=''):
196 """Execute a command in the system shell, always return None.
197
198 Options:
199
200 - verbose (0): print the command to be executed.
201
202 - debug (0): only print, do not actually execute.
203
204 - header (''): Header to print on screen prior to the executed command (it
205 is only prepended to the command, no newlines are added).
206
207 Note: this is similar to system(), but it returns None so it can
208 be conveniently used in interactive loops without getting the return value
209 (typically 0) printed many times."""
210
211 stat = 0
212 if verbose or debug: print header+cmd
213 # flush stdout so we don't mangle python's buffering
214 sys.stdout.flush()
215
216 if not debug:
217 set_term_title("IPy " + cmd)
218 os.system(cmd)
219 set_term_title("IPy " + abbrev_cwd())
220
221 # override shell() for win32 to deal with network shares
222 if os.name in ('nt','dos'):
223
224 shell_ori = shell
225
226 def shell(cmd, verbose=0, debug=0, header=''):
227 if os.getcwd().startswith(r"\\"):
228 path = os.getcwd()
229 # change to c drive (cannot be on UNC-share when issuing os.system,
230 # as cmd.exe cannot handle UNC addresses)
231 os.chdir("c:")
232 # issue pushd to the UNC-share and then run the command
233 try:
234 shell_ori('"pushd %s&&"'%path+cmd,verbose,debug,header)
235 finally:
236 os.chdir(path)
237 else:
238 shell_ori(cmd,verbose,debug,header)
239
240 shell.__doc__ = shell_ori.__doc__
241
242
243 def getoutput(cmd, verbose=0, debug=0, header='', split=0):
244 """Dummy substitute for perl's backquotes.
245
246 Executes a command and returns the output.
247
248 Accepts the same arguments as system(), plus:
249
250 - split(0): if true, the output is returned as a list split on newlines.
251
252 Note: a stateful version of this function is available through the
253 SystemExec class.
254
255 This is pretty much deprecated and rarely used, getoutputerror may be
256 what you need.
257
258 """
259
260 if verbose or debug: print header+cmd
261 if not debug:
262 pipe = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).stdout
263 output = pipe.read()
264 # stipping last \n is here for backwards compat.
265 if output.endswith('\n'):
266 output = output[:-1]
267 if split:
268 return output.split('\n')
269 else:
270 return output
271
272
273 # for compatibility with older naming conventions
274 xsys = system
275
276
277 def getoutputerror(cmd, verbose=0, debug=0, header='', split=0):
278 """Return (standard output,standard error) of executing cmd in a shell.
279
280 Accepts the same arguments as system(), plus:
281
282 - split(0): if true, each of stdout/err is returned as a list split on
283 newlines.
284
285 Note: a stateful version of this function is available through the
286 SystemExec class."""
287
288 if verbose or debug: print header+cmd
289 if not cmd:
290 if split:
291 return [],[]
292 else:
293 return '',''
294 if not debug:
295 p = subprocess.Popen(cmd, shell=True,
296 stdin=subprocess.PIPE,
297 stdout=subprocess.PIPE,
298 stderr=subprocess.PIPE,
299 close_fds=True)
300 pin, pout, perr = (p.stdin, p.stdout, p.stderr)
301
302 tout = pout.read().rstrip()
303 terr = perr.read().rstrip()
304 pin.close()
305 pout.close()
306 perr.close()
307 if split:
308 return tout.split('\n'),terr.split('\n')
309 else:
310 return tout,terr
311
312
313 class SystemExec:
314 """Access the system and getoutput functions through a stateful interface.
315
316 Note: here we refer to the system and getoutput functions from this
317 library, not the ones from the standard python library.
318
319 This class offers the system and getoutput functions as methods, but the
320 verbose, debug and header parameters can be set for the instance (at
321 creation time or later) so that they don't need to be specified on each
322 call.
323
324 For efficiency reasons, there's no way to override the parameters on a
325 per-call basis other than by setting instance attributes. If you need
326 local overrides, it's best to directly call system() or getoutput().
327
328 The following names are provided as alternate options:
329 - xsys: alias to system
330 - bq: alias to getoutput
331
332 An instance can then be created as:
333 >>> sysexec = SystemExec(verbose=1,debug=0,header='Calling: ')
334 """
335
336 def __init__(self, verbose=0, debug=0, header='', split=0):
337 """Specify the instance's values for verbose, debug and header."""
338 self.verbose = verbose
339 self.debug = debug
340 self.header = header
341 self.split = split
342
343 def system(self, cmd):
344 """Stateful interface to system(), with the same keyword parameters."""
345
346 system(cmd, self.verbose, self.debug, self.header)
347
348 def shell(self, cmd):
349 """Stateful interface to shell(), with the same keyword parameters."""
350
351 shell(cmd, self.verbose, self.debug, self.header)
352
353 xsys = system # alias
354
355 def getoutput(self, cmd):
356 """Stateful interface to getoutput()."""
357
358 return getoutput(cmd, self.verbose, self.debug, self.header, self.split)
359
360 def getoutputerror(self, cmd):
361 """Stateful interface to getoutputerror()."""
362
363 return getoutputerror(cmd, self.verbose, self.debug, self.header, self.split)
364
365 bq = getoutput # alias
366
367
@@ -15,11 +15,14 b' Tests for platutils.py'
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 import sys
17 import sys
18 from unittest import TestCase
18
19
19 import nose.tools as nt
20 import nose.tools as nt
20
21
21 from IPython.utils.process import find_cmd, FindCmdError, arg_split
22 from IPython.utils.process import (find_cmd, FindCmdError, arg_split,
23 system, getoutput, getoutputerror)
22 from IPython.testing import decorators as dec
24 from IPython.testing import decorators as dec
25 from IPython.testing import tools as tt
23
26
24 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
25 # Tests
28 # Tests
@@ -66,3 +69,27 b' def test_arg_split():'
66 ]
69 ]
67 for argstr, argv in tests:
70 for argstr, argv in tests:
68 nt.assert_equal(arg_split(argstr), argv)
71 nt.assert_equal(arg_split(argstr), argv)
72
73
74 class SubProcessTestCase(TestCase, tt.TempFileMixin):
75 def setUp(self):
76 """Make a valid python temp file."""
77 lines = ["from __future__ import print_function",
78 "import sys",
79 "print('on stdout', end='', file=sys.stdout)",
80 "print('on stderr', end='', file=sys.stderr)",
81 "sys.stdout.flush()",
82 "sys.stderr.flush()"]
83 self.mktmp('\n'.join(lines))
84
85 def test_system(self):
86 system('python "%s"' % self.fname)
87
88 def test_getoutput(self):
89 out = getoutput('python "%s"' % self.fname)
90 self.assertEquals(out, 'on stdout')
91
92 def test_getoutput(self):
93 out, err = getoutputerror('python "%s"' % self.fname)
94 self.assertEquals(out, 'on stdout')
95 self.assertEquals(err, 'on stderr')
@@ -19,9 +19,6 b' from __future__ import print_function'
19 import inspect
19 import inspect
20 import os
20 import os
21 import re
21 import re
22 import sys
23
24 from subprocess import Popen, PIPE
25
22
26 # Our own
23 # Our own
27 from IPython.core.interactiveshell import (
24 from IPython.core.interactiveshell import (
@@ -83,26 +80,6 b' class ZMQInteractiveShell(InteractiveShell):'
83
80
84 displayhook_class = Type(ZMQDisplayHook)
81 displayhook_class = Type(ZMQDisplayHook)
85
82
86 def system(self, cmd):
87 cmd = self.var_expand(cmd, depth=2).strip()
88
89 # Runnning a bacgkrounded process from within the gui isn't supported
90 # because we do p.wait() at the end. So instead of silently blocking
91 # we simply refuse to run in this mode, to avoid surprising the user.
92 if cmd.endswith('&'):
93 raise OSError("Background processes not supported.")
94
95 sys.stdout.flush()
96 sys.stderr.flush()
97 p = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE)
98 for line in p.stdout.read().split('\n'):
99 if len(line) > 0:
100 print(line)
101 for line in p.stderr.read().split('\n'):
102 if len(line) > 0:
103 print(line, file=sys.stderr)
104 p.wait()
105
106 def init_io(self):
83 def init_io(self):
107 # This will just use sys.stdout and sys.stderr. If you want to
84 # This will just use sys.stdout and sys.stderr. If you want to
108 # override sys.stdout and sys.stderr themselves, you need to do that
85 # override sys.stdout and sys.stderr themselves, you need to do that
General Comments 0
You need to be logged in to leave comments. Login now