summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Walther <tw@spotify.com>2017-12-20 11:12:34 +0000
committerThomas Walther <tw@spotify.com>2017-12-20 11:12:34 +0000
commit543d320f615b2589d19e92ccf4c57719c27d0067 (patch)
treea230085363547a24c5e0903d71db6aaad8b6bb98
parent96396e17a20f699fa62ff7fe43f75f266eabb705 (diff)
parent8a1195a4837bd4f7477dff6f7c6a22c234e192e7 (diff)
downloadpexpect-543d320f615b2589d19e92ccf4c57719c27d0067.tar.gz
Merge remote-tracking branch 'upstream/master'
-rw-r--r--.travis.yml7
-rw-r--r--README.rst2
-rw-r--r--doc/conf.py4
-rw-r--r--doc/history.rst35
-rw-r--r--doc/overview.rst14
-rwxr-xr-xexamples/astat.py2
-rwxr-xr-xexamples/cgishell.cgi2
-rwxr-xr-xexamples/chess.py6
-rwxr-xr-xexamples/chess3.py6
-rwxr-xr-xexamples/df.py2
-rwxr-xr-xexamples/hive.py12
-rwxr-xr-xexamples/monitor.py14
-rwxr-xr-xexamples/topip.py6
-rwxr-xr-xexamples/uptime.py6
-rw-r--r--notes/notes.txt2
-rw-r--r--pexpect/__init__.py2
-rw-r--r--pexpect/_async.py (renamed from pexpect/async.py)0
-rw-r--r--pexpect/bashrc.sh15
-rw-r--r--pexpect/expect.py9
-rw-r--r--pexpect/fdpexpect.py2
-rw-r--r--pexpect/popen_spawn.py5
-rw-r--r--pexpect/pty_spawn.py5
-rw-r--r--pexpect/pxssh.py14
-rw-r--r--pexpect/replwrap.py6
-rw-r--r--pexpect/screen.py2
-rw-r--r--pexpect/spawnbase.py40
-rw-r--r--pexpect/utils.py5
-rw-r--r--tests/test_async.py30
-rw-r--r--tests/test_unicode.py2
29 files changed, 168 insertions, 89 deletions
diff --git a/.travis.yml b/.travis.yml
index 251dfaa..40d9622 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,7 +5,14 @@ python:
- 3.3
- 3.4
- 3.5
+ - 3.6
- pypy
+ - nightly
+
+matrix:
+ allow_failures:
+ # PyPy on Travis is currently incompatible with Cryptography.
+ - python: pypy
install:
- export PYTHONIOENCODING=UTF8
diff --git a/README.rst b/README.rst
index 6c56a17..0f5cb98 100644
--- a/README.rst
+++ b/README.rst
@@ -1,4 +1,4 @@
-.. image:: https://travis-ci.org/pexpect/pexpect.png?branch=master
+.. image:: https://travis-ci.org/pexpect/pexpect.svg?branch=master
:target: https://travis-ci.org/pexpect/pexpect
:align: right
:alt: Build status
diff --git a/doc/conf.py b/doc/conf.py
index cda9f5d..6fc815f 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -52,9 +52,9 @@ copyright = u'2013, Noah Spurrier and contributors'
# built documents.
#
# The short X.Y version.
-version = '4.2'
+version = '4.3.1'
# The full version, including alpha/beta/rc tags.
-release = '4.2.1'
+release = version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/doc/history.rst b/doc/history.rst
index bf38894..dd0a2b9 100644
--- a/doc/history.rst
+++ b/doc/history.rst
@@ -4,6 +4,39 @@ History
Releases
--------
+Version 4.3.1
+`````````````
+
+* When launching bash for :mod:`pexpect.replwrap`, load the system ``bashrc``
+ from a couple of different common locations (:ghpull:`457`), and then unset
+ the ``PROMPT_COMMAND`` environment variable, which can interfere with the
+ prompt we're expecting (:ghpull:`459`).
+
+Version 4.3
+```````````
+
+* The ``async=`` parameter to integrate with asyncio has become ``async_=``
+ (:ghpull:`431`), as *async* is becoming a Python keyword from Python 3.6.
+ Pexpect will still recognise ``async`` as an alternative spelling.
+* Similarly, the module ``pexpect.async`` became ``pexpect._async``
+ (:ghpull:`450`). This module is not part of the public API.
+* Fix problems with asyncio objects closing file descriptors during garbage
+ collection (:ghissue:`347`, :ghpull:`376`).
+* Set the ``.pid`` attribute of a :class:`~.PopenSpawn` object (:ghpull:`417`).
+* Fix passing Windows paths to :class:`~.PopenSpawn` (:ghpull:`446`).
+* :class:`~.PopenSpawn` on Windows can pass string commands through to ``Popen``
+ without splitting them into a list (:ghpull:`447`).
+* Stop ``shlex`` trying to read from stdin when :class:`~.PopenSpawn` is
+ passed ``cmd=None`` (:ghissue:`433`, :ghpull:`434`).
+* Ensure that an error closing a Pexpect spawn object raises a Pexpect error,
+ rather than a Ptyprocess error (:ghissue:`383`, :ghpull:`386`).
+* Cleaned up invalid backslash escape sequences in strings (:ghpull:`430`,
+ :ghpull:`445`).
+* The pattern for a password prompt in :mod:`pexpect.pxssh` changed from
+ ``password`` to ``password:`` (:ghpull:`452`).
+* Correct docstring for using unicode with spawn (:ghpull:`395`).
+* Various other improvements to documentation.
+
Version 4.2.1
`````````````
@@ -200,7 +233,7 @@ Version 2.3
* Changed the name of ``send_eof()`` to ``sendeof()``.
* Modified ``kill()`` so that it checks to make sure the pid ``isalive()``.
* modified ``spawn()`` (really called from ``__spawn()``) so that it does not
- raise an expection if ``setwinsize()`` fails. Some platforms such as Cygwin
+ raise an exception if ``setwinsize()`` fails. Some platforms such as Cygwin
do not like setwinsize. This was a constant problem and since it is not a
critical feature I decided to just silence the error. Normally I don't like
to do that, but in this case I'm making an exception.
diff --git a/doc/overview.rst b/doc/overview.rst
index d394ef1..eca8565 100644
--- a/doc/overview.rst
+++ b/doc/overview.rst
@@ -249,9 +249,19 @@ Pexpect on Windows
Pexpect can be used on Windows to wait for a pattern to be produced by a child
process, using :class:`pexpect.popen_spawn.PopenSpawn`, or a file descriptor,
-using :class:`pexpect.fdpexpect.fdspawn`. This should be considered experimental
-for now.
+using :class:`pexpect.fdpexpect.fdspawn`.
:class:`pexpect.spawn` and :func:`pexpect.run` are *not* available on Windows,
as they rely on Unix pseudoterminals (ptys). Cross platform code must not use
these.
+
+``PopenSpawn`` is not a direct replacement for ``spawn``. Many programs only
+offer interactive behaviour if they detect that they are running in a terminal.
+When run by ``PopenSpawn``, they may behave differently.
+
+.. seealso::
+
+ `winpexpect <https://pypi.python.org/pypi/winpexpect>`__ and
+ `wexpect <https://gist.github.com/anthonyeden/8488763>`__
+ Two unmaintained pexpect-like modules for Windows, which work with a
+ hidden console.
diff --git a/examples/astat.py b/examples/astat.py
index a083fe1..abba1be 100755
--- a/examples/astat.py
+++ b/examples/astat.py
@@ -90,7 +90,7 @@ def main():
p = pxssh.pxssh()
p.login(hostname, username, password)
p.sendline('apachectl status')
- p.expect('([0-9]+\.[0-9]+)\s*requests/sec')
+ p.expect(r'([0-9]+\.[0-9]+)\s*requests/sec')
requests_per_second = p.match.groups()[0]
p.logout()
print(requests_per_second)
diff --git a/examples/cgishell.cgi b/examples/cgishell.cgi
index 23bef5f..57d8667 100755
--- a/examples/cgishell.cgi
+++ b/examples/cgishell.cgi
@@ -12,7 +12,7 @@ The client web browser needs nothing but CSS and Javascript.
--port : set the local port for the server to listen on
--watch : show the virtual screen after each client request
-This project is probably not the most security concious thing I've ever built.
+This project is probably not the most security conscious thing I've ever built.
This should be considered an experimental tool -- at best.
"""
diff --git a/examples/chess.py b/examples/chess.py
index 421727d..f97a3a9 100755
--- a/examples/chess.py
+++ b/examples/chess.py
@@ -29,8 +29,8 @@ from __future__ import absolute_import
import pexpect
import ANSI
-REGEX_MOVE = '(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)'
-REGEX_MOVE_PART = '(?:[0-9]|\x1b\[C)(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)'
+REGEX_MOVE = r'(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)'
+REGEX_MOVE_PART = r'(?:[0-9]|\x1b\[C)(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)'
class Chess:
@@ -71,7 +71,7 @@ class Chess:
def get_computer_move (self):
print('Here')
- i = self.child.expect (['\[17;59H', '\[17;58H'])
+ i = self.child.expect ([r'\[17;59H', r'\[17;58H'])
print(i)
if i == 0:
self.child.expect (REGEX_MOVE)
diff --git a/examples/chess3.py b/examples/chess3.py
index dc02663..2c087b0 100755
--- a/examples/chess3.py
+++ b/examples/chess3.py
@@ -29,8 +29,8 @@ from __future__ import absolute_import
import pexpect
import ANSI
-REGEX_MOVE = '(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)'
-REGEX_MOVE_PART = '(?:[0-9]|\x1b\[C)(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)'
+REGEX_MOVE = r'(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)'
+REGEX_MOVE_PART = r'(?:[0-9]|\x1b\[C)(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)'
class Chess:
@@ -74,7 +74,7 @@ class Chess:
def get_computer_move (self):
print('Here')
- i = self.child.expect (['\[17;59H', '\[17;58H'])
+ i = self.child.expect ([r'\[17;59H', r'\[17;58H'])
print(i)
if i == 0:
self.child.expect (REGEX_MOVE)
diff --git a/examples/df.py b/examples/df.py
index 4faa038..f8e3fc3 100755
--- a/examples/df.py
+++ b/examples/df.py
@@ -35,7 +35,7 @@ import pexpect
child = pexpect.spawn ('df')
# parse 'df' output into a list.
-pattern = "\n(\S+).*?([0-9]+)%"
+pattern = r"\n(\S+).*?([0-9]+)%"
filesystem_list = []
for dummy in range (0, 1000):
i = child.expect ([pattern, pexpect.EOF])
diff --git a/examples/hive.py b/examples/hive.py
index 00ddbea..1b7bcbf 100755
--- a/examples/hive.py
+++ b/examples/hive.py
@@ -15,7 +15,7 @@ Example:
password:
connecting to host1.example.com - OK
connecting to host2.example.net - OK
- targetting hosts: 192.168.1.104 192.168.1.107
+ targeting hosts: 192.168.1.104 192.168.1.107
CMD (? for help) > uptime
=======================================================================
host1.example.com
@@ -160,7 +160,7 @@ CMD_HELP='''Hive commands are preceded by a colon : (just think of vi).
:resync
This is similar to :sync, but it does not change the mode. It looks for the
- prompt and thus consumes all input from all targetted hosts.
+ prompt and thus consumes all input from all targeted hosts.
:prompt
@@ -170,12 +170,12 @@ CMD_HELP='''Hive commands are preceded by a colon : (just think of vi).
:send my text
- This will send the 'my text' wihtout a line feed to the targetted hosts.
+ This will send the 'my text' wihtout a line feed to the targeted hosts.
This output of the hosts is not automatically synchronized.
:control X
- This will send the given control character to the targetted hosts.
+ This will send the given control character to the targeted hosts.
For example, ":control c" will send ASCII 3.
:exit
@@ -255,7 +255,7 @@ def main ():
synchronous_mode = True
target_hostnames = host_names[:]
- print('targetting hosts:', ' '.join(target_hostnames))
+ print('targeting hosts:', ' '.join(target_hostnames))
while True:
cmd = raw_input('CMD (? for help) > ')
cmd = cmd.strip()
@@ -348,7 +348,7 @@ def main ():
target_hostnames = cmd.split()[1:]
if len(target_hostnames) == 0 or target_hostnames[0] == all:
target_hostnames = host_names[:]
- print('targetting hosts:', ' '.join(target_hostnames))
+ print('targeting hosts:', ' '.join(target_hostnames))
continue
elif cmd == ':exit' or cmd == ':q' or cmd == ':quit':
break
diff --git a/examples/monitor.py b/examples/monitor.py
index 9cb0eaf..c030d3a 100755
--- a/examples/monitor.py
+++ b/examples/monitor.py
@@ -59,7 +59,7 @@ except NameError:
# Some constants.
#
COMMAND_PROMPT = '[#$] ' ### This is way too simple for industrial use -- we will change is ASAP.
-TERMINAL_PROMPT = '(?i)terminal type\?'
+TERMINAL_PROMPT = r'(?i)terminal type\?'
TERMINAL_TYPE = 'vt100'
# This is the prompt we get if SSH does not have the remote host's public key stored in the cache.
SSH_NEWKEY = '(?i)are you sure you want to continue connecting'
@@ -130,12 +130,12 @@ def main():
#
# Set command prompt to something more unique.
#
- COMMAND_PROMPT = "\[PEXPECT\]\$ "
- child.sendline ("PS1='[PEXPECT]\$ '") # In case of sh-style
+ COMMAND_PROMPT = r"\[PEXPECT\]\$ "
+ child.sendline (r"PS1='[PEXPECT]\$ '") # In case of sh-style
i = child.expect ([pexpect.TIMEOUT, COMMAND_PROMPT], timeout=10)
if i == 0:
print("# Couldn't set sh-style prompt -- trying csh-style.")
- child.sendline ("set prompt='[PEXPECT]\$ '")
+ child.sendline (r"set prompt='[PEXPECT]\$ '")
i = child.expect ([pexpect.TIMEOUT, COMMAND_PROMPT], timeout=10)
if i == 0:
print("Failed to set command prompt using sh or csh style.")
@@ -159,20 +159,20 @@ def main():
# Run and parse 'uptime'.
child.sendline ('uptime')
- child.expect('up\s+(.*?),\s+([0-9]+) users?,\s+load averages?: ([0-9]+\.[0-9][0-9]),?\s+([0-9]+\.[0-9][0-9]),?\s+([0-9]+\.[0-9][0-9])')
+ child.expect(r'up\s+(.*?),\s+([0-9]+) users?,\s+load averages?: ([0-9]+\.[0-9][0-9]),?\s+([0-9]+\.[0-9][0-9]),?\s+([0-9]+\.[0-9][0-9])')
duration, users, av1, av5, av15 = child.match.groups()
days = '0'
hours = '0'
mins = '0'
if 'day' in duration:
- child.match = re.search('([0-9]+)\s+day',duration)
+ child.match = re.search(r'([0-9]+)\s+day',duration)
days = str(int(child.match.group(1)))
if ':' in duration:
child.match = re.search('([0-9]+):([0-9]+)',duration)
hours = str(int(child.match.group(1)))
mins = str(int(child.match.group(2)))
if 'min' in duration:
- child.match = re.search('([0-9]+)\s+min',duration)
+ child.match = re.search(r'([0-9]+)\s+min',duration)
mins = str(int(child.match.group(1)))
print()
print('Uptime: %s days, %s users, %s (1 min), %s (5 min), %s (15 min)' % (
diff --git a/examples/topip.py b/examples/topip.py
index 25bd100..64dac30 100755
--- a/examples/topip.py
+++ b/examples/topip.py
@@ -206,10 +206,10 @@ def main():
stddev_trigger = 5
if ipv6_flag:
- netstat_pattern = '(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+::ffff:(\S+):(\S+)\s+.*?\r'
+ netstat_pattern = r'(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+::ffff:(\S+):(\S+)\s+.*?\r'
else:
- netstat_pattern = '(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(?:::ffff:)*(\S+):(\S+)\s+.*?\r'
- #netstat_pattern = '(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+):(\S+)\s+.*?\r'
+ netstat_pattern = r'(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(?:::ffff:)*(\S+):(\S+)\s+.*?\r'
+ #netstat_pattern = r'(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+):(\S+)\s+.*?\r'
# run netstat (either locally or via SSH).
if use_localhost:
diff --git a/examples/uptime.py b/examples/uptime.py
index 659edfc..86b8dff 100755
--- a/examples/uptime.py
+++ b/examples/uptime.py
@@ -54,7 +54,7 @@ import re
p = pexpect.spawnu('uptime')
# This parses uptime output into the major groups using regex group matching.
-p.expect('up\s+(.*?),\s+([0-9]+) users?,\s+load averages?: ([0-9]+\.[0-9][0-9]),?\s+([0-9]+\.[0-9][0-9]),?\s+([0-9]+\.[0-9][0-9])')
+p.expect(r'up\s+(.*?),\s+([0-9]+) users?,\s+load averages?: ([0-9]+\.[0-9][0-9]),?\s+([0-9]+\.[0-9][0-9]),?\s+([0-9]+\.[0-9][0-9])')
duration, users, av1, av5, av15 = p.match.groups()
# The duration is a little harder to parse because of all the different
@@ -65,14 +65,14 @@ days = '0'
hours = '0'
mins = '0'
if 'day' in duration:
- p.match = re.search('([0-9]+)\s+day',duration)
+ p.match = re.search(r'([0-9]+)\s+day',duration)
days = str(int(p.match.group(1)))
if ':' in duration:
p.match = re.search('([0-9]+):([0-9]+)',duration)
hours = str(int(p.match.group(1)))
mins = str(int(p.match.group(2)))
if 'min' in duration:
- p.match = re.search('([0-9]+)\s+min',duration)
+ p.match = re.search(r'([0-9]+)\s+min',duration)
mins = str(int(p.match.group(1)))
# Print the parsed fields in CSV format.
diff --git a/notes/notes.txt b/notes/notes.txt
index a793587..8ff6cfe 100644
--- a/notes/notes.txt
+++ b/notes/notes.txt
@@ -19,7 +19,7 @@
# http://www.erlenstar.demon.co.uk/unix/faq_4.html#SEC52
# Nonblocking on Win32?
-# Reasearch this as a way to maybe make pipe work for Win32.
+# Research this as a way to maybe make pipe work for Win32.
# http://groups.google.com/groups?q=setraw+tty&hl=en&selm=uvgpvisvk.fsf%40roundpoint.com&rnum=7
#
# if istty:
diff --git a/pexpect/__init__.py b/pexpect/__init__.py
index 6c509e6..40bcc97 100644
--- a/pexpect/__init__.py
+++ b/pexpect/__init__.py
@@ -75,7 +75,7 @@ if sys.platform != 'win32':
from .pty_spawn import spawn, spawnu
from .run import run, runu
-__version__ = '4.2.1'
+__version__ = '4.3.1'
__revision__ = ''
__all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu',
'which', 'split_command_line', '__version__', '__revision__']
diff --git a/pexpect/async.py b/pexpect/_async.py
index 3a1a1ad..3a1a1ad 100644
--- a/pexpect/async.py
+++ b/pexpect/_async.py
diff --git a/pexpect/bashrc.sh b/pexpect/bashrc.sh
index 99a3ac2..c734ac9 100644
--- a/pexpect/bashrc.sh
+++ b/pexpect/bashrc.sh
@@ -1,5 +1,16 @@
-source /etc/bash.bashrc
-source ~/.bashrc
+# Different platforms have different names for the systemwide bashrc
+if [[ -f /etc/bashrc ]]; then
+ source /etc/bashrc
+fi
+if [[ -f /etc/bash.bashrc ]]; then
+ source /etc/bash.bashrc
+fi
+if [[ -f ~/.bashrc ]]; then
+ source ~/.bashrc
+fi
# Reset PS1 so pexpect can find it
PS1="$"
+
+# Unset PROMPT_COMMAND, so that it can't change PS1 to something unexpected.
+unset PROMPT_COMMAND
diff --git a/pexpect/expect.py b/pexpect/expect.py
index 660cfb5..3080d6c 100644
--- a/pexpect/expect.py
+++ b/pexpect/expect.py
@@ -30,7 +30,6 @@ class Expecter(object):
def eof(self, err=None):
spawn = self.spawn
- from . import EOF
spawn.before = spawn.buffer
spawn.buffer = spawn.string_type()
@@ -51,7 +50,6 @@ class Expecter(object):
def timeout(self, err=None):
spawn = self.spawn
- from . import TIMEOUT
spawn.before = spawn.buffer
spawn.after = TIMEOUT
@@ -79,7 +77,6 @@ class Expecter(object):
def expect_loop(self, timeout=-1):
"""Blocking expect"""
spawn = self.spawn
- from . import EOF, TIMEOUT
if timeout is not None:
end_time = time.time() + timeout
@@ -161,7 +158,7 @@ class searcher_string(object):
return '\n'.join(ss)
def search(self, buffer, freshlen, searchwindowsize=None):
- '''This searches 'buffer' for the first occurence of one of the search
+ '''This searches 'buffer' for the first occurrence of one of the search
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.
@@ -220,7 +217,7 @@ class searcher_re(object):
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
+ match - the re.match object returned by a successful re.search
'''
@@ -267,7 +264,7 @@ class searcher_re(object):
return '\n'.join(ss)
def search(self, buffer, freshlen, searchwindowsize=None):
- '''This searches 'buffer' for the first occurence of one of the regular
+ '''This searches 'buffer' for the first occurrence of one of the regular
expressions. 'freshlen' must indicate the number of bytes at the end of
'buffer' which have not been searched before.
diff --git a/pexpect/fdpexpect.py b/pexpect/fdpexpect.py
index ac7443e..cd60804 100644
--- a/pexpect/fdpexpect.py
+++ b/pexpect/fdpexpect.py
@@ -1,5 +1,5 @@
'''This is like pexpect, but it will work with any file descriptor that you
-pass it. You are reponsible for opening and close the file descriptor.
+pass it. You are responsible for opening and close the file descriptor.
This allows you to use Pexpect with sockets and named pipes (FIFOs).
PEXPECT LICENSE
diff --git a/pexpect/popen_spawn.py b/pexpect/popen_spawn.py
index 0291247..e591197 100644
--- a/pexpect/popen_spawn.py
+++ b/pexpect/popen_spawn.py
@@ -15,6 +15,7 @@ except ImportError:
from .spawnbase import SpawnBase, PY3
from .exceptions import EOF
+from .utils import string_types
class PopenSpawn(SpawnBase):
if PY3:
@@ -39,8 +40,8 @@ class PopenSpawn(SpawnBase):
kwargs['startupinfo'] = startupinfo
kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
- if not isinstance(cmd, (list, tuple)):
- cmd = shlex.split(cmd)
+ if isinstance(cmd, string_types) and sys.platform != 'win32':
+ cmd = shlex.split(cmd, posix=os.name == 'posix')
self.proc = subprocess.Popen(cmd, **kwargs)
self.pid = self.proc.pid
diff --git a/pexpect/pty_spawn.py b/pexpect/pty_spawn.py
index 0d44230..4afda6a 100644
--- a/pexpect/pty_spawn.py
+++ b/pexpect/pty_spawn.py
@@ -316,7 +316,10 @@ class spawn(SpawnBase):
and SIGINT). '''
self.flush()
- self.ptyproc.close(force=force)
+ with _wrap_ptyprocess_err():
+ # PtyProcessError may be raised if it is not possible to terminate
+ # the child.
+ self.ptyproc.close(force=force)
self.isalive() # Update exit status from ptyproc
self.child_fd = -1
diff --git a/pexpect/pxssh.py b/pexpect/pxssh.py
index a9c01d5..e2fccb6 100644
--- a/pexpect/pxssh.py
+++ b/pexpect/pxssh.py
@@ -114,12 +114,12 @@ class pxssh (spawn):
#prompt command different than the regex.
# used to match the command-line prompt
- self.UNIQUE_PROMPT = "\[PEXPECT\][\$\#] "
+ self.UNIQUE_PROMPT = r"\[PEXPECT\][\$\#] "
self.PROMPT = self.UNIQUE_PROMPT
# used to set shell command-line prompt to UNIQUE_PROMPT.
- self.PROMPT_SET_SH = "PS1='[PEXPECT]\$ '"
- self.PROMPT_SET_CSH = "set prompt='[PEXPECT]\$ '"
+ self.PROMPT_SET_SH = r"PS1='[PEXPECT]\$ '"
+ self.PROMPT_SET_CSH = r"set prompt='[PEXPECT]\$ '"
self.SSH_OPTS = ("-o'RSAAuthentication=no'"
+ " -o 'PubkeyAuthentication=no'")
# Disabling host key checking, makes you vulnerable to MITM attacks.
@@ -277,7 +277,7 @@ class pxssh (spawn):
# This does not distinguish between a remote server 'password' prompt
# and a local ssh 'passphrase' prompt (for unlocking a private key).
spawn._spawn(self, cmd)
- i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT, "(?i)connection closed by remote host", EOF], timeout=login_timeout)
+ i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password:)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT, "(?i)connection closed by remote host", EOF], timeout=login_timeout)
# First phase
if i==0:
@@ -285,13 +285,13 @@ class pxssh (spawn):
# This is what you get if SSH does not have the remote host's
# public key stored in the 'known_hosts' cache.
self.sendline("yes")
- i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT])
+ i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password:)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT])
if i==2: # password or passphrase
self.sendline(password)
- i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT])
+ i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password:)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT])
if i==4:
self.sendline(terminal_type)
- i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT])
+ i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password:)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT])
if i==7:
self.close()
raise ExceptionPxssh('Could not establish connection to host')
diff --git a/pexpect/replwrap.py b/pexpect/replwrap.py
index 118aa9f..ed0e657 100644
--- a/pexpect/replwrap.py
+++ b/pexpect/replwrap.py
@@ -114,9 +114,9 @@ def bash(command="bash"):
# replwrap seeing that as the next prompt, we'll embed the marker characters
# for invisible characters in the prompt; these show up when inspecting the
# environment variable, but not when bash displays the prompt.
- ps1 = PEXPECT_PROMPT[:5] + u'\[\]' + PEXPECT_PROMPT[5:]
- ps2 = PEXPECT_CONTINUATION_PROMPT[:5] + u'\[\]' + PEXPECT_CONTINUATION_PROMPT[5:]
+ ps1 = PEXPECT_PROMPT[:5] + u'\\[\\]' + PEXPECT_PROMPT[5:]
+ ps2 = PEXPECT_CONTINUATION_PROMPT[:5] + u'\\[\\]' + PEXPECT_CONTINUATION_PROMPT[5:]
prompt_change = u"PS1='{0}' PS2='{1}' PROMPT_COMMAND=''".format(ps1, ps2)
- return REPLWrapper(child, u'\$', prompt_change,
+ return REPLWrapper(child, u'\\$', prompt_change,
extra_init_cmd="export PAGER=cat")
diff --git a/pexpect/screen.py b/pexpect/screen.py
index 0bced89..5ab45b9 100644
--- a/pexpect/screen.py
+++ b/pexpect/screen.py
@@ -69,7 +69,7 @@ def constrain (n, min, max):
class screen:
'''This object maintains the state of a virtual text screen as a
- rectangluar array. This maintains a virtual cursor position and handles
+ rectangular array. This maintains a virtual cursor position and handles
scrolling as characters are added. This supports most of the methods needed
by an ANSI text screen. Row and column indexes are 1-based (not zero-based,
like arrays).
diff --git a/pexpect/spawnbase.py b/pexpect/spawnbase.py
index cb64582..9cdcba6 100644
--- a/pexpect/spawnbase.py
+++ b/pexpect/spawnbase.py
@@ -223,7 +223,7 @@ class SpawnBase(object):
self._pattern_type_err(p)
return compiled_pattern_list
- def expect(self, pattern, timeout=-1, searchwindowsize=-1, async=False):
+ def expect(self, pattern, timeout=-1, searchwindowsize=-1, async_=False, **kw):
'''This seeks through the stream until a pattern is matched. The
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.
@@ -307,7 +307,7 @@ class SpawnBase(object):
If you are trying to optimize for speed then see expect_list().
On Python 3.4, or Python 3.3 with asyncio installed, passing
- ``async=True`` will make this return an :mod:`asyncio` coroutine,
+ ``async_=True`` will make this return an :mod:`asyncio` coroutine,
which you can yield from to get the same result that this method would
normally give directly. So, inside a coroutine, you can replace this code::
@@ -315,15 +315,19 @@ class SpawnBase(object):
With this non-blocking form::
- index = yield from p.expect(patterns, async=True)
+ index = yield from p.expect(patterns, async_=True)
'''
+ if 'async' in kw:
+ async_ = kw.pop('async')
+ if kw:
+ raise TypeError("Unknown keyword arguments: {}".format(kw))
compiled_pattern_list = self.compile_pattern_list(pattern)
return self.expect_list(compiled_pattern_list,
- timeout, searchwindowsize, async)
+ timeout, searchwindowsize, async_)
def expect_list(self, pattern_list, timeout=-1, searchwindowsize=-1,
- async=False):
+ async_=False, **kw):
'''This takes a list of compiled regular expressions and returns the
index into the pattern_list that matched the child output. The list may
also contain EOF or TIMEOUT(which are not compiled regular
@@ -333,21 +337,25 @@ class SpawnBase(object):
the expect() method. This is called by expect().
- Like :meth:`expect`, passing ``async=True`` will make this return an
+ Like :meth:`expect`, passing ``async_=True`` will make this return an
asyncio coroutine.
'''
if timeout == -1:
timeout = self.timeout
+ if 'async' in kw:
+ async_ = kw.pop('async')
+ if kw:
+ raise TypeError("Unknown keyword arguments: {}".format(kw))
exp = Expecter(self, searcher_re(pattern_list), searchwindowsize)
- if async:
- from .async import expect_async
+ if async_:
+ from ._async import expect_async
return expect_async(exp, timeout)
else:
return exp.expect_loop(timeout)
def expect_exact(self, pattern_list, timeout=-1, searchwindowsize=-1,
- async=False):
+ async_=False, **kw):
'''This is similar to expect(), but uses plain string matching instead
of compiled regular expressions in 'pattern_list'. The 'pattern_list'
@@ -361,11 +369,15 @@ class SpawnBase(object):
This method is also useful when you don't want to have to worry about
escaping regular expression characters that you want to match.
- Like :meth:`expect`, passing ``async=True`` will make this return an
+ Like :meth:`expect`, passing ``async_=True`` will make this return an
asyncio coroutine.
'''
if timeout == -1:
timeout = self.timeout
+ if 'async' in kw:
+ async_ = kw.pop('async')
+ if kw:
+ raise TypeError("Unknown keyword arguments: {}".format(kw))
if (isinstance(pattern_list, self.allowed_string_types) or
pattern_list in (TIMEOUT, EOF)):
@@ -385,8 +397,8 @@ class SpawnBase(object):
pattern_list = [prepare_pattern(p) for p in pattern_list]
exp = Expecter(self, searcher_string(pattern_list), searchwindowsize)
- if async:
- from .async import expect_async
+ if async_:
+ from ._async import expect_async
return expect_async(exp, timeout)
else:
return exp.expect_loop(timeout)
@@ -417,7 +429,7 @@ class SpawnBase(object):
# 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.
+ # I would catch any bugs early and ensure consistent 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
@@ -489,7 +501,7 @@ class SpawnBase(object):
# For 'with spawn(...) as child:'
def __enter__(self):
return self
-
+
def __exit__(self, etype, evalue, tb):
# We rely on subclasses to implement close(). If they don't, it's not
# clear what a context manager should do.
diff --git a/pexpect/utils.py b/pexpect/utils.py
index ae0fe9d..bafc280 100644
--- a/pexpect/utils.py
+++ b/pexpect/utils.py
@@ -11,6 +11,11 @@ except NameError:
# Alias Python2 exception to Python3
InterruptedError = select.error
+if sys.version_info[0] >= 3:
+ string_types = (str,)
+else:
+ string_types = (unicode, str)
+
def is_executable_file(path):
"""Checks that path is an executable regular file, or a symlink towards one.
diff --git a/tests/test_async.py b/tests/test_async.py
index 1a15524..1cc3236 100644
--- a/tests/test_async.py
+++ b/tests/test_async.py
@@ -18,53 +18,53 @@ class AsyncTests(PexpectTestCase):
def test_simple_expect(self):
p = pexpect.spawn('cat')
p.sendline('Hello asyncio')
- coro = p.expect(['Hello', pexpect.EOF] , async=True)
+ coro = p.expect(['Hello', pexpect.EOF] , async_=True)
assert run(coro) == 0
print('Done')
def test_timeout(self):
p = pexpect.spawn('cat')
- coro = p.expect('foo', timeout=1, async=True)
+ coro = p.expect('foo', timeout=1, async_=True)
with self.assertRaises(pexpect.TIMEOUT):
run(coro)
p = pexpect.spawn('cat')
- coro = p.expect(['foo', pexpect.TIMEOUT], timeout=1, async=True)
+ coro = p.expect(['foo', pexpect.TIMEOUT], timeout=1, async_=True)
assert run(coro) == 1
def test_eof(self):
p = pexpect.spawn('cat')
p.sendline('Hi')
- coro = p.expect(pexpect.EOF, async=True)
+ coro = p.expect(pexpect.EOF, async_=True)
p.sendeof()
assert run(coro) == 0
p = pexpect.spawn('cat')
p.sendeof()
- coro = p.expect('Blah', async=True)
+ coro = p.expect('Blah', async_=True)
with self.assertRaises(pexpect.EOF):
run(coro)
def test_expect_exact(self):
p = pexpect.spawn('%s list100.py' % sys.executable)
- assert run(p.expect_exact(b'5', async=True)) == 0
- assert run(p.expect_exact(['wpeok', b'11'], async=True)) == 1
- assert run(p.expect_exact([b'foo', pexpect.EOF], async=True)) == 1
+ assert run(p.expect_exact(b'5', async_=True)) == 0
+ assert run(p.expect_exact(['wpeok', b'11'], async_=True)) == 1
+ assert run(p.expect_exact([b'foo', pexpect.EOF], async_=True)) == 1
def test_async_utf8(self):
p = pexpect.spawn('%s list100.py' % sys.executable, encoding='utf8')
- assert run(p.expect_exact(u'5', async=True)) == 0
- assert run(p.expect_exact([u'wpeok', u'11'], async=True)) == 1
- assert run(p.expect_exact([u'foo', pexpect.EOF], async=True)) == 1
+ assert run(p.expect_exact(u'5', async_=True)) == 0
+ assert run(p.expect_exact([u'wpeok', u'11'], async_=True)) == 1
+ assert run(p.expect_exact([u'foo', pexpect.EOF], async_=True)) == 1
def test_async_and_gc(self):
p = pexpect.spawn('%s sleep_for.py 1' % sys.executable, encoding='utf8')
- assert run(p.expect_exact(u'READY', async=True)) == 0
+ assert run(p.expect_exact(u'READY', async_=True)) == 0
gc.collect()
- assert run(p.expect_exact(u'END', async=True)) == 0
+ assert run(p.expect_exact(u'END', async_=True)) == 0
def test_async_and_sync(self):
p = pexpect.spawn('echo 1234', encoding='utf8', maxread=1)
- assert run(p.expect_exact(u'1', async=True)) == 0
+ assert run(p.expect_exact(u'1', async_=True)) == 0
assert p.expect_exact(u'2') == 0
- assert run(p.expect_exact(u'3', async=True)) == 0
+ assert run(p.expect_exact(u'3', async_=True)) == 0
diff --git a/tests/test_unicode.py b/tests/test_unicode.py
index 1d2f933..9b5b988 100644
--- a/tests/test_unicode.py
+++ b/tests/test_unicode.py
@@ -155,7 +155,7 @@ class UnicodeTests(PexpectTestCase.PexpectTestCase):
def test_spawn_utf8_incomplete(self):
# This test case ensures correct incremental decoding, which
# otherwise fails when the stream inspected by os.read()
- # does not align exactly at a utf-8 multibyte boundry:
+ # does not align exactly at a utf-8 multibyte boundary:
# UnicodeDecodeError: 'utf8' codec can't decode byte 0xe2 in
# position 0: unexpected end of data
p = pexpect.spawnu('cat', maxread=1)