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