_pexpect.py
2123 lines
| 83.6 KiB
| text/x-python
|
PythonLexer
Thomas Kluyver
|
r16384 | '''Pexpect is a Python module for spawning child applications and controlling | ||
Fernando Perez
|
r2906 | them automatically. Pexpect can be used for automating interactive applications | ||
such as ssh, ftp, passwd, telnet, etc. It can be used to a automate setup | ||||
scripts for duplicating software package installations on different servers. It | ||||
can be used for automated software testing. Pexpect is in the spirit of Don | ||||
Libes' Expect, but Pexpect is pure Python. Other Expect-like modules for Python | ||||
require TCL and Expect or require C extensions to be compiled. Pexpect does not | ||||
use C, Expect, or TCL extensions. It should work on any platform that supports | ||||
the standard Python pty module. The Pexpect interface focuses on ease of use so | ||||
that simple tasks are easy. | ||||
Thomas Kluyver
|
r5430 | There are two main interfaces to the Pexpect system; these are the function, | ||
run() and the class, spawn. The spawn class is more powerful. The run() | ||||
function is simpler than spawn, and is good for quickly calling program. When | ||||
you call the run() function it executes a given program and then returns the | ||||
Fernando Perez
|
r2906 | output. This is a handy replacement for os.system(). | ||
For example:: | ||||
pexpect.run('ls -la') | ||||
Thomas Kluyver
|
r5430 | The spawn class is the more powerful interface to the Pexpect system. You can | ||
use this to spawn a child program then interact with it by sending input and | ||||
expecting responses (waiting for patterns in the child's output). | ||||
Fernando Perez
|
r2906 | |||
For example:: | ||||
Thomas Kluyver
|
r16384 | child = pexpect.spawn('scp foo user@example.com:.') | ||
child.expect('Password:') | ||||
child.sendline(mypassword) | ||||
Fernando Perez
|
r2906 | |||
This works even for commands that ask for passwords or other input outside of | ||||
Thomas Kluyver
|
r5430 | the normal stdio streams. For example, ssh reads input directly from the TTY | ||
device which bypasses stdin. | ||||
Fernando Perez
|
r2906 | |||
Credits: Noah Spurrier, Richard Holden, Marco Molteni, Kimberley Burchett, | ||||
Robert Stone, Hartmut Goebel, Chad Schroeder, Erick Tryzelaar, Dave Kirby, Ids | ||||
vander Molen, George Todd, Noel Taylor, Nicolas D. Cesar, Alexander Gattin, | ||||
Thomas Kluyver
|
r5430 | Jacques-Etienne Baudoux, Geoffrey Marshall, Francisco Lourenco, Glen Mabey, | ||
Karthik Gurusamy, Fernando Perez, Corey Minyard, Jon Cohen, Guillaume | ||||
Chazarain, Andrew Ryan, Nick Craig-Wood, Andrew Stone, Jorgen Grahn, John | ||||
Thomas Kluyver
|
r16384 | Spiegel, Jan Grant, and Shane Kerr. Let me know if I forgot anyone. | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r5430 | Pexpect is free, open source, and all that good stuff. | ||
Fernando Perez
|
r2906 | http://pexpect.sourceforge.net/ | ||
Thomas Kluyver
|
r16384 | |||
PEXPECT LICENSE | ||||
This license is approved by the OSI and FSF as GPL-compatible. | ||||
http://opensource.org/licenses/isc-license.txt | ||||
Copyright (c) 2012, Noah Spurrier <noah@noah.org> | ||||
PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY | ||||
PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE | ||||
COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. | ||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||||
''' | ||||
Fernando Perez
|
r2906 | |||
try: | ||||
Thomas Kluyver
|
r16384 | import os | ||
import sys | ||||
import time | ||||
Fernando Perez
|
r2906 | import select | ||
import re | ||||
import struct | ||||
import resource | ||||
import types | ||||
import pty | ||||
import tty | ||||
import termios | ||||
import fcntl | ||||
import errno | ||||
import traceback | ||||
import signal | ||||
Thomas Kluyver
|
r16384 | import codecs | ||
Thomas Kluyver
|
r17142 | import stat | ||
Thomas Kluyver
|
r16384 | except ImportError: # pragma: no cover | ||
err = sys.exc_info()[1] | ||||
raise ImportError(str(err) + ''' | ||||
Fernando Perez
|
r2906 | |||
A critical module was not found. Probably this operating system does not | ||||
Thomas Kluyver
|
r16384 | support it. Pexpect is intended for UNIX-like operating systems.''') | ||
Thomas Kluyver
|
r17142 | __version__ = '3.3' | ||
Thomas Kluyver
|
r16384 | __revision__ = '' | ||
__all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu', | ||||
'which', 'split_command_line', '__version__', '__revision__'] | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | PY3 = (sys.version_info[0] >= 3) | ||
Fernando Perez
|
r2906 | |||
# Exception classes used by this module. | ||||
class ExceptionPexpect(Exception): | ||||
Thomas Kluyver
|
r16384 | '''Base class for all exceptions raised by this module. | ||
''' | ||||
Fernando Perez
|
r2906 | |||
def __init__(self, value): | ||||
Thomas Kluyver
|
r16384 | super(ExceptionPexpect, self).__init__(value) | ||
Fernando Perez
|
r2906 | self.value = value | ||
def __str__(self): | ||||
return str(self.value) | ||||
def get_trace(self): | ||||
Thomas Kluyver
|
r16384 | '''This returns an abbreviated stack trace with lines that only concern | ||
Fernando Perez
|
r2906 | the caller. In other words, the stack trace inside the Pexpect module | ||
Thomas Kluyver
|
r16384 | is not included. ''' | ||
Fernando Perez
|
r2906 | |||
tblist = traceback.extract_tb(sys.exc_info()[2]) | ||||
Thomas Kluyver
|
r16384 | tblist = [item for item in tblist if 'pexpect/__init__' not in item[0]] | ||
Fernando Perez
|
r2906 | tblist = traceback.format_list(tblist) | ||
return ''.join(tblist) | ||||
class EOF(ExceptionPexpect): | ||||
Thomas Kluyver
|
r16384 | '''Raised when EOF is read from a child. | ||
This usually means the child has exited.''' | ||||
Fernando Perez
|
r2906 | |||
class TIMEOUT(ExceptionPexpect): | ||||
Thomas Kluyver
|
r16384 | '''Raised when a read time exceeds the timeout. ''' | ||
Fernando Perez
|
r2906 | |||
##class TIMEOUT_PATTERN(TIMEOUT): | ||||
Thomas Kluyver
|
r16384 | ## '''Raised when the pattern match time exceeds the timeout. | ||
Fernando Perez
|
r2906 | ## This is different than a read TIMEOUT because the child process may | ||
## give output, thus never give a TIMEOUT, but the output | ||||
## may never match a pattern. | ||||
Thomas Kluyver
|
r16384 | ## ''' | ||
Fernando Perez
|
r2906 | ##class MAXBUFFER(ExceptionPexpect): | ||
Thomas Kluyver
|
r16384 | ## '''Raised when a buffer fills before matching an expected pattern.''' | ||
Thomas Kluyver
|
r5430 | |||
Thomas Kluyver
|
r16384 | def run(command, timeout=-1, withexitstatus=False, events=None, | ||
extra_args=None, logfile=None, cwd=None, env=None): | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | ''' | ||
Fernando Perez
|
r2906 | This function runs the given command; waits for it to finish; then | ||
returns all output as a string. STDERR is included in output. If the full | ||||
path to the command is not given then the path is searched. | ||||
Note that lines are terminated by CR/LF (\\r\\n) combination even on | ||||
Thomas Kluyver
|
r16384 | UNIX-like systems because this is the standard for pseudottys. If you set | ||
Fernando Perez
|
r2906 | 'withexitstatus' to true, then run will return a tuple of (command_output, | ||
exitstatus). If 'withexitstatus' is false then this returns just | ||||
command_output. | ||||
The run() function can often be used instead of creating a spawn instance. | ||||
For example, the following code uses spawn:: | ||||
from pexpect import * | ||||
Thomas Kluyver
|
r16384 | child = spawn('scp foo user@example.com:.') | ||
child.expect('(?i)password') | ||||
child.sendline(mypassword) | ||||
Fernando Perez
|
r2906 | |||
The previous code can be replace with the following:: | ||||
from pexpect import * | ||||
Thomas Kluyver
|
r16384 | run('scp foo user@example.com:.', events={'(?i)password': mypassword}) | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | **Examples** | ||
Fernando Perez
|
r2906 | |||
Start the apache daemon on the local machine:: | ||||
from pexpect import * | ||||
Thomas Kluyver
|
r16384 | run("/usr/local/apache/bin/apachectl start") | ||
Fernando Perez
|
r2906 | |||
Check in a file using SVN:: | ||||
from pexpect import * | ||||
Thomas Kluyver
|
r16384 | run("svn ci -m 'automatic commit' my_file.py") | ||
Fernando Perez
|
r2906 | |||
Run a command and capture exit status:: | ||||
from pexpect import * | ||||
Thomas Kluyver
|
r16384 | (command_output, exitstatus) = run('ls -l /bin', withexitstatus=1) | ||
Fernando Perez
|
r2906 | |||
The following will run SSH and execute 'ls -l' on the remote machine. The | ||||
password 'secret' will be sent if the '(?i)password' pattern is ever seen:: | ||||
Thomas Kluyver
|
r16384 | run("ssh username@machine.example.com 'ls -l'", | ||
events={'(?i)password':'secret\\n'}) | ||||
Fernando Perez
|
r2906 | |||
This will start mencoder to rip a video from DVD. This will also display | ||||
progress ticks every 5 seconds as it runs. For example:: | ||||
from pexpect import * | ||||
def print_ticks(d): | ||||
print d['event_count'], | ||||
Thomas Kluyver
|
r16384 | run("mencoder dvd://1 -o video.avi -oac copy -ovc copy", | ||
events={TIMEOUT:print_ticks}, timeout=5) | ||||
Fernando Perez
|
r2906 | |||
The 'events' argument should be a dictionary of patterns and responses. | ||||
Whenever one of the patterns is seen in the command out run() will send the | ||||
associated response string. Note that you should put newlines in your | ||||
string if Enter is necessary. The responses may also contain callback | ||||
functions. Any callback is function that takes a dictionary as an argument. | ||||
The dictionary contains all the locals from the run() function, so you can | ||||
access the child spawn object or any other variable defined in run() | ||||
(event_count, child, and extra_args are the most useful). A callback may | ||||
return True to stop the current run process otherwise run() continues until | ||||
the next event. A callback may also return a string which will be sent to | ||||
the child. 'extra_args' is not used by directly run(). It provides a way to | ||||
pass data to a callback function through run() through the locals | ||||
Thomas Kluyver
|
r16384 | dictionary passed to a callback. | ||
''' | ||||
return _run(command, timeout=timeout, withexitstatus=withexitstatus, | ||||
events=events, extra_args=extra_args, logfile=logfile, cwd=cwd, | ||||
env=env, _spawn=spawn) | ||||
def runu(command, timeout=-1, withexitstatus=False, events=None, | ||||
extra_args=None, logfile=None, cwd=None, env=None, **kwargs): | ||||
"""This offers the same interface as :func:`run`, but using unicode. | ||||
Like :class:`spawnu`, you can pass ``encoding`` and ``errors`` parameters, | ||||
which will be used for both input and output. | ||||
""" | ||||
return _run(command, timeout=timeout, withexitstatus=withexitstatus, | ||||
events=events, extra_args=extra_args, logfile=logfile, cwd=cwd, | ||||
env=env, _spawn=spawnu, **kwargs) | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | def _run(command, timeout, withexitstatus, events, extra_args, logfile, cwd, | ||
env, _spawn, **kwargs): | ||||
Fernando Perez
|
r2906 | if timeout == -1: | ||
Thomas Kluyver
|
r16384 | child = _spawn(command, maxread=2000, logfile=logfile, cwd=cwd, env=env, | ||
**kwargs) | ||||
Fernando Perez
|
r2906 | else: | ||
Thomas Kluyver
|
r16384 | child = _spawn(command, timeout=timeout, maxread=2000, logfile=logfile, | ||
cwd=cwd, env=env, **kwargs) | ||||
Fernando Perez
|
r2906 | if events is not None: | ||
Thomas Kluyver
|
r16384 | patterns = list(events.keys()) | ||
responses = list(events.values()) | ||||
Fernando Perez
|
r2906 | else: | ||
Thomas Kluyver
|
r16384 | # This assumes EOF or TIMEOUT will eventually cause run to terminate. | ||
patterns = None | ||||
responses = None | ||||
Fernando Perez
|
r2906 | child_result_list = [] | ||
event_count = 0 | ||||
Thomas Kluyver
|
r16384 | while True: | ||
Fernando Perez
|
r2906 | try: | ||
Thomas Kluyver
|
r16384 | index = child.expect(patterns) | ||
if isinstance(child.after, child.allowed_string_types): | ||||
Fernando Perez
|
r2906 | child_result_list.append(child.before + child.after) | ||
Thomas Kluyver
|
r16384 | else: | ||
# child.after may have been a TIMEOUT or EOF, | ||||
# which we don't want appended to the list. | ||||
Fernando Perez
|
r2906 | child_result_list.append(child.before) | ||
Thomas Kluyver
|
r16384 | if isinstance(responses[index], child.allowed_string_types): | ||
Fernando Perez
|
r2906 | child.send(responses[index]) | ||
Thomas Kluyver
|
r16384 | elif isinstance(responses[index], types.FunctionType): | ||
Fernando Perez
|
r2906 | callback_result = responses[index](locals()) | ||
sys.stdout.flush() | ||||
Thomas Kluyver
|
r16384 | if isinstance(callback_result, child.allowed_string_types): | ||
Fernando Perez
|
r2906 | child.send(callback_result) | ||
elif callback_result: | ||||
break | ||||
else: | ||||
Thomas Kluyver
|
r16384 | raise TypeError('The callback must be a string or function.') | ||
Fernando Perez
|
r2906 | event_count = event_count + 1 | ||
Thomas Kluyver
|
r16384 | except TIMEOUT: | ||
Fernando Perez
|
r2906 | child_result_list.append(child.before) | ||
break | ||||
Thomas Kluyver
|
r16384 | except EOF: | ||
Fernando Perez
|
r2906 | child_result_list.append(child.before) | ||
break | ||||
Thomas Kluyver
|
r16384 | child_result = child.string_type().join(child_result_list) | ||
Fernando Perez
|
r2906 | if withexitstatus: | ||
child.close() | ||||
return (child_result, child.exitstatus) | ||||
else: | ||||
return child_result | ||||
Thomas Kluyver
|
r16384 | class spawn(object): | ||
'''This is the main class interface for Pexpect. Use this class to start | ||||
and control child applications. ''' | ||||
string_type = bytes | ||||
if PY3: | ||||
allowed_string_types = (bytes, str) | ||||
@staticmethod | ||||
def _chr(c): | ||||
return bytes([c]) | ||||
linesep = os.linesep.encode('ascii') | ||||
Thomas Kluyver
|
r17142 | crlf = '\r\n'.encode('ascii') | ||
Thomas Kluyver
|
r16384 | |||
@staticmethod | ||||
def write_to_stdout(b): | ||||
try: | ||||
return sys.stdout.buffer.write(b) | ||||
except AttributeError: | ||||
# If stdout has been replaced, it may not have .buffer | ||||
return sys.stdout.write(b.decode('ascii', 'replace')) | ||||
else: | ||||
allowed_string_types = (basestring,) # analysis:ignore | ||||
_chr = staticmethod(chr) | ||||
linesep = os.linesep | ||||
Thomas Kluyver
|
r17142 | crlf = '\r\n' | ||
Thomas Kluyver
|
r16384 | write_to_stdout = sys.stdout.write | ||
encoding = None | ||||
def __init__(self, command, args=[], timeout=30, maxread=2000, | ||||
searchwindowsize=None, logfile=None, cwd=None, env=None, | ||||
Thomas Kluyver
|
r17142 | ignore_sighup=True, echo=True): | ||
Thomas Kluyver
|
r16384 | |||
'''This is the constructor. The command parameter may be a string that | ||||
Fernando Perez
|
r2906 | includes a command and any arguments to the command. For example:: | ||
Thomas Kluyver
|
r16384 | child = pexpect.spawn('/usr/bin/ftp') | ||
child = pexpect.spawn('/usr/bin/ssh user@example.com') | ||||
child = pexpect.spawn('ls -latr /tmp') | ||||
Fernando Perez
|
r2906 | |||
You may also construct it with a list of arguments like so:: | ||||
Thomas Kluyver
|
r16384 | child = pexpect.spawn('/usr/bin/ftp', []) | ||
child = pexpect.spawn('/usr/bin/ssh', ['user@example.com']) | ||||
child = pexpect.spawn('ls', ['-latr', '/tmp']) | ||||
Fernando Perez
|
r2906 | |||
After this the child application will be created and will be ready to | ||||
talk to. For normal use, see expect() and send() and sendline(). | ||||
Remember that Pexpect does NOT interpret shell meta characters such as | ||||
Thomas Kluyver
|
r16384 | redirect, pipe, or wild cards (``>``, ``|``, or ``*``). This is a | ||
common mistake. If you want to run a command and pipe it through | ||||
another command then you must also start a shell. For example:: | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | child = pexpect.spawn('/bin/bash -c "ls -l | grep LOG > logs.txt"') | ||
Fernando Perez
|
r2906 | child.expect(pexpect.EOF) | ||
The second form of spawn (where you pass a list of arguments) is useful | ||||
in situations where you wish to spawn a command and pass it its own | ||||
argument list. This can make syntax more clear. For example, the | ||||
following is equivalent to the previous example:: | ||||
Thomas Kluyver
|
r16384 | shell_cmd = 'ls -l | grep LOG > logs.txt' | ||
Fernando Perez
|
r2906 | child = pexpect.spawn('/bin/bash', ['-c', shell_cmd]) | ||
child.expect(pexpect.EOF) | ||||
The maxread attribute sets the read buffer size. This is maximum number | ||||
of bytes that Pexpect will try to read from a TTY at one time. Setting | ||||
the maxread size to 1 will turn off buffering. Setting the maxread | ||||
value higher may help performance in cases where large amounts of | ||||
output are read back from the child. This feature is useful in | ||||
conjunction with searchwindowsize. | ||||
Thomas Kluyver
|
r16384 | The searchwindowsize attribute sets the how far back in the incoming | ||
Fernando Perez
|
r2906 | seach buffer Pexpect will search for pattern matches. Every time | ||
Pexpect reads some data from the child it will append the data to the | ||||
Thomas Kluyver
|
r16384 | incoming buffer. The default is to search from the beginning of the | ||
incoming buffer each time new data is read from the child. But this is | ||||
Fernando Perez
|
r2906 | very inefficient if you are running a command that generates a large | ||
Thomas Kluyver
|
r16384 | amount of data where you want to match. The searchwindowsize does not | ||
affect the size of the incoming data buffer. You will still have | ||||
Fernando Perez
|
r2906 | access to the full buffer after expect() returns. | ||
The logfile member turns on or off logging. All input and output will | ||||
be copied to the given file object. Set logfile to None to stop | ||||
logging. This is the default. Set logfile to sys.stdout to echo | ||||
everything to standard output. The logfile is flushed after each write. | ||||
Example log input and output to a file:: | ||||
child = pexpect.spawn('some_command') | ||||
Thomas Kluyver
|
r16384 | fout = file('mylog.txt','w') | ||
Fernando Perez
|
r2906 | child.logfile = fout | ||
Example log to stdout:: | ||||
child = pexpect.spawn('some_command') | ||||
child.logfile = sys.stdout | ||||
The logfile_read and logfile_send members can be used to separately log | ||||
the input from the child and output sent to the child. Sometimes you | ||||
don't want to see everything you write to the child. You only want to | ||||
log what the child sends back. For example:: | ||||
child = pexpect.spawn('some_command') | ||||
child.logfile_read = sys.stdout | ||||
To separately log output sent to the child use logfile_send:: | ||||
self.logfile_send = fout | ||||
Thomas Kluyver
|
r16384 | If ``ignore_sighup`` is True, the child process will ignore SIGHUP | ||
signals. For now, the default is True, to preserve the behaviour of | ||||
earlier versions of Pexpect, but you should pass this explicitly if you | ||||
want to rely on it. | ||||
Fernando Perez
|
r2906 | The delaybeforesend helps overcome a weird behavior that many users | ||
were experiencing. The typical problem was that a user would expect() a | ||||
"Password:" prompt and then immediately call sendline() to send the | ||||
password. The user would then see that their password was echoed back | ||||
to them. Passwords don't normally echo. The problem is caused by the | ||||
fact that most applications print out the "Password" prompt and then | ||||
turn off stdin echo, but if you send your password before the | ||||
application turned off echo, then you get your password echoed. | ||||
Normally this wouldn't be a problem when interacting with a human at a | ||||
real keyboard. If you introduce a slight delay just before writing then | ||||
this seems to clear up the problem. This was such a common problem for | ||||
many users that I decided that the default pexpect behavior should be | ||||
to sleep just before writing to the child application. 1/20th of a | ||||
second (50 ms) seems to be enough to clear up the problem. You can set | ||||
delaybeforesend to 0 to return to the old behavior. Most Linux machines | ||||
don't like this to be below 0.03. I don't know why. | ||||
Note that spawn is clever about finding commands on your path. | ||||
It uses the same logic that "which" uses to find executables. | ||||
If you wish to get the exit status of the child you must call the | ||||
close() method. The exit or signal status of the child will be stored | ||||
in self.exitstatus or self.signalstatus. If the child exited normally | ||||
then exitstatus will store the exit return code and signalstatus will | ||||
be None. If the child was terminated abnormally with a signal then | ||||
signalstatus will store the signal value and exitstatus will be None. | ||||
If you need more detail you can also read the self.status member which | ||||
stores the status returned by os.waitpid. You can interpret this using | ||||
Thomas Kluyver
|
r17142 | os.WIFEXITED/os.WEXITSTATUS or os.WIFSIGNALED/os.TERMSIG. | ||
The echo attribute may be set to False to disable echoing of input. | ||||
As a pseudo-terminal, all input echoed by the "keyboard" (send() | ||||
or sendline()) will be repeated to output. For many cases, it is | ||||
not desirable to have echo enabled, and it may be later disabled | ||||
using setecho(False) followed by waitnoecho(). However, for some | ||||
platforms such as Solaris, this is not possible, and should be | ||||
disabled immediately on spawn. | ||||
''' | ||||
Fernando Perez
|
r2906 | |||
self.STDIN_FILENO = pty.STDIN_FILENO | ||||
self.STDOUT_FILENO = pty.STDOUT_FILENO | ||||
self.STDERR_FILENO = pty.STDERR_FILENO | ||||
self.stdin = sys.stdin | ||||
self.stdout = sys.stdout | ||||
self.stderr = sys.stderr | ||||
self.searcher = None | ||||
self.ignorecase = False | ||||
self.before = None | ||||
self.after = None | ||||
self.match = None | ||||
self.match_index = None | ||||
self.terminated = True | ||||
self.exitstatus = None | ||||
self.signalstatus = None | ||||
Thomas Kluyver
|
r16384 | # status returned by os.waitpid | ||
self.status = None | ||||
Fernando Perez
|
r2906 | self.flag_eof = False | ||
self.pid = None | ||||
Thomas Kluyver
|
r17142 | # the child file descriptor is initially closed | ||
Thomas Kluyver
|
r16384 | self.child_fd = -1 | ||
Fernando Perez
|
r2906 | self.timeout = timeout | ||
self.delimiter = EOF | ||||
self.logfile = logfile | ||||
Thomas Kluyver
|
r16384 | # input from child (read_nonblocking) | ||
self.logfile_read = None | ||||
# output to send (send, sendline) | ||||
self.logfile_send = None | ||||
# max bytes to read at one time into buffer | ||||
self.maxread = maxread | ||||
# This is the read buffer. See maxread. | ||||
self.buffer = self.string_type() | ||||
# Data before searchwindowsize point is preserved, but not searched. | ||||
self.searchwindowsize = searchwindowsize | ||||
# Delay used before sending data to child. Time in seconds. | ||||
# Most Linux machines don't like this to be below 0.03 (30 ms). | ||||
self.delaybeforesend = 0.05 | ||||
# Used by close() to give kernel time to update process status. | ||||
# Time in seconds. | ||||
self.delayafterclose = 0.1 | ||||
# Used by terminate() to give kernel time to update process status. | ||||
# Time in seconds. | ||||
self.delayafterterminate = 0.1 | ||||
self.softspace = False | ||||
self.name = '<' + repr(self) + '>' | ||||
self.closed = True | ||||
Fernando Perez
|
r2906 | self.cwd = cwd | ||
self.env = env | ||||
Thomas Kluyver
|
r17142 | self.echo = echo | ||
Thomas Kluyver
|
r16384 | self.ignore_sighup = ignore_sighup | ||
Thomas Kluyver
|
r17142 | _platform = sys.platform.lower() | ||
Thomas Kluyver
|
r16384 | # This flags if we are running on irix | ||
Thomas Kluyver
|
r17142 | self.__irix_hack = _platform.startswith('irix') | ||
Fernando Perez
|
r2906 | # Solaris uses internal __fork_pty(). All others use pty.fork(). | ||
Thomas Kluyver
|
r17142 | self.use_native_pty_fork = not ( | ||
_platform.startswith('solaris') or | ||||
_platform.startswith('sunos')) | ||||
# inherit EOF and INTR definitions from controlling process. | ||||
try: | ||||
from termios import VEOF, VINTR | ||||
fd = sys.__stdin__.fileno() | ||||
self._INTR = ord(termios.tcgetattr(fd)[6][VINTR]) | ||||
self._EOF = ord(termios.tcgetattr(fd)[6][VEOF]) | ||||
except (ImportError, OSError, IOError, termios.error): | ||||
# unless the controlling process is also not a terminal, | ||||
# such as cron(1). Fall-back to using CEOF and CINTR. | ||||
try: | ||||
from termios import CEOF, CINTR | ||||
(self._INTR, self._EOF) = (CINTR, CEOF) | ||||
except ImportError: | ||||
# ^C, ^D | ||||
(self._INTR, self._EOF) = (3, 4) | ||||
Thomas Kluyver
|
r16384 | # Support subclasses that do not use command or args. | ||
Fernando Perez
|
r2906 | if command is None: | ||
self.command = None | ||||
self.args = None | ||||
self.name = '<pexpect factory incomplete>' | ||||
else: | ||||
Thomas Kluyver
|
r16384 | self._spawn(command, args) | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | @staticmethod | ||
def _coerce_expect_string(s): | ||||
if not isinstance(s, bytes): | ||||
return s.encode('ascii') | ||||
return s | ||||
@staticmethod | ||||
def _coerce_send_string(s): | ||||
if not isinstance(s, bytes): | ||||
return s.encode('utf-8') | ||||
return s | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | @staticmethod | ||
def _coerce_read_string(s): | ||||
return s | ||||
def __del__(self): | ||||
'''This makes sure that no system resources are left open. Python only | ||||
Fernando Perez
|
r2906 | garbage collects Python objects. OS file descriptors are not Python | ||
objects, so they must be handled explicitly. If the child file | ||||
descriptor was opened outside of this class (passed to the constructor) | ||||
Thomas Kluyver
|
r16384 | then this does not close it. ''' | ||
Fernando Perez
|
r2906 | |||
if not self.closed: | ||||
# It is possible for __del__ methods to execute during the | ||||
# teardown of the Python VM itself. Thus self.close() may | ||||
# trigger an exception because os.close may be None. | ||||
try: | ||||
self.close() | ||||
Thomas Kluyver
|
r16384 | # which exception, shouldnt' we catch explicitly .. ? | ||
Thomas Kluyver
|
r5430 | except: | ||
Fernando Perez
|
r2906 | pass | ||
def __str__(self): | ||||
Thomas Kluyver
|
r16384 | '''This returns a human-readable string that represents the state of | ||
the object. ''' | ||||
Fernando Perez
|
r2906 | |||
s = [] | ||||
s.append(repr(self)) | ||||
Thomas Kluyver
|
r5430 | s.append('version: ' + __version__) | ||
Fernando Perez
|
r2906 | s.append('command: ' + str(self.command)) | ||
Thomas Kluyver
|
r16384 | s.append('args: %r' % (self.args,)) | ||
s.append('searcher: %r' % (self.searcher,)) | ||||
s.append('buffer (last 100 chars): %r' % (self.buffer)[-100:],) | ||||
s.append('before (last 100 chars): %r' % (self.before)[-100:],) | ||||
s.append('after: %r' % (self.after,)) | ||||
s.append('match: %r' % (self.match,)) | ||||
Fernando Perez
|
r2906 | s.append('match_index: ' + str(self.match_index)) | ||
s.append('exitstatus: ' + str(self.exitstatus)) | ||||
s.append('flag_eof: ' + str(self.flag_eof)) | ||||
s.append('pid: ' + str(self.pid)) | ||||
s.append('child_fd: ' + str(self.child_fd)) | ||||
s.append('closed: ' + str(self.closed)) | ||||
s.append('timeout: ' + str(self.timeout)) | ||||
s.append('delimiter: ' + str(self.delimiter)) | ||||
s.append('logfile: ' + str(self.logfile)) | ||||
s.append('logfile_read: ' + str(self.logfile_read)) | ||||
s.append('logfile_send: ' + str(self.logfile_send)) | ||||
s.append('maxread: ' + str(self.maxread)) | ||||
s.append('ignorecase: ' + str(self.ignorecase)) | ||||
s.append('searchwindowsize: ' + str(self.searchwindowsize)) | ||||
s.append('delaybeforesend: ' + str(self.delaybeforesend)) | ||||
s.append('delayafterclose: ' + str(self.delayafterclose)) | ||||
s.append('delayafterterminate: ' + str(self.delayafterterminate)) | ||||
return '\n'.join(s) | ||||
Thomas Kluyver
|
r16384 | def _spawn(self, command, args=[]): | ||
'''This starts the given command in a child process. This does all the | ||||
Fernando Perez
|
r2906 | fork/exec type of stuff for a pty. This is called by __init__. If args | ||
is empty then command will be parsed (split on spaces) and args will be | ||||
Thomas Kluyver
|
r16384 | set to parsed arguments. ''' | ||
Fernando Perez
|
r2906 | |||
# The pid and child_fd of this object get set by this method. | ||||
# Note that it is difficult for this method to fail. | ||||
# You cannot detect if the child process cannot start. | ||||
# So the only way you can tell if the child process started | ||||
# or not is to try to read from the file descriptor. If you get | ||||
# EOF immediately then it means that the child is already dead. | ||||
Thomas Kluyver
|
r16384 | # That may not necessarily be bad because you may have spawned a child | ||
Fernando Perez
|
r2906 | # that performs some task; creates no stdout output; and then dies. | ||
# If command is an int type then it may represent a file descriptor. | ||||
Thomas Kluyver
|
r16384 | if isinstance(command, type(0)): | ||
raise ExceptionPexpect('Command is an int type. ' + | ||||
'If this is a file descriptor then maybe you want to ' + | ||||
'use fdpexpect.fdspawn which takes an existing ' + | ||||
'file descriptor instead of a command string.') | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | if not isinstance(args, type([])): | ||
raise TypeError('The argument, args, must be a list.') | ||||
Fernando Perez
|
r2906 | |||
if args == []: | ||||
self.args = split_command_line(command) | ||||
self.command = self.args[0] | ||||
else: | ||||
Thomas Kluyver
|
r16384 | # Make a shallow copy of the args list. | ||
self.args = args[:] | ||||
self.args.insert(0, command) | ||||
Fernando Perez
|
r2906 | self.command = command | ||
command_with_path = which(self.command) | ||||
if command_with_path is None: | ||||
Thomas Kluyver
|
r16384 | raise ExceptionPexpect('The command was not found or was not ' + | ||
'executable: %s.' % self.command) | ||||
Fernando Perez
|
r2906 | self.command = command_with_path | ||
self.args[0] = self.command | ||||
Thomas Kluyver
|
r16384 | self.name = '<' + ' '.join(self.args) + '>' | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | assert self.pid is None, 'The pid member must be None.' | ||
assert self.command is not None, 'The command member must not be None.' | ||||
Fernando Perez
|
r2906 | |||
if self.use_native_pty_fork: | ||||
try: | ||||
self.pid, self.child_fd = pty.fork() | ||||
Thomas Kluyver
|
r17142 | except OSError: # pragma: no cover | ||
Thomas Kluyver
|
r16384 | err = sys.exc_info()[1] | ||
raise ExceptionPexpect('pty.fork() failed: ' + str(err)) | ||||
else: | ||||
# Use internal __fork_pty | ||||
Fernando Perez
|
r2906 | self.pid, self.child_fd = self.__fork_pty() | ||
Thomas Kluyver
|
r17142 | # Some platforms must call setwinsize() and setecho() from the | ||
# child process, and others from the master process. We do both, | ||||
# allowing IOError for either. | ||||
if self.pid == pty.CHILD: | ||||
Thomas Kluyver
|
r16384 | # Child | ||
Thomas Kluyver
|
r17142 | self.child_fd = self.STDIN_FILENO | ||
# set default window size of 24 rows by 80 columns | ||||
Fernando Perez
|
r2906 | try: | ||
self.setwinsize(24, 80) | ||||
Thomas Kluyver
|
r17142 | except IOError as err: | ||
if err.args[0] not in (errno.EINVAL, errno.ENOTTY): | ||||
raise | ||||
# disable echo if spawn argument echo was unset | ||||
if not self.echo: | ||||
try: | ||||
self.setecho(self.echo) | ||||
except (IOError, termios.error) as err: | ||||
if err.args[0] not in (errno.EINVAL, errno.ENOTTY): | ||||
raise | ||||
Fernando Perez
|
r2906 | # Do not allow child to inherit open file descriptors from parent. | ||
max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0] | ||||
Thomas Kluyver
|
r17142 | os.closerange(3, max_fd) | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | if self.ignore_sighup: | ||
signal.signal(signal.SIGHUP, signal.SIG_IGN) | ||||
Fernando Perez
|
r2906 | |||
if self.cwd is not None: | ||||
os.chdir(self.cwd) | ||||
if self.env is None: | ||||
os.execv(self.command, self.args) | ||||
else: | ||||
os.execvpe(self.command, self.args, self.env) | ||||
# Parent | ||||
Thomas Kluyver
|
r17142 | try: | ||
self.setwinsize(24, 80) | ||||
except IOError as err: | ||||
if err.args[0] not in (errno.EINVAL, errno.ENOTTY): | ||||
raise | ||||
Fernando Perez
|
r2906 | self.terminated = False | ||
self.closed = False | ||||
def __fork_pty(self): | ||||
Thomas Kluyver
|
r16384 | '''This implements a substitute for the forkpty system call. This | ||
Fernando Perez
|
r2906 | should be more portable than the pty.fork() function. Specifically, | ||
this should work on Solaris. | ||||
Modified 10.06.05 by Geoff Marshall: Implemented __fork_pty() method to | ||||
resolve the issue with Python's pty.fork() not supporting Solaris, | ||||
particularly ssh. Based on patch to posixmodule.c authored by Noah | ||||
Spurrier:: | ||||
http://mail.python.org/pipermail/python-dev/2003-May/035281.html | ||||
Thomas Kluyver
|
r16384 | ''' | ||
Fernando Perez
|
r2906 | |||
parent_fd, child_fd = os.openpty() | ||||
if parent_fd < 0 or child_fd < 0: | ||||
Thomas Kluyver
|
r16384 | raise ExceptionPexpect("Could not open with os.openpty().") | ||
Fernando Perez
|
r2906 | |||
pid = os.fork() | ||||
Thomas Kluyver
|
r17142 | if pid == pty.CHILD: | ||
Fernando Perez
|
r2906 | # Child. | ||
os.close(parent_fd) | ||||
self.__pty_make_controlling_tty(child_fd) | ||||
Thomas Kluyver
|
r17142 | os.dup2(child_fd, self.STDIN_FILENO) | ||
os.dup2(child_fd, self.STDOUT_FILENO) | ||||
os.dup2(child_fd, self.STDERR_FILENO) | ||||
Fernando Perez
|
r2906 | |||
else: | ||||
# Parent. | ||||
os.close(child_fd) | ||||
return pid, parent_fd | ||||
def __pty_make_controlling_tty(self, tty_fd): | ||||
Thomas Kluyver
|
r16384 | '''This makes the pseudo-terminal the controlling tty. This should be | ||
Fernando Perez
|
r2906 | more portable than the pty.fork() function. Specifically, this should | ||
Thomas Kluyver
|
r16384 | work on Solaris. ''' | ||
Fernando Perez
|
r2906 | |||
child_name = os.ttyname(tty_fd) | ||||
Thomas Kluyver
|
r17142 | # Disconnect from controlling tty, if any. Raises OSError of ENXIO | ||
# if there was no controlling tty to begin with, such as when | ||||
# executed by a cron(1) job. | ||||
Thomas Kluyver
|
r5430 | try: | ||
Thomas Kluyver
|
r16384 | fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY) | ||
Thomas Kluyver
|
r17142 | os.close(fd) | ||
except OSError as err: | ||||
if err.errno != errno.ENXIO: | ||||
raise | ||||
Fernando Perez
|
r2906 | |||
os.setsid() | ||||
Thomas Kluyver
|
r17142 | # Verify we are disconnected from controlling tty by attempting to open | ||
# it again. We expect that OSError of ENXIO should always be raised. | ||||
Fernando Perez
|
r2906 | try: | ||
Thomas Kluyver
|
r16384 | fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY) | ||
Thomas Kluyver
|
r17142 | os.close(fd) | ||
raise ExceptionPexpect("OSError of errno.ENXIO should be raised.") | ||||
except OSError as err: | ||||
if err.errno != errno.ENXIO: | ||||
raise | ||||
Fernando Perez
|
r2906 | |||
# Verify we can open child pty. | ||||
Thomas Kluyver
|
r16384 | fd = os.open(child_name, os.O_RDWR) | ||
Thomas Kluyver
|
r17142 | os.close(fd) | ||
Fernando Perez
|
r2906 | |||
# Verify we now have a controlling tty. | ||||
fd = os.open("/dev/tty", os.O_WRONLY) | ||||
Thomas Kluyver
|
r17142 | os.close(fd) | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | def fileno(self): | ||
'''This returns the file descriptor of the pty for the child. | ||||
''' | ||||
Fernando Perez
|
r2906 | return self.child_fd | ||
Thomas Kluyver
|
r16384 | def close(self, force=True): | ||
'''This closes the connection with the child application. Note that | ||||
Fernando Perez
|
r2906 | calling close() more than once is valid. This emulates standard Python | ||
behavior with files. Set force to True if you want to make sure that | ||||
the child is terminated (SIGKILL is sent if the child ignores SIGHUP | ||||
Thomas Kluyver
|
r16384 | and SIGINT). ''' | ||
Fernando Perez
|
r2906 | |||
if not self.closed: | ||||
self.flush() | ||||
Thomas Kluyver
|
r16384 | os.close(self.child_fd) | ||
# Give kernel time to update process status. | ||||
time.sleep(self.delayafterclose) | ||||
Fernando Perez
|
r2906 | if self.isalive(): | ||
if not self.terminate(force): | ||||
Thomas Kluyver
|
r16384 | raise ExceptionPexpect('Could not terminate the child.') | ||
Fernando Perez
|
r2906 | self.child_fd = -1 | ||
self.closed = True | ||||
#self.pid = None | ||||
Thomas Kluyver
|
r16384 | def flush(self): | ||
'''This does nothing. It is here to support the interface for a | ||||
File-like object. ''' | ||||
Fernando Perez
|
r2906 | |||
pass | ||||
Thomas Kluyver
|
r16384 | def isatty(self): | ||
'''This returns True if the file descriptor is open and connected to a | ||||
Thomas Kluyver
|
r17142 | tty(-like) device, else False. | ||
On SVR4-style platforms implementing streams, such as SunOS and HP-UX, | ||||
the child pty may not appear as a terminal device. This means | ||||
methods such as setecho(), setwinsize(), getwinsize() may raise an | ||||
IOError. ''' | ||||
Fernando Perez
|
r2906 | |||
return os.isatty(self.child_fd) | ||||
Thomas Kluyver
|
r16384 | def waitnoecho(self, timeout=-1): | ||
'''This waits until the terminal ECHO flag is set False. This returns | ||||
Fernando Perez
|
r2906 | True if the echo mode is off. This returns False if the ECHO flag was | ||
not set False before the timeout. This can be used to detect when the | ||||
child is waiting for a password. Usually a child application will turn | ||||
off echo mode when it is waiting for the user to enter a password. For | ||||
example, instead of expecting the "password:" prompt you can wait for | ||||
the child to set ECHO off:: | ||||
Thomas Kluyver
|
r16384 | p = pexpect.spawn('ssh user@example.com') | ||
Fernando Perez
|
r2906 | p.waitnoecho() | ||
p.sendline(mypassword) | ||||
Thomas Kluyver
|
r5430 | If timeout==-1 then this method will use the value in self.timeout. | ||
If timeout==None then this method to block until ECHO flag is False. | ||||
Thomas Kluyver
|
r16384 | ''' | ||
Fernando Perez
|
r2906 | |||
if timeout == -1: | ||||
timeout = self.timeout | ||||
if timeout is not None: | ||||
end_time = time.time() + timeout | ||||
while True: | ||||
if not self.getecho(): | ||||
return True | ||||
if timeout < 0 and timeout is not None: | ||||
return False | ||||
if timeout is not None: | ||||
timeout = end_time - time.time() | ||||
time.sleep(0.1) | ||||
Thomas Kluyver
|
r16384 | def getecho(self): | ||
'''This returns the terminal echo mode. This returns True if echo is | ||||
Fernando Perez
|
r2906 | on or False if echo is off. Child applications that are expecting you | ||
Thomas Kluyver
|
r17142 | to enter a password often set ECHO False. See waitnoecho(). | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r17142 | Not supported on platforms where ``isatty()`` returns False. ''' | ||
try: | ||||
attr = termios.tcgetattr(self.child_fd) | ||||
except termios.error as err: | ||||
errmsg = 'getecho() may not be called on this platform' | ||||
if err.args[0] == errno.EINVAL: | ||||
raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg)) | ||||
raise | ||||
self.echo = bool(attr[3] & termios.ECHO) | ||||
return self.echo | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | def setecho(self, state): | ||
'''This sets the terminal echo mode on or off. Note that anything the | ||||
Fernando Perez
|
r2906 | child sent before the echo will be lost, so you should be sure that | ||
your input buffer is empty before you call setecho(). For example, the | ||||
following will work as expected:: | ||||
Thomas Kluyver
|
r16384 | p = pexpect.spawn('cat') # Echo is on by default. | ||
p.sendline('1234') # We expect see this twice from the child... | ||||
p.expect(['1234']) # ... once from the tty echo... | ||||
p.expect(['1234']) # ... and again from cat itself. | ||||
Fernando Perez
|
r2906 | p.setecho(False) # Turn off tty echo | ||
Thomas Kluyver
|
r16384 | p.sendline('abcd') # We will set this only once (echoed by cat). | ||
p.sendline('wxyz') # We will set this only once (echoed by cat) | ||||
p.expect(['abcd']) | ||||
p.expect(['wxyz']) | ||||
Fernando Perez
|
r2906 | |||
The following WILL NOT WORK because the lines sent before the setecho | ||||
will be lost:: | ||||
p = pexpect.spawn('cat') | ||||
Thomas Kluyver
|
r16384 | p.sendline('1234') | ||
Fernando Perez
|
r2906 | p.setecho(False) # Turn off tty echo | ||
Thomas Kluyver
|
r16384 | p.sendline('abcd') # We will set this only once (echoed by cat). | ||
p.sendline('wxyz') # We will set this only once (echoed by cat) | ||||
p.expect(['1234']) | ||||
p.expect(['1234']) | ||||
p.expect(['abcd']) | ||||
p.expect(['wxyz']) | ||||
Thomas Kluyver
|
r17142 | |||
Not supported on platforms where ``isatty()`` returns False. | ||||
Thomas Kluyver
|
r16384 | ''' | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r17142 | errmsg = 'setecho() may not be called on this platform' | ||
try: | ||||
attr = termios.tcgetattr(self.child_fd) | ||||
except termios.error as err: | ||||
if err.args[0] == errno.EINVAL: | ||||
raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg)) | ||||
raise | ||||
Fernando Perez
|
r2906 | if state: | ||
attr[3] = attr[3] | termios.ECHO | ||||
else: | ||||
attr[3] = attr[3] & ~termios.ECHO | ||||
Thomas Kluyver
|
r17142 | |||
try: | ||||
# I tried TCSADRAIN and TCSAFLUSH, but these were inconsistent and | ||||
# blocked on some platforms. TCSADRAIN would probably be ideal. | ||||
termios.tcsetattr(self.child_fd, termios.TCSANOW, attr) | ||||
except IOError as err: | ||||
if err.args[0] == errno.EINVAL: | ||||
raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg)) | ||||
raise | ||||
self.echo = state | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | def _log(self, s, direction): | ||
if self.logfile is not None: | ||||
self.logfile.write(s) | ||||
self.logfile.flush() | ||||
second_log = self.logfile_send if (direction=='send') else self.logfile_read | ||||
if second_log is not None: | ||||
second_log.write(s) | ||||
second_log.flush() | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | def read_nonblocking(self, size=1, timeout=-1): | ||
'''This reads at most size characters from the child application. It | ||||
Fernando Perez
|
r2906 | includes a timeout. If the read does not complete within the timeout | ||
period then a TIMEOUT exception is raised. If the end of file is read | ||||
then an EOF exception will be raised. If a log file was set using | ||||
setlog() then all data will also be written to the log file. | ||||
Thomas Kluyver
|
r16384 | If timeout is None then the read may block indefinitely. | ||
If timeout is -1 then the self.timeout value is used. If timeout is 0 | ||||
then the child is polled and if there is no data immediately ready | ||||
then this will raise a TIMEOUT exception. | ||||
Fernando Perez
|
r2906 | |||
The timeout refers only to the amount of time to read at least one | ||||
character. This is not effected by the 'size' parameter, so if you call | ||||
read_nonblocking(size=100, timeout=30) and only one character is | ||||
available right away then one character will be returned immediately. | ||||
It will not wait for 30 seconds for another 99 characters to come in. | ||||
This is a wrapper around os.read(). It uses select.select() to | ||||
Thomas Kluyver
|
r16384 | implement the timeout. ''' | ||
Fernando Perez
|
r2906 | |||
if self.closed: | ||||
Thomas Kluyver
|
r16384 | raise ValueError('I/O operation on closed file.') | ||
Fernando Perez
|
r2906 | |||
if timeout == -1: | ||||
timeout = self.timeout | ||||
# Note that some systems such as Solaris do not give an EOF when | ||||
# the child dies. In fact, you can still try to read | ||||
# from the child_fd -- it will block forever or until TIMEOUT. | ||||
# For this case, I test isalive() before doing any reading. | ||||
# If isalive() is false, then I pretend that this is the same as EOF. | ||||
if not self.isalive(): | ||||
Thomas Kluyver
|
r16384 | # timeout of 0 means "poll" | ||
r, w, e = self.__select([self.child_fd], [], [], 0) | ||||
Fernando Perez
|
r2906 | if not r: | ||
self.flag_eof = True | ||||
Thomas Kluyver
|
r16384 | raise EOF('End Of File (EOF). Braindead platform.') | ||
Fernando Perez
|
r2906 | elif self.__irix_hack: | ||
Thomas Kluyver
|
r16384 | # Irix takes a long time before it realizes a child was terminated. | ||
# FIXME So does this mean Irix systems are forced to always have | ||||
# FIXME a 2 second delay when calling read_nonblocking? That sucks. | ||||
Fernando Perez
|
r2906 | r, w, e = self.__select([self.child_fd], [], [], 2) | ||
if not r and not self.isalive(): | ||||
self.flag_eof = True | ||||
Thomas Kluyver
|
r16384 | raise EOF('End Of File (EOF). Slow platform.') | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | r, w, e = self.__select([self.child_fd], [], [], timeout) | ||
Fernando Perez
|
r2906 | |||
if not r: | ||||
if not self.isalive(): | ||||
Thomas Kluyver
|
r16384 | # Some platforms, such as Irix, will claim that their | ||
# processes are alive; timeout on the select; and | ||||
# then finally admit that they are not alive. | ||||
Fernando Perez
|
r2906 | self.flag_eof = True | ||
Thomas Kluyver
|
r16384 | raise EOF('End of File (EOF). Very slow platform.') | ||
Fernando Perez
|
r2906 | else: | ||
Thomas Kluyver
|
r16384 | raise TIMEOUT('Timeout exceeded.') | ||
Fernando Perez
|
r2906 | |||
if self.child_fd in r: | ||||
try: | ||||
s = os.read(self.child_fd, size) | ||||
Thomas Kluyver
|
r17142 | except OSError as err: | ||
if err.args[0] == errno.EIO: | ||||
# Linux-style EOF | ||||
self.flag_eof = True | ||||
raise EOF('End Of File (EOF). Exception style platform.') | ||||
raise | ||||
Thomas Kluyver
|
r16384 | if s == b'': | ||
Thomas Kluyver
|
r17142 | # BSD-style EOF | ||
Fernando Perez
|
r2906 | self.flag_eof = True | ||
Thomas Kluyver
|
r16384 | raise EOF('End Of File (EOF). Empty string style platform.') | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | s = self._coerce_read_string(s) | ||
self._log(s, 'read') | ||||
Fernando Perez
|
r2906 | return s | ||
Thomas Kluyver
|
r17142 | raise ExceptionPexpect('Reached an unexpected state.') # pragma: no cover | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | def read(self, size=-1): | ||
'''This reads at most "size" bytes from the file (less if the read hits | ||||
Fernando Perez
|
r2906 | EOF before obtaining size bytes). If the size argument is negative or | ||
omitted, read all data until EOF is reached. The bytes are returned as | ||||
a string object. An empty string is returned when EOF is encountered | ||||
Thomas Kluyver
|
r16384 | immediately. ''' | ||
Fernando Perez
|
r2906 | |||
if size == 0: | ||||
Thomas Kluyver
|
r16384 | return self.string_type() | ||
Fernando Perez
|
r2906 | if size < 0: | ||
Thomas Kluyver
|
r16384 | # delimiter default is EOF | ||
self.expect(self.delimiter) | ||||
Fernando Perez
|
r2906 | return self.before | ||
# I could have done this more directly by not using expect(), but | ||||
# I deliberately decided to couple read() to expect() so that | ||||
# I would catch any bugs early and ensure consistant behavior. | ||||
# It's a little less efficient, but there is less for me to | ||||
# worry about if I have to later modify read() or expect(). | ||||
# Note, it's OK if size==-1 in the regex. That just means it | ||||
# will never match anything in which case we stop only on EOF. | ||||
Thomas Kluyver
|
r16384 | cre = re.compile(self._coerce_expect_string('.{%d}' % size), re.DOTALL) | ||
# delimiter default is EOF | ||||
index = self.expect([cre, self.delimiter]) | ||||
Fernando Perez
|
r2906 | if index == 0: | ||
Thomas Kluyver
|
r16384 | ### FIXME self.before should be ''. Should I assert this? | ||
return self.after | ||||
Fernando Perez
|
r2906 | return self.before | ||
Thomas Kluyver
|
r16384 | def readline(self, size=-1): | ||
'''This reads and returns one entire line. The newline at the end of | ||||
line is returned as part of the string, unless the file ends without a | ||||
newline. An empty string is returned if EOF is encountered immediately. | ||||
This looks for a newline as a CR/LF pair (\\r\\n) even on UNIX because | ||||
this is what the pseudotty device returns. So contrary to what you may | ||||
expect you will receive newlines as \\r\\n. | ||||
If the size argument is 0 then an empty string is returned. In all | ||||
other cases the size argument is ignored, which is not standard | ||||
behavior for a file-like object. ''' | ||||
Fernando Perez
|
r2906 | |||
if size == 0: | ||||
Thomas Kluyver
|
r16384 | return self.string_type() | ||
# delimiter default is EOF | ||||
Thomas Kluyver
|
r17142 | index = self.expect([self.crlf, self.delimiter]) | ||
Fernando Perez
|
r2906 | if index == 0: | ||
Thomas Kluyver
|
r17142 | return self.before + self.crlf | ||
Thomas Kluyver
|
r16384 | else: | ||
return self.before | ||||
Bradley M. Froehle
|
r7847 | |||
Thomas Kluyver
|
r16384 | def __iter__(self): | ||
'''This is to support iterators over a file-like object. | ||||
''' | ||||
return iter(self.readline, self.string_type()) | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | def readlines(self, sizehint=-1): | ||
'''This reads until EOF using readline() and returns a list containing | ||||
the lines thus read. The optional 'sizehint' argument is ignored. | ||||
Remember, because this reads until EOF that means the child | ||||
process should have closed its stdout. If you run this method on | ||||
a child that is still running with its stdout open then this | ||||
method will block until it timesout.''' | ||||
Fernando Perez
|
r2906 | |||
lines = [] | ||||
while True: | ||||
line = self.readline() | ||||
if not line: | ||||
break | ||||
lines.append(line) | ||||
return lines | ||||
Thomas Kluyver
|
r16384 | def write(self, s): | ||
'''This is similar to send() except that there is no return value. | ||||
''' | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | self.send(s) | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | def writelines(self, sequence): | ||
'''This calls write() for each element in the sequence. The sequence | ||||
Fernando Perez
|
r2906 | can be any iterable object producing strings, typically a list of | ||
Thomas Kluyver
|
r16384 | strings. This does not add line separators. There is no return value. | ||
''' | ||||
Fernando Perez
|
r2906 | |||
for s in sequence: | ||||
Thomas Kluyver
|
r16384 | self.write(s) | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r5430 | def send(self, s): | ||
Thomas Kluyver
|
r16384 | '''Sends string ``s`` to the child process, returning the number of | ||
bytes written. If a logfile is specified, a copy is written to that | ||||
log. ''' | ||||
Fernando Perez
|
r2906 | |||
time.sleep(self.delaybeforesend) | ||||
Thomas Kluyver
|
r16384 | s = self._coerce_send_string(s) | ||
self._log(s, 'send') | ||||
return self._send(s) | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | def _send(self, s): | ||
return os.write(self.child_fd, s) | ||||
def sendline(self, s=''): | ||||
'''Wraps send(), sending string ``s`` to child process, with os.linesep | ||||
automatically appended. Returns number of bytes written. ''' | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | n = self.send(s) | ||
n = n + self.send(self.linesep) | ||||
Fernando Perez
|
r2906 | return n | ||
def sendcontrol(self, char): | ||||
Thomas Kluyver
|
r16384 | '''Helper method that wraps send() with mnemonic access for sending control | ||
character to the child (such as Ctrl-C or Ctrl-D). For example, to send | ||||
Ctrl-G (ASCII 7, bell, '\a'):: | ||||
Fernando Perez
|
r2906 | |||
child.sendcontrol('g') | ||||
See also, sendintr() and sendeof(). | ||||
Thomas Kluyver
|
r16384 | ''' | ||
Fernando Perez
|
r2906 | |||
char = char.lower() | ||||
a = ord(char) | ||||
Thomas Kluyver
|
r16384 | if a >= 97 and a <= 122: | ||
Fernando Perez
|
r2906 | a = a - ord('a') + 1 | ||
Thomas Kluyver
|
r16384 | return self.send(self._chr(a)) | ||
d = {'@': 0, '`': 0, | ||||
'[': 27, '{': 27, | ||||
'\\': 28, '|': 28, | ||||
']': 29, '}': 29, | ||||
'^': 30, '~': 30, | ||||
'_': 31, | ||||
'?': 127} | ||||
Fernando Perez
|
r2906 | if char not in d: | ||
return 0 | ||||
Thomas Kluyver
|
r16384 | return self.send(self._chr(d[char])) | ||
Fernando Perez
|
r2906 | |||
def sendeof(self): | ||||
Thomas Kluyver
|
r16384 | '''This sends an EOF to the child. This sends a character which causes | ||
Fernando Perez
|
r2906 | the pending parent output buffer to be sent to the waiting child | ||
program without waiting for end-of-line. If it is the first character | ||||
of the line, the read() in the user program returns 0, which signifies | ||||
end-of-file. This means to work as expected a sendeof() has to be | ||||
called at the beginning of a line. This method does not send a newline. | ||||
It is the responsibility of the caller to ensure the eof is sent at the | ||||
Thomas Kluyver
|
r16384 | beginning of a line. ''' | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r17142 | self.send(self._chr(self._EOF)) | ||
Fernando Perez
|
r2906 | |||
def sendintr(self): | ||||
Thomas Kluyver
|
r16384 | '''This sends a SIGINT to the child. It does not require | ||
the SIGINT to be the first character on a line. ''' | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r17142 | self.send(self._chr(self._INTR)) | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | def eof(self): | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | '''This returns True if the EOF exception was ever raised. | ||
''' | ||||
Fernando Perez
|
r2906 | |||
return self.flag_eof | ||||
def terminate(self, force=False): | ||||
Thomas Kluyver
|
r16384 | '''This forces a child process to terminate. It starts nicely with | ||
Fernando Perez
|
r2906 | SIGHUP and SIGINT. If "force" is True then moves onto SIGKILL. This | ||
returns True if the child was terminated. This returns False if the | ||||
Thomas Kluyver
|
r16384 | child could not be terminated. ''' | ||
Fernando Perez
|
r2906 | |||
if not self.isalive(): | ||||
return True | ||||
try: | ||||
self.kill(signal.SIGHUP) | ||||
time.sleep(self.delayafterterminate) | ||||
if not self.isalive(): | ||||
return True | ||||
self.kill(signal.SIGCONT) | ||||
time.sleep(self.delayafterterminate) | ||||
if not self.isalive(): | ||||
return True | ||||
self.kill(signal.SIGINT) | ||||
time.sleep(self.delayafterterminate) | ||||
if not self.isalive(): | ||||
return True | ||||
if force: | ||||
self.kill(signal.SIGKILL) | ||||
time.sleep(self.delayafterterminate) | ||||
if not self.isalive(): | ||||
return True | ||||
else: | ||||
return False | ||||
return False | ||||
Thomas Kluyver
|
r16384 | except OSError: | ||
Fernando Perez
|
r2906 | # I think there are kernel timing issues that sometimes cause | ||
# this to happen. I think isalive() reports True, but the | ||||
# process is dead to the kernel. | ||||
# Make one last attempt to see if the kernel is up to date. | ||||
time.sleep(self.delayafterterminate) | ||||
if not self.isalive(): | ||||
return True | ||||
else: | ||||
return False | ||||
def wait(self): | ||||
Thomas Kluyver
|
r16384 | '''This waits until the child exits. This is a blocking call. This will | ||
Fernando Perez
|
r2906 | not read any data from the child, so this will block forever if the | ||
child has unread output and has terminated. In other words, the child | ||||
Thomas Kluyver
|
r16384 | may have printed output then called exit(), but, the child is | ||
technically still alive until its output is read by the parent. ''' | ||||
Fernando Perez
|
r2906 | |||
if self.isalive(): | ||||
pid, status = os.waitpid(self.pid, 0) | ||||
else: | ||||
Thomas Kluyver
|
r16384 | raise ExceptionPexpect('Cannot wait for dead child process.') | ||
Fernando Perez
|
r2906 | self.exitstatus = os.WEXITSTATUS(status) | ||
Thomas Kluyver
|
r16384 | if os.WIFEXITED(status): | ||
Fernando Perez
|
r2906 | self.status = status | ||
self.exitstatus = os.WEXITSTATUS(status) | ||||
self.signalstatus = None | ||||
self.terminated = True | ||||
Thomas Kluyver
|
r16384 | elif os.WIFSIGNALED(status): | ||
Fernando Perez
|
r2906 | self.status = status | ||
self.exitstatus = None | ||||
self.signalstatus = os.WTERMSIG(status) | ||||
self.terminated = True | ||||
Thomas Kluyver
|
r17142 | elif os.WIFSTOPPED(status): # pragma: no cover | ||
Thomas Kluyver
|
r16384 | # You can't call wait() on a child process in the stopped state. | ||
raise ExceptionPexpect('Called wait() on a stopped child ' + | ||||
'process. This is not supported. Is some other ' + | ||||
'process attempting job control with our child pid?') | ||||
Fernando Perez
|
r2906 | return self.exitstatus | ||
def isalive(self): | ||||
Thomas Kluyver
|
r16384 | '''This tests if the child process is running or not. This is | ||
Fernando Perez
|
r2906 | non-blocking. If the child was terminated then this will read the | ||
exitstatus or signalstatus of the child. This returns True if the child | ||||
process appears to be running or False if not. It can take literally | ||||
Thomas Kluyver
|
r16384 | SECONDS for Solaris to return the right status. ''' | ||
Fernando Perez
|
r2906 | |||
if self.terminated: | ||||
return False | ||||
if self.flag_eof: | ||||
Thomas Kluyver
|
r16384 | # This is for Linux, which requires the blocking form | ||
Thomas Kluyver
|
r17142 | # of waitpid to get the status of a defunct process. | ||
Thomas Kluyver
|
r16384 | # This is super-lame. The flag_eof would have been set | ||
# in read_nonblocking(), so this should be safe. | ||||
Fernando Perez
|
r2906 | waitpid_options = 0 | ||
else: | ||||
waitpid_options = os.WNOHANG | ||||
try: | ||||
pid, status = os.waitpid(self.pid, waitpid_options) | ||||
Thomas Kluyver
|
r16384 | except OSError: | ||
err = sys.exc_info()[1] | ||||
# No child processes | ||||
if err.errno == errno.ECHILD: | ||||
raise ExceptionPexpect('isalive() encountered condition ' + | ||||
'where "terminated" is 0, but there was no child ' + | ||||
'process. Did someone else call waitpid() ' + | ||||
'on our process?') | ||||
Fernando Perez
|
r2906 | else: | ||
Thomas Kluyver
|
r16384 | raise err | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | # I have to do this twice for Solaris. | ||
# I can't even believe that I figured this out... | ||||
# If waitpid() returns 0 it means that no child process | ||||
# wishes to report, and the value of status is undefined. | ||||
Fernando Perez
|
r2906 | if pid == 0: | ||
try: | ||||
Thomas Kluyver
|
r16384 | ### os.WNOHANG) # Solaris! | ||
pid, status = os.waitpid(self.pid, waitpid_options) | ||||
Thomas Kluyver
|
r17142 | except OSError as e: # pragma: no cover | ||
Thomas Kluyver
|
r16384 | # This should never happen... | ||
if e.errno == errno.ECHILD: | ||||
raise ExceptionPexpect('isalive() encountered condition ' + | ||||
'that should never happen. There was no child ' + | ||||
'process. Did someone else call waitpid() ' + | ||||
'on our process?') | ||||
Fernando Perez
|
r2906 | else: | ||
Thomas Kluyver
|
r16384 | raise | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | # If pid is still 0 after two calls to waitpid() then the process | ||
# really is alive. This seems to work on all platforms, except for | ||||
# Irix which seems to require a blocking call on waitpid or select, | ||||
# so I let read_nonblocking take care of this situation | ||||
# (unfortunately, this requires waiting through the timeout). | ||||
Fernando Perez
|
r2906 | if pid == 0: | ||
return True | ||||
if pid == 0: | ||||
return True | ||||
Thomas Kluyver
|
r16384 | if os.WIFEXITED(status): | ||
Fernando Perez
|
r2906 | self.status = status | ||
self.exitstatus = os.WEXITSTATUS(status) | ||||
self.signalstatus = None | ||||
self.terminated = True | ||||
Thomas Kluyver
|
r16384 | elif os.WIFSIGNALED(status): | ||
Fernando Perez
|
r2906 | self.status = status | ||
self.exitstatus = None | ||||
self.signalstatus = os.WTERMSIG(status) | ||||
self.terminated = True | ||||
Thomas Kluyver
|
r16384 | elif os.WIFSTOPPED(status): | ||
raise ExceptionPexpect('isalive() encountered condition ' + | ||||
'where child process is stopped. This is not ' + | ||||
'supported. Is some other process attempting ' + | ||||
'job control with our child pid?') | ||||
Fernando Perez
|
r2906 | return False | ||
def kill(self, sig): | ||||
Thomas Kluyver
|
r16384 | '''This sends the given signal to the child application. In keeping | ||
Fernando Perez
|
r2906 | with UNIX tradition it has a misleading name. It does not necessarily | ||
Thomas Kluyver
|
r16384 | kill the child unless you send the right signal. ''' | ||
Fernando Perez
|
r2906 | |||
# Same as os.kill, but the pid is given for you. | ||||
if self.isalive(): | ||||
os.kill(self.pid, sig) | ||||
Thomas Kluyver
|
r16384 | def _pattern_type_err(self, pattern): | ||
raise TypeError('got {badtype} ({badobj!r}) as pattern, must be one' | ||||
' of: {goodtypes}, pexpect.EOF, pexpect.TIMEOUT'\ | ||||
.format(badtype=type(pattern), | ||||
badobj=pattern, | ||||
goodtypes=', '.join([str(ast)\ | ||||
for ast in self.allowed_string_types]) | ||||
) | ||||
) | ||||
Fernando Perez
|
r2906 | def compile_pattern_list(self, patterns): | ||
Thomas Kluyver
|
r16384 | '''This compiles a pattern-string or a list of pattern-strings. | ||
Fernando Perez
|
r2906 | Patterns must be a StringType, EOF, TIMEOUT, SRE_Pattern, or a list of | ||
those. Patterns may also be None which results in an empty list (you | ||||
might do this if waiting for an EOF or TIMEOUT condition without | ||||
expecting any pattern). | ||||
This is used by expect() when calling expect_list(). Thus expect() is | ||||
nothing more than:: | ||||
cpl = self.compile_pattern_list(pl) | ||||
return self.expect_list(cpl, timeout) | ||||
If you are using expect() within a loop it may be more | ||||
efficient to compile the patterns first and then call expect_list(). | ||||
This avoid calls in a loop to compile_pattern_list():: | ||||
cpl = self.compile_pattern_list(my_pattern) | ||||
while some_condition: | ||||
... | ||||
i = self.expect_list(clp, timeout) | ||||
... | ||||
Thomas Kluyver
|
r16384 | ''' | ||
Fernando Perez
|
r2906 | |||
if patterns is None: | ||||
return [] | ||||
Thomas Kluyver
|
r4835 | if not isinstance(patterns, list): | ||
Fernando Perez
|
r2906 | patterns = [patterns] | ||
Thomas Kluyver
|
r16384 | # Allow dot to match \n | ||
compile_flags = re.DOTALL | ||||
Fernando Perez
|
r2906 | if self.ignorecase: | ||
compile_flags = compile_flags | re.IGNORECASE | ||||
compiled_pattern_list = [] | ||||
Thomas Kluyver
|
r16384 | for idx, p in enumerate(patterns): | ||
if isinstance(p, self.allowed_string_types): | ||||
p = self._coerce_expect_string(p) | ||||
Fernando Perez
|
r2906 | compiled_pattern_list.append(re.compile(p, compile_flags)) | ||
elif p is EOF: | ||||
compiled_pattern_list.append(EOF) | ||||
elif p is TIMEOUT: | ||||
compiled_pattern_list.append(TIMEOUT) | ||||
Thomas Kluyver
|
r16384 | elif isinstance(p, type(re.compile(''))): | ||
Fernando Perez
|
r2906 | compiled_pattern_list.append(p) | ||
else: | ||||
Thomas Kluyver
|
r16384 | self._pattern_type_err(p) | ||
Fernando Perez
|
r2906 | return compiled_pattern_list | ||
Thomas Kluyver
|
r16384 | def expect(self, pattern, timeout=-1, searchwindowsize=-1): | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | '''This seeks through the stream until a pattern is matched. The | ||
Fernando Perez
|
r2906 | pattern is overloaded and may take several types. The pattern can be a | ||
StringType, EOF, a compiled re, or a list of any of those types. | ||||
Strings will be compiled to re types. This returns the index into the | ||||
pattern list. If the pattern was not a list this returns index 0 on a | ||||
successful match. This may raise exceptions for EOF or TIMEOUT. To | ||||
avoid the EOF or TIMEOUT exceptions add EOF or TIMEOUT to the pattern | ||||
list. That will cause expect to match an EOF or TIMEOUT condition | ||||
instead of raising an exception. | ||||
Thomas Kluyver
|
r16384 | If you pass a list of patterns and more than one matches, the first | ||
match in the stream is chosen. If more than one pattern matches at that | ||||
point, the leftmost in the pattern list is chosen. For example:: | ||||
Fernando Perez
|
r2906 | |||
# the input is 'foobar' | ||||
Thomas Kluyver
|
r16384 | index = p.expect(['bar', 'foo', 'foobar']) | ||
# returns 1('foo') even though 'foobar' is a "better" match | ||||
Fernando Perez
|
r2906 | |||
Please note, however, that buffering can affect this behavior, since | ||||
input arrives in unpredictable chunks. For example:: | ||||
# the input is 'foobar' | ||||
Thomas Kluyver
|
r16384 | index = p.expect(['foobar', 'foo']) | ||
# returns 0('foobar') if all input is available at once, | ||||
# but returs 1('foo') if parts of the final 'bar' arrive late | ||||
Fernando Perez
|
r2906 | |||
After a match is found the instance attributes 'before', 'after' and | ||||
'match' will be set. You can see all the data read before the match in | ||||
'before'. You can see the data that was matched in 'after'. The | ||||
re.MatchObject used in the re match will be in 'match'. If an error | ||||
occurred then 'before' will be set to all the data read so far and | ||||
'after' and 'match' will be None. | ||||
If timeout is -1 then timeout will be set to the self.timeout value. | ||||
A list entry may be EOF or TIMEOUT instead of a string. This will | ||||
catch these exceptions and return the index of the list entry instead | ||||
of raising the exception. The attribute 'after' will be set to the | ||||
exception type. The attribute 'match' will be None. This allows you to | ||||
write code like this:: | ||||
Thomas Kluyver
|
r16384 | index = p.expect(['good', 'bad', pexpect.EOF, pexpect.TIMEOUT]) | ||
Fernando Perez
|
r2906 | if index == 0: | ||
do_something() | ||||
elif index == 1: | ||||
do_something_else() | ||||
elif index == 2: | ||||
do_some_other_thing() | ||||
elif index == 3: | ||||
do_something_completely_different() | ||||
instead of code like this:: | ||||
try: | ||||
Thomas Kluyver
|
r16384 | index = p.expect(['good', 'bad']) | ||
Fernando Perez
|
r2906 | if index == 0: | ||
do_something() | ||||
elif index == 1: | ||||
do_something_else() | ||||
except EOF: | ||||
do_some_other_thing() | ||||
except TIMEOUT: | ||||
do_something_completely_different() | ||||
These two forms are equivalent. It all depends on what you want. You | ||||
can also just expect the EOF if you are waiting for all output of a | ||||
child to finish. For example:: | ||||
p = pexpect.spawn('/bin/ls') | ||||
Thomas Kluyver
|
r16384 | p.expect(pexpect.EOF) | ||
Fernando Perez
|
r2906 | print p.before | ||
If you are trying to optimize for speed then see expect_list(). | ||||
Thomas Kluyver
|
r16384 | ''' | ||
Fernando Perez
|
r2906 | |||
compiled_pattern_list = self.compile_pattern_list(pattern) | ||||
Thomas Kluyver
|
r16384 | return self.expect_list(compiled_pattern_list, | ||
timeout, searchwindowsize) | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | def expect_list(self, pattern_list, timeout=-1, searchwindowsize=-1): | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | '''This takes a list of compiled regular expressions and returns the | ||
Fernando Perez
|
r2906 | index into the pattern_list that matched the child output. The list may | ||
Thomas Kluyver
|
r16384 | also contain EOF or TIMEOUT(which are not compiled regular | ||
Fernando Perez
|
r2906 | expressions). This method is similar to the expect() method except that | ||
expect_list() does not recompile the pattern list on every call. This | ||||
may help if you are trying to optimize for speed, otherwise just use | ||||
the expect() method. This is called by expect(). If timeout==-1 then | ||||
the self.timeout value is used. If searchwindowsize==-1 then the | ||||
Thomas Kluyver
|
r16384 | self.searchwindowsize value is used. ''' | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | return self.expect_loop(searcher_re(pattern_list), | ||
timeout, searchwindowsize) | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | def expect_exact(self, pattern_list, timeout=-1, searchwindowsize=-1): | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | '''This is similar to expect(), but uses plain string matching instead | ||
Fernando Perez
|
r2906 | of compiled regular expressions in 'pattern_list'. The 'pattern_list' | ||
may be a string; a list or other sequence of strings; or TIMEOUT and | ||||
EOF. | ||||
This call might be faster than expect() for two reasons: string | ||||
searching is faster than RE matching and it is possible to limit the | ||||
search to just the end of the input buffer. | ||||
This method is also useful when you don't want to have to worry about | ||||
Thomas Kluyver
|
r16384 | escaping regular expression characters that you want to match.''' | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | if (isinstance(pattern_list, self.allowed_string_types) or | ||
pattern_list in (TIMEOUT, EOF)): | ||||
Fernando Perez
|
r2906 | pattern_list = [pattern_list] | ||
Thomas Kluyver
|
r16384 | def prepare_pattern(pattern): | ||
if pattern in (TIMEOUT, EOF): | ||||
return pattern | ||||
if isinstance(pattern, self.allowed_string_types): | ||||
return self._coerce_expect_string(pattern) | ||||
self._pattern_type_err(pattern) | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | try: | ||
pattern_list = iter(pattern_list) | ||||
except TypeError: | ||||
self._pattern_type_err(pattern_list) | ||||
pattern_list = [prepare_pattern(p) for p in pattern_list] | ||||
return self.expect_loop(searcher_string(pattern_list), | ||||
timeout, searchwindowsize) | ||||
def expect_loop(self, searcher, timeout=-1, searchwindowsize=-1): | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | '''This is the common loop used inside expect. The 'searcher' should be | ||
an instance of searcher_re or searcher_string, which describes how and | ||||
what to search for in the input. | ||||
See expect() for other arguments, return value and exceptions. ''' | ||||
Fernando Perez
|
r2906 | |||
self.searcher = searcher | ||||
if timeout == -1: | ||||
timeout = self.timeout | ||||
if timeout is not None: | ||||
end_time = time.time() + timeout | ||||
if searchwindowsize == -1: | ||||
searchwindowsize = self.searchwindowsize | ||||
try: | ||||
incoming = self.buffer | ||||
freshlen = len(incoming) | ||||
Thomas Kluyver
|
r16384 | while True: | ||
# Keep reading until exception or return. | ||||
Fernando Perez
|
r2906 | index = searcher.search(incoming, freshlen, searchwindowsize) | ||
if index >= 0: | ||||
Thomas Kluyver
|
r16384 | self.buffer = incoming[searcher.end:] | ||
self.before = incoming[: searcher.start] | ||||
self.after = incoming[searcher.start: searcher.end] | ||||
Fernando Perez
|
r2906 | self.match = searcher.match | ||
self.match_index = index | ||||
return self.match_index | ||||
# No match at this point | ||||
Thomas Kluyver
|
r16384 | if (timeout is not None) and (timeout < 0): | ||
raise TIMEOUT('Timeout exceeded in expect_any().') | ||||
Fernando Perez
|
r2906 | # Still have time left, so read more data | ||
Thomas Kluyver
|
r16384 | c = self.read_nonblocking(self.maxread, timeout) | ||
Fernando Perez
|
r2906 | freshlen = len(c) | ||
Thomas Kluyver
|
r16384 | time.sleep(0.0001) | ||
Fernando Perez
|
r2906 | incoming = incoming + c | ||
if timeout is not None: | ||||
timeout = end_time - time.time() | ||||
Thomas Kluyver
|
r16384 | except EOF: | ||
err = sys.exc_info()[1] | ||||
self.buffer = self.string_type() | ||||
Fernando Perez
|
r2906 | self.before = incoming | ||
self.after = EOF | ||||
index = searcher.eof_index | ||||
if index >= 0: | ||||
self.match = EOF | ||||
self.match_index = index | ||||
return self.match_index | ||||
else: | ||||
self.match = None | ||||
self.match_index = None | ||||
Thomas Kluyver
|
r16384 | raise EOF(str(err) + '\n' + str(self)) | ||
except TIMEOUT: | ||||
err = sys.exc_info()[1] | ||||
Fernando Perez
|
r2906 | self.buffer = incoming | ||
self.before = incoming | ||||
self.after = TIMEOUT | ||||
index = searcher.timeout_index | ||||
if index >= 0: | ||||
self.match = TIMEOUT | ||||
self.match_index = index | ||||
return self.match_index | ||||
else: | ||||
self.match = None | ||||
self.match_index = None | ||||
Thomas Kluyver
|
r16384 | raise TIMEOUT(str(err) + '\n' + str(self)) | ||
Fernando Perez
|
r2906 | except: | ||
self.before = incoming | ||||
self.after = None | ||||
self.match = None | ||||
self.match_index = None | ||||
raise | ||||
def getwinsize(self): | ||||
Thomas Kluyver
|
r16384 | '''This returns the terminal window size of the child tty. The return | ||
value is a tuple of (rows, cols). ''' | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r13352 | TIOCGWINSZ = getattr(termios, 'TIOCGWINSZ', 1074295912) | ||
Fernando Perez
|
r2906 | s = struct.pack('HHHH', 0, 0, 0, 0) | ||
Thomas Kluyver
|
r16384 | x = fcntl.ioctl(self.child_fd, TIOCGWINSZ, s) | ||
Fernando Perez
|
r2906 | return struct.unpack('HHHH', x)[0:2] | ||
Thomas Kluyver
|
r16384 | def setwinsize(self, rows, cols): | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | '''This sets the terminal window size of the child tty. This will cause | ||
Fernando Perez
|
r2906 | a SIGWINCH signal to be sent to the child. This does not change the | ||
physical window size. It changes the size reported to TTY-aware | ||||
applications like vi or curses -- applications that respond to the | ||||
Thomas Kluyver
|
r16384 | SIGWINCH signal. ''' | ||
# Some very old platforms have a bug that causes the value for | ||||
# termios.TIOCSWINSZ to be truncated. There was a hack here to work | ||||
# around this, but it caused problems with newer platforms so has been | ||||
# removed. For details see https://github.com/pexpect/pexpect/issues/39 | ||||
Fernando Perez
|
r2906 | TIOCSWINSZ = getattr(termios, 'TIOCSWINSZ', -2146929561) | ||
# Note, assume ws_xpixel and ws_ypixel are zero. | ||||
Thomas Kluyver
|
r16384 | s = struct.pack('HHHH', rows, cols, 0, 0) | ||
Fernando Perez
|
r2906 | fcntl.ioctl(self.fileno(), TIOCSWINSZ, s) | ||
Thomas Kluyver
|
r16384 | def interact(self, escape_character=chr(29), | ||
input_filter=None, output_filter=None): | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | '''This gives control of the child process to the interactive user (the | ||
Fernando Perez
|
r2906 | human at the keyboard). Keystrokes are sent to the child process, and | ||
the stdout and stderr output of the child process is printed. This | ||||
simply echos the child stdout and child stderr to the real stdout and | ||||
it echos the real stdin to the child stdin. When the user types the | ||||
escape_character this method will stop. The default for | ||||
escape_character is ^]. This should not be confused with ASCII 27 -- | ||||
the ESC character. ASCII 29 was chosen for historical merit because | ||||
this is the character used by 'telnet' as the escape character. The | ||||
escape_character will not be sent to the child process. | ||||
You may pass in optional input and output filter functions. These | ||||
functions should take a string and return a string. The output_filter | ||||
will be passed all the output from the child process. The input_filter | ||||
will be passed all the keyboard input from the user. The input_filter | ||||
is run BEFORE the check for the escape_character. | ||||
Note that if you change the window size of the parent the SIGWINCH | ||||
signal will not be passed through to the child. If you want the child | ||||
window size to change when the parent's window size changes then do | ||||
something like the following example:: | ||||
import pexpect, struct, fcntl, termios, signal, sys | ||||
def sigwinch_passthrough (sig, data): | ||||
s = struct.pack("HHHH", 0, 0, 0, 0) | ||||
Thomas Kluyver
|
r16384 | a = struct.unpack('hhhh', fcntl.ioctl(sys.stdout.fileno(), | ||
termios.TIOCGWINSZ , s)) | ||||
Fernando Perez
|
r2906 | global p | ||
p.setwinsize(a[0],a[1]) | ||||
Thomas Kluyver
|
r16384 | # Note this 'p' global and used in sigwinch_passthrough. | ||
p = pexpect.spawn('/bin/bash') | ||||
Fernando Perez
|
r2906 | signal.signal(signal.SIGWINCH, sigwinch_passthrough) | ||
p.interact() | ||||
Thomas Kluyver
|
r16384 | ''' | ||
Fernando Perez
|
r2906 | |||
# Flush the buffer. | ||||
Thomas Kluyver
|
r16384 | self.write_to_stdout(self.buffer) | ||
Fernando Perez
|
r2906 | self.stdout.flush() | ||
Thomas Kluyver
|
r16384 | self.buffer = self.string_type() | ||
Fernando Perez
|
r2906 | mode = tty.tcgetattr(self.STDIN_FILENO) | ||
tty.setraw(self.STDIN_FILENO) | ||||
Thomas Kluyver
|
r16384 | if PY3: | ||
escape_character = escape_character.encode('latin-1') | ||||
Fernando Perez
|
r2906 | try: | ||
self.__interact_copy(escape_character, input_filter, output_filter) | ||||
finally: | ||||
tty.tcsetattr(self.STDIN_FILENO, tty.TCSAFLUSH, mode) | ||||
def __interact_writen(self, fd, data): | ||||
Thomas Kluyver
|
r16384 | '''This is used by the interact() method. | ||
''' | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r5430 | while data != b'' and self.isalive(): | ||
Fernando Perez
|
r2906 | n = os.write(fd, data) | ||
data = data[n:] | ||||
def __interact_read(self, fd): | ||||
Thomas Kluyver
|
r16384 | '''This is used by the interact() method. | ||
''' | ||||
Fernando Perez
|
r2906 | |||
return os.read(fd, 1000) | ||||
Thomas Kluyver
|
r16384 | def __interact_copy(self, escape_character=None, | ||
input_filter=None, output_filter=None): | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | '''This is used by the interact() method. | ||
''' | ||||
Fernando Perez
|
r2906 | |||
while self.isalive(): | ||||
Thomas Kluyver
|
r16384 | r, w, e = self.__select([self.child_fd, self.STDIN_FILENO], [], []) | ||
Fernando Perez
|
r2906 | if self.child_fd in r: | ||
Thomas Kluyver
|
r16384 | try: | ||
data = self.__interact_read(self.child_fd) | ||||
Thomas Kluyver
|
r17142 | except OSError as err: | ||
if err.args[0] == errno.EIO: | ||||
# Linux-style EOF | ||||
break | ||||
raise | ||||
if data == b'': | ||||
# BSD-style EOF | ||||
break | ||||
Thomas Kluyver
|
r16384 | if output_filter: | ||
data = output_filter(data) | ||||
Fernando Perez
|
r2906 | if self.logfile is not None: | ||
Thomas Kluyver
|
r16384 | self.logfile.write(data) | ||
Fernando Perez
|
r2906 | self.logfile.flush() | ||
os.write(self.STDOUT_FILENO, data) | ||||
if self.STDIN_FILENO in r: | ||||
data = self.__interact_read(self.STDIN_FILENO) | ||||
Thomas Kluyver
|
r16384 | if input_filter: | ||
data = input_filter(data) | ||||
Fernando Perez
|
r2906 | i = data.rfind(escape_character) | ||
if i != -1: | ||||
data = data[:i] | ||||
self.__interact_writen(self.child_fd, data) | ||||
break | ||||
self.__interact_writen(self.child_fd, data) | ||||
Thomas Kluyver
|
r16384 | def __select(self, iwtd, owtd, ewtd, timeout=None): | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | '''This is a wrapper around select.select() that ignores signals. If | ||
Fernando Perez
|
r2906 | select.select raises a select.error exception and errno is an EINTR | ||
error then it is ignored. Mainly this is used to ignore sigwinch | ||||
Thomas Kluyver
|
r16384 | (terminal resize). ''' | ||
Fernando Perez
|
r2906 | |||
# if select() is interrupted by a signal (errno==EINTR) then | ||||
# we loop back and enter the select() again. | ||||
if timeout is not None: | ||||
end_time = time.time() + timeout | ||||
while True: | ||||
try: | ||||
Thomas Kluyver
|
r16384 | return select.select(iwtd, owtd, ewtd, timeout) | ||
except select.error: | ||||
err = sys.exc_info()[1] | ||||
if err.args[0] == errno.EINTR: | ||||
# if we loop back we have to subtract the | ||||
# amount of time we already waited. | ||||
Fernando Perez
|
r2906 | if timeout is not None: | ||
timeout = end_time - time.time() | ||||
if timeout < 0: | ||||
Thomas Kluyver
|
r16384 | return([], [], []) | ||
else: | ||||
# something else caused the select.error, so | ||||
# this actually is an exception. | ||||
Fernando Perez
|
r2906 | raise | ||
Thomas Kluyver
|
r16384 | ############################################################################## | ||
# The following methods are no longer supported or allowed. | ||||
Thomas Kluyver
|
r17142 | def setmaxread(self, maxread): # pragma: no cover | ||
Thomas Kluyver
|
r16384 | |||
'''This method is no longer supported or allowed. I don't like getters | ||||
and setters without a good reason. ''' | ||||
raise ExceptionPexpect('This method is no longer supported ' + | ||||
'or allowed. Just assign a value to the ' + | ||||
'maxread member variable.') | ||||
Thomas Kluyver
|
r17142 | def setlog(self, fileobject): # pragma: no cover | ||
Thomas Kluyver
|
r16384 | |||
'''This method is no longer supported or allowed. | ||||
''' | ||||
raise ExceptionPexpect('This method is no longer supported ' + | ||||
'or allowed. Just assign a value to the logfile ' + | ||||
'member variable.') | ||||
Fernando Perez
|
r2906 | |||
############################################################################## | ||||
# End of spawn class | ||||
############################################################################## | ||||
Thomas Kluyver
|
r16384 | class spawnu(spawn): | ||
"""Works like spawn, but accepts and returns unicode strings. | ||||
Extra parameters: | ||||
:param encoding: The encoding to use for communications (default: 'utf-8') | ||||
:param errors: How to handle encoding/decoding errors; one of 'strict' | ||||
(the default), 'ignore', or 'replace', as described | ||||
for :meth:`~bytes.decode` and :meth:`~str.encode`. | ||||
""" | ||||
if PY3: | ||||
string_type = str | ||||
allowed_string_types = (str, ) | ||||
_chr = staticmethod(chr) | ||||
linesep = os.linesep | ||||
Thomas Kluyver
|
r17142 | crlf = '\r\n' | ||
Thomas Kluyver
|
r16384 | else: | ||
string_type = unicode | ||||
allowed_string_types = (unicode, ) | ||||
_chr = staticmethod(unichr) | ||||
linesep = os.linesep.decode('ascii') | ||||
Thomas Kluyver
|
r17142 | crlf = '\r\n'.decode('ascii') | ||
Thomas Kluyver
|
r16384 | # This can handle unicode in both Python 2 and 3 | ||
write_to_stdout = sys.stdout.write | ||||
def __init__(self, *args, **kwargs): | ||||
self.encoding = kwargs.pop('encoding', 'utf-8') | ||||
self.errors = kwargs.pop('errors', 'strict') | ||||
self._decoder = codecs.getincrementaldecoder(self.encoding)(errors=self.errors) | ||||
super(spawnu, self).__init__(*args, **kwargs) | ||||
@staticmethod | ||||
def _coerce_expect_string(s): | ||||
return s | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | @staticmethod | ||
def _coerce_send_string(s): | ||||
return s | ||||
def _coerce_read_string(self, s): | ||||
return self._decoder.decode(s, final=False) | ||||
def _send(self, s): | ||||
return os.write(self.child_fd, s.encode(self.encoding, self.errors)) | ||||
class searcher_string(object): | ||||
'''This is a plain string search helper for the spawn.expect_any() method. | ||||
Thomas Kluyver
|
r5430 | This helper class is for speed. For more powerful regex patterns | ||
see the helper class, searcher_re. | ||||
Fernando Perez
|
r2906 | |||
Attributes: | ||||
eof_index - index of EOF, or -1 | ||||
timeout_index - index of TIMEOUT, or -1 | ||||
After a successful match by the search() method the following attributes | ||||
are available: | ||||
start - index into the buffer, first byte of match | ||||
end - index into the buffer, first byte after match | ||||
match - the matching string itself | ||||
Thomas Kluyver
|
r5430 | |||
Thomas Kluyver
|
r16384 | ''' | ||
Fernando Perez
|
r2906 | |||
def __init__(self, strings): | ||||
Thomas Kluyver
|
r16384 | '''This creates an instance of searcher_string. This argument 'strings' | ||
may be a list; a sequence of strings; or the EOF or TIMEOUT types. ''' | ||||
Fernando Perez
|
r2906 | |||
self.eof_index = -1 | ||||
self.timeout_index = -1 | ||||
self._strings = [] | ||||
Thomas Kluyver
|
r5430 | for n, s in enumerate(strings): | ||
Fernando Perez
|
r2906 | if s is EOF: | ||
self.eof_index = n | ||||
continue | ||||
if s is TIMEOUT: | ||||
self.timeout_index = n | ||||
continue | ||||
self._strings.append((n, s)) | ||||
def __str__(self): | ||||
Thomas Kluyver
|
r16384 | '''This returns a human-readable string that represents the state of | ||
the object.''' | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | ss = [(ns[0], ' %d: "%s"' % ns) for ns in self._strings] | ||
ss.append((-1, 'searcher_string:')) | ||||
Fernando Perez
|
r2906 | if self.eof_index >= 0: | ||
Thomas Kluyver
|
r16384 | ss.append((self.eof_index, ' %d: EOF' % self.eof_index)) | ||
Fernando Perez
|
r2906 | if self.timeout_index >= 0: | ||
Thomas Kluyver
|
r16384 | ss.append((self.timeout_index, | ||
' %d: TIMEOUT' % self.timeout_index)) | ||||
Fernando Perez
|
r2906 | ss.sort() | ||
Thomas Kluyver
|
r16384 | ss = list(zip(*ss))[1] | ||
return '\n'.join(ss) | ||||
Fernando Perez
|
r2906 | |||
def search(self, buffer, freshlen, searchwindowsize=None): | ||||
Thomas Kluyver
|
r16384 | '''This searches 'buffer' for the first occurence of one of the search | ||
Fernando Perez
|
r2906 | strings. 'freshlen' must indicate the number of bytes at the end of | ||
'buffer' which have not been searched before. It helps to avoid | ||||
searching the same, possibly big, buffer over and over again. | ||||
See class spawn for the 'searchwindowsize' argument. | ||||
If there is a match this returns the index of that string, and sets | ||||
Thomas Kluyver
|
r16384 | 'start', 'end' and 'match'. Otherwise, this returns -1. ''' | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | first_match = None | ||
Fernando Perez
|
r2906 | |||
# 'freshlen' helps a lot here. Further optimizations could | ||||
# possibly include: | ||||
# | ||||
# using something like the Boyer-Moore Fast String Searching | ||||
# Algorithm; pre-compiling the search through a list of | ||||
# strings into something that can scan the input once to | ||||
# search for all N strings; realize that if we search for | ||||
# ['bar', 'baz'] and the input is '...foo' we need not bother | ||||
# rescanning until we've read three more bytes. | ||||
# | ||||
# Sadly, I don't know enough about this interesting topic. /grahn | ||||
for index, s in self._strings: | ||||
if searchwindowsize is None: | ||||
# the match, if any, can only be in the fresh data, | ||||
# or at the very end of the old data | ||||
Thomas Kluyver
|
r16384 | offset = -(freshlen + len(s)) | ||
Fernando Perez
|
r2906 | else: | ||
# better obey searchwindowsize | ||||
offset = -searchwindowsize | ||||
n = buffer.find(s, offset) | ||||
Thomas Kluyver
|
r16384 | if n >= 0 and (first_match is None or n < first_match): | ||
Fernando Perez
|
r2906 | first_match = n | ||
best_index, best_match = index, s | ||||
Thomas Kluyver
|
r16384 | if first_match is None: | ||
Fernando Perez
|
r2906 | return -1 | ||
self.match = best_match | ||||
self.start = first_match | ||||
self.end = self.start + len(self.match) | ||||
return best_index | ||||
Thomas Kluyver
|
r16384 | class searcher_re(object): | ||
'''This is regular expression string search helper for the | ||||
Thomas Kluyver
|
r5430 | spawn.expect_any() method. This helper class is for powerful | ||
pattern matching. For speed, see the helper class, searcher_string. | ||||
Fernando Perez
|
r2906 | |||
Attributes: | ||||
eof_index - index of EOF, or -1 | ||||
timeout_index - index of TIMEOUT, or -1 | ||||
After a successful match by the search() method the following attributes | ||||
are available: | ||||
start - index into the buffer, first byte of match | ||||
end - index into the buffer, first byte after match | ||||
match - the re.match object returned by a succesful re.search | ||||
Thomas Kluyver
|
r16384 | ''' | ||
Fernando Perez
|
r2906 | |||
def __init__(self, patterns): | ||||
Thomas Kluyver
|
r16384 | '''This creates an instance that searches for 'patterns' Where | ||
Fernando Perez
|
r2906 | 'patterns' may be a list or other sequence of compiled regular | ||
Thomas Kluyver
|
r16384 | expressions, or the EOF or TIMEOUT types.''' | ||
Fernando Perez
|
r2906 | |||
self.eof_index = -1 | ||||
self.timeout_index = -1 | ||||
self._searches = [] | ||||
Thomas Kluyver
|
r16384 | for n, s in zip(list(range(len(patterns))), patterns): | ||
Fernando Perez
|
r2906 | if s is EOF: | ||
self.eof_index = n | ||||
continue | ||||
if s is TIMEOUT: | ||||
self.timeout_index = n | ||||
continue | ||||
self._searches.append((n, s)) | ||||
def __str__(self): | ||||
Thomas Kluyver
|
r16384 | '''This returns a human-readable string that represents the state of | ||
the object.''' | ||||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | #ss = [(n, ' %d: re.compile("%s")' % | ||
# (n, repr(s.pattern))) for n, s in self._searches] | ||||
ss = list() | ||||
for n, s in self._searches: | ||||
try: | ||||
ss.append((n, ' %d: re.compile("%s")' % (n, s.pattern))) | ||||
except UnicodeEncodeError: | ||||
# for test cases that display __str__ of searches, dont throw | ||||
# another exception just because stdout is ascii-only, using | ||||
# repr() | ||||
ss.append((n, ' %d: re.compile(%r)' % (n, s.pattern))) | ||||
ss.append((-1, 'searcher_re:')) | ||||
Fernando Perez
|
r2906 | if self.eof_index >= 0: | ||
Thomas Kluyver
|
r16384 | ss.append((self.eof_index, ' %d: EOF' % self.eof_index)) | ||
Fernando Perez
|
r2906 | if self.timeout_index >= 0: | ||
Thomas Kluyver
|
r16384 | ss.append((self.timeout_index, ' %d: TIMEOUT' % | ||
self.timeout_index)) | ||||
Fernando Perez
|
r2906 | ss.sort() | ||
Thomas Kluyver
|
r16384 | ss = list(zip(*ss))[1] | ||
return '\n'.join(ss) | ||||
Fernando Perez
|
r2906 | |||
def search(self, buffer, freshlen, searchwindowsize=None): | ||||
Thomas Kluyver
|
r16384 | '''This searches 'buffer' for the first occurence of one of the regular | ||
Fernando Perez
|
r2906 | expressions. 'freshlen' must indicate the number of bytes at the end of | ||
'buffer' which have not been searched before. | ||||
See class spawn for the 'searchwindowsize' argument. | ||||
If there is a match this returns the index of that string, and sets | ||||
Thomas Kluyver
|
r16384 | 'start', 'end' and 'match'. Otherwise, returns -1.''' | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | first_match = None | ||
Fernando Perez
|
r2906 | # 'freshlen' doesn't help here -- we cannot predict the | ||
# length of a match, and the re module provides no help. | ||||
if searchwindowsize is None: | ||||
searchstart = 0 | ||||
else: | ||||
Thomas Kluyver
|
r16384 | searchstart = max(0, len(buffer) - searchwindowsize) | ||
Fernando Perez
|
r2906 | for index, s in self._searches: | ||
match = s.search(buffer, searchstart) | ||||
if match is None: | ||||
continue | ||||
n = match.start() | ||||
Thomas Kluyver
|
r16384 | if first_match is None or n < first_match: | ||
Fernando Perez
|
r2906 | first_match = n | ||
the_match = match | ||||
best_index = index | ||||
Thomas Kluyver
|
r16384 | if first_match is None: | ||
Fernando Perez
|
r2906 | return -1 | ||
self.start = first_match | ||||
self.match = the_match | ||||
self.end = self.match.end() | ||||
return best_index | ||||
Thomas Kluyver
|
r17142 | def is_executable_file(path): | ||
"""Checks that path is an executable regular file (or a symlink to a file). | ||||
This is roughly ``os.path isfile(path) and os.access(path, os.X_OK)``, but | ||||
on some platforms :func:`os.access` gives us the wrong answer, so this | ||||
checks permission bits directly. | ||||
""" | ||||
# follow symlinks, | ||||
fpath = os.path.realpath(path) | ||||
Thomas Kluyver
|
r16384 | |||
Thomas Kluyver
|
r17142 | # return False for non-files (directories, fifo, etc.) | ||
if not os.path.isfile(fpath): | ||||
return False | ||||
# On Solaris, etc., "If the process has appropriate privileges, an | ||||
# implementation may indicate success for X_OK even if none of the | ||||
# execute file permission bits are set." | ||||
# | ||||
# For this reason, it is necessary to explicitly check st_mode | ||||
# get file mode using os.stat, and check if `other', | ||||
# that is anybody, may read and execute. | ||||
mode = os.stat(fpath).st_mode | ||||
if mode & stat.S_IROTH and mode & stat.S_IXOTH: | ||||
return True | ||||
# get current user's group ids, and check if `group', | ||||
# when matching ours, may read and execute. | ||||
user_gids = os.getgroups() + [os.getgid()] | ||||
if (os.stat(fpath).st_gid in user_gids and | ||||
mode & stat.S_IRGRP and mode & stat.S_IXGRP): | ||||
return True | ||||
# finally, if file owner matches our effective userid, | ||||
# check if `user', may read and execute. | ||||
user_gids = os.getgroups() + [os.getgid()] | ||||
if (os.stat(fpath).st_uid == os.geteuid() and | ||||
mode & stat.S_IRUSR and mode & stat.S_IXUSR): | ||||
return True | ||||
return False | ||||
def which(filename): | ||||
Thomas Kluyver
|
r16384 | '''This takes a given filename; tries to find it in the environment path; | ||
Fernando Perez
|
r2906 | then checks if it is executable. This returns the full path to the filename | ||
Thomas Kluyver
|
r16384 | if found and executable. Otherwise this returns None.''' | ||
Fernando Perez
|
r2906 | |||
Thomas Kluyver
|
r16384 | # Special case where filename contains an explicit path. | ||
Thomas Kluyver
|
r17142 | if os.path.dirname(filename) != '' and is_executable_file(filename): | ||
return filename | ||||
Bradley M. Froehle
|
r7859 | if 'PATH' not in os.environ or os.environ['PATH'] == '': | ||
Fernando Perez
|
r2906 | p = os.defpath | ||
else: | ||||
p = os.environ['PATH'] | ||||
Thomas Kluyver
|
r3161 | pathlist = p.split(os.pathsep) | ||
Fernando Perez
|
r2906 | for path in pathlist: | ||
Thomas Kluyver
|
r16384 | ff = os.path.join(path, filename) | ||
Thomas Kluyver
|
r17142 | if is_executable_file(ff): | ||
Thomas Kluyver
|
r16384 | return ff | ||
Fernando Perez
|
r2906 | return None | ||
Thomas Kluyver
|
r16384 | |||
Fernando Perez
|
r2906 | def split_command_line(command_line): | ||
Thomas Kluyver
|
r16384 | '''This splits a command line into a list of arguments. It splits arguments | ||
Fernando Perez
|
r2906 | on spaces, but handles embedded quotes, doublequotes, and escaped | ||
characters. It's impossible to do this with a regular expression, so I | ||||
Thomas Kluyver
|
r16384 | wrote a little state machine to parse the command line. ''' | ||
Fernando Perez
|
r2906 | |||
arg_list = [] | ||||
arg = '' | ||||
# Constants to name the states we can be in. | ||||
state_basic = 0 | ||||
state_esc = 1 | ||||
state_singlequote = 2 | ||||
state_doublequote = 3 | ||||
Thomas Kluyver
|
r16384 | # The state when consuming whitespace between commands. | ||
state_whitespace = 4 | ||||
Fernando Perez
|
r2906 | state = state_basic | ||
for c in command_line: | ||||
if state == state_basic or state == state_whitespace: | ||||
Thomas Kluyver
|
r16384 | if c == '\\': | ||
# Escape the next character | ||||
Fernando Perez
|
r2906 | state = state_esc | ||
Thomas Kluyver
|
r16384 | elif c == r"'": | ||
# Handle single quote | ||||
Fernando Perez
|
r2906 | state = state_singlequote | ||
Thomas Kluyver
|
r16384 | elif c == r'"': | ||
# Handle double quote | ||||
Fernando Perez
|
r2906 | state = state_doublequote | ||
elif c.isspace(): | ||||
# Add arg to arg_list if we aren't in the middle of whitespace. | ||||
if state == state_whitespace: | ||||
Thomas Kluyver
|
r16384 | # Do nothing. | ||
None | ||||
Fernando Perez
|
r2906 | else: | ||
arg_list.append(arg) | ||||
arg = '' | ||||
state = state_whitespace | ||||
else: | ||||
arg = arg + c | ||||
state = state_basic | ||||
elif state == state_esc: | ||||
arg = arg + c | ||||
state = state_basic | ||||
elif state == state_singlequote: | ||||
if c == r"'": | ||||
state = state_basic | ||||
else: | ||||
arg = arg + c | ||||
elif state == state_doublequote: | ||||
if c == r'"': | ||||
state = state_basic | ||||
else: | ||||
arg = arg + c | ||||
if arg != '': | ||||
arg_list.append(arg) | ||||
return arg_list | ||||
Thomas Kluyver
|
r17142 | # vim: set shiftround expandtab tabstop=4 shiftwidth=4 ft=python autoindent : | ||