commit python-pexpect for openSUSE:Factory
Hello community, here is the log from the commit of package python-pexpect for openSUSE:Factory checked in at 2019-05-27 08:37:25 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pexpect (Old) and /work/SRC/openSUSE:Factory/.python-pexpect.new.5148 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "python-pexpect" Mon May 27 08:37:25 2019 rev:28 rq:705391 version:4.7.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pexpect/python-pexpect.changes 2018-12-27 00:27:30.387755644 +0100 +++ /work/SRC/openSUSE:Factory/.python-pexpect.new.5148/python-pexpect.changes 2019-05-27 08:37:26.583093016 +0200 @@ -1,0 +2,12 @@ +Sat May 25 09:06:04 UTC 2019 - Tomáš Chvátal <tchvatal@suse.com> + +- Update to 4.7.0: + * The :meth:`.pxssh.login` method now no longer requires a username if an ssh config is provided and will raise an error if neither are provided. (:ghpull:`562`). + * The :meth:`.pxssh.login` method now supports providing your own ssh command via the cmd parameter. (:ghpull:`528`) (:ghpull:`563`). + * :class:`.pxssh` now supports the use_poll parameter which is passed into :meth:`.pexpect.spawn` (:ghpull:`542`). + * Minor bug fix with ssh_config. (:ghpull:`498`). + * :meth:`.replwrap.run_command` now has async support via an async_ parameter. (:ghpull:`501`). + * :meth:`.pexpect.spawn` will now read additional bytes if able up to a buffer limit. (:ghpull:`304`). +- Drop merged patch fix-test.patch + +------------------------------------------------------------------- Old: ---- fix-test.patch pexpect-4.6.0.tar.gz New: ---- pexpect-4.7.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pexpect.spec ++++++ --- /var/tmp/diff_new_pack.6jP1es/_old 2019-05-27 08:37:27.227092764 +0200 +++ /var/tmp/diff_new_pack.6jP1es/_new 2019-05-27 08:37:27.231092763 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-pexpect # -# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,20 +18,21 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-pexpect -Version: 4.6.0 +Version: 4.7.0 Release: 0 Summary: Pure Python Expect-like module License: ISC Group: Development/Libraries/Python URL: http://pexpect.readthedocs.org/en/latest/ Source: https://files.pythonhosted.org/packages/source/p/pexpect/pexpect-%{version}.tar.gz -Patch0: fix-test.patch BuildRequires: %{python_module ptyprocess} BuildRequires: %{python_module pytest} BuildRequires: fdupes # For man validation BuildRequires: man # For test command calls +# For bash validation +BuildRequires: bash BuildRequires: openssl BuildRequires: python-rpm-macros Requires: python-ptyprocess @@ -44,7 +45,6 @@ %prep %setup -q -n pexpect-%{version} -%patch0 -p1 # Fix wrong-script-interpreter find examples -type f -name "*.py" -exec sed -i "s|#!%{_bindir}/env python||" {} \; @@ -59,7 +59,8 @@ %check export LANG=en_US.UTF-8 -%python_expand py.test-%{$python_bin_suffix} +# test_bash https://github.com/pexpect/pexpect/issues/568 +%pytest -k 'not test_bash' %files %{python_files} %license LICENSE ++++++ pexpect-4.6.0.tar.gz -> pexpect-4.7.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/.coveragerc new/pexpect-4.7.0/.coveragerc --- old/pexpect-4.6.0/.coveragerc 2015-12-08 19:43:59.000000000 +0100 +++ new/pexpect-4.7.0/.coveragerc 1970-01-01 01:00:00.000000000 +0100 @@ -1,3 +0,0 @@ -[run] -source = pexpect -parallel = True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/MANIFEST.in new/pexpect-4.7.0/MANIFEST.in --- old/pexpect-4.6.0/MANIFEST.in 1970-01-01 01:00:00.000000000 +0100 +++ new/pexpect-4.7.0/MANIFEST.in 2019-04-07 03:54:51.000000000 +0200 @@ -0,0 +1,6 @@ +recursive-include doc * +prune doc/_build +recursive-include examples * +include .coveragerc README.rst LICENSE pexpect/bashrc.sh +recursive-include tests * +global-exclude __pycache__ *.pyc *~ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/PKG-INFO new/pexpect-4.7.0/PKG-INFO --- old/pexpect-4.6.0/PKG-INFO 2018-05-29 14:32:50.000000000 +0200 +++ new/pexpect-4.7.0/PKG-INFO 2019-04-07 03:54:58.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pexpect -Version: 4.6.0 +Version: 4.7.0 Summary: Pexpect allows easy control of interactive console applications. Home-page: https://pexpect.readthedocs.io/ Author: Noah Spurrier; Thomas Kluyver; Jeff Quast diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/doc/api/pxssh.rst new/pexpect-4.7.0/doc/api/pxssh.rst --- old/pexpect-4.6.0/doc/api/pxssh.rst 2015-12-08 19:43:59.000000000 +0100 +++ new/pexpect-4.7.0/doc/api/pxssh.rst 2019-04-07 03:54:51.000000000 +0200 @@ -1,6 +1,14 @@ pxssh - control an SSH session ============================== +.. note:: + + *pxssh* is a screen-scraping wrapper around the SSH command on your system. + In many cases, you should consider using + `Paramiko <https://github.com/paramiko/paramiko>`_ instead. + Paramiko is a Python module which speaks the SSH protocol directly, so it + doesn't have the extra complexity of running a local subprocess. + .. automodule:: pexpect.pxssh .. autoclass:: ExceptionPxssh diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/doc/conf.py new/pexpect-4.7.0/doc/conf.py --- old/pexpect-4.6.0/doc/conf.py 2018-05-29 14:15:18.000000000 +0200 +++ new/pexpect-4.7.0/doc/conf.py 2019-04-07 03:54:51.000000000 +0200 @@ -52,7 +52,7 @@ # built documents. # # The short X.Y version. -version = '4.6' +version = '4.7' # The full version, including alpha/beta/rc tags. release = version diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/doc/history.rst new/pexpect-4.7.0/doc/history.rst --- old/pexpect-4.6.0/doc/history.rst 2018-05-29 14:12:45.000000000 +0200 +++ new/pexpect-4.7.0/doc/history.rst 2019-04-07 03:54:51.000000000 +0200 @@ -4,6 +4,24 @@ Releases -------- +Version 4.7 +``````````` + +* The :meth:`.pxssh.login` method now no longer requires a username if an ssh + config is provided and will raise an error if neither are provided. + (:ghpull:`562`). +* The :meth:`.pxssh.login` method now supports providing your own ``ssh`` + command via the ``cmd`` parameter. + (:ghpull:`528`) (:ghpull:`563`). +* :class:`.pxssh` now supports the ``use_poll`` parameter which is passed into :meth:`.pexpect.spawn` + (:ghpull:`542`). +* Minor bug fix with ``ssh_config``. + (:ghpull:`498`). +* :meth:`.replwrap.run_command` now has async support via an ``async_`` parameter. + (:ghpull:`501`). +* :meth:`.pexpect.spawn` will now read additional bytes if able up to a buffer limit. + (:ghpull:`304`). + Version 4.6 ``````````` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/doc/overview.rst new/pexpect-4.7.0/doc/overview.rst --- old/pexpect-4.6.0/doc/overview.rst 2018-04-13 19:51:22.000000000 +0200 +++ new/pexpect-4.7.0/doc/overview.rst 2019-04-07 03:54:51.000000000 +0200 @@ -207,7 +207,10 @@ following will turn on logging and send output to stdout (the screen):: child = pexpect.spawn(foo) - child.logfile = sys.stdout + child.logfile = sys.stdout.buffer + +The `sys.stdout.buffer` object is available since Python 3. With Python 2, one +has to assign just `sys.stdout` instead. Exceptions ---------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/pexpect/__init__.py new/pexpect-4.7.0/pexpect/__init__.py --- old/pexpect-4.6.0/pexpect/__init__.py 2018-05-29 14:15:18.000000000 +0200 +++ new/pexpect-4.7.0/pexpect/__init__.py 2019-04-07 03:54:51.000000000 +0200 @@ -75,7 +75,7 @@ from .pty_spawn import spawn, spawnu from .run import run, runu -__version__ = '4.6.0' +__version__ = '4.7.0' __revision__ = '' __all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu', 'which', 'split_command_line', '__version__', '__revision__'] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/pexpect/_async.py new/pexpect-4.7.0/pexpect/_async.py --- old/pexpect-4.6.0/pexpect/_async.py 2018-04-11 21:03:57.000000000 +0200 +++ new/pexpect-4.7.0/pexpect/_async.py 2019-04-07 03:54:51.000000000 +0200 @@ -1,5 +1,6 @@ import asyncio import errno +import signal from pexpect import EOF @@ -29,6 +30,23 @@ transport.pause_reading() return expecter.timeout(e) +@asyncio.coroutine +def repl_run_command_async(repl, cmdlines, timeout=-1): + res = [] + repl.child.sendline(cmdlines[0]) + for line in cmdlines[1:]: + yield from repl._expect_prompt(timeout=timeout, async_=True) + res.append(repl.child.before) + repl.child.sendline(line) + + # Command was fully submitted, now wait for the next prompt + prompt_idx = yield from repl._expect_prompt(timeout=timeout, async_=True) + if prompt_idx == 1: + # We got the continuation prompt - command was incomplete + repl.child.kill(signal.SIGINT) + yield from repl._expect_prompt(timeout=1, async_=True) + raise ValueError("Continuation prompt found - input was incomplete:") + return u''.join(res + [repl.child.before]) class PatternWaiter(asyncio.Protocol): transport = None @@ -41,7 +59,7 @@ if not self.fut.done(): self.fut.set_result(result) self.transport.pause_reading() - + def error(self, exc): if not self.fut.done(): self.fut.set_exception(exc) @@ -49,7 +67,7 @@ def connection_made(self, transport): self.transport = transport - + def data_received(self, data): spawn = self.expecter.spawn s = spawn._decoder.decode(data) @@ -67,7 +85,7 @@ except Exception as e: self.expecter.errored() self.error(e) - + def eof_received(self): # N.B. If this gets called, async will close the pipe (the spawn object) # for us @@ -78,7 +96,7 @@ self.error(e) else: self.found(index) - + def connection_lost(self, exc): if isinstance(exc, OSError) and exc.errno == errno.EIO: # We may get here without eof_received being called, e.g on Linux diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/pexpect/expect.py new/pexpect-4.7.0/pexpect/expect.py --- old/pexpect-4.6.0/pexpect/expect.py 2018-04-11 21:03:57.000000000 +0200 +++ new/pexpect-4.7.0/pexpect/expect.py 2019-04-07 03:54:51.000000000 +0200 @@ -244,7 +244,7 @@ self.eof_index = -1 self.timeout_index = -1 self._searches = [] - for n, s in zip(list(range(len(patterns))), patterns): + for n, s in enumerate(patterns): if s is EOF: self.eof_index = n continue diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/pexpect/pty_spawn.py new/pexpect-4.7.0/pexpect/pty_spawn.py --- old/pexpect-4.6.0/pexpect/pty_spawn.py 2018-04-13 19:35:52.000000000 +0200 +++ new/pexpect-4.7.0/pexpect/pty_spawn.py 2019-04-07 03:54:51.000000000 +0200 @@ -430,61 +430,83 @@ 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 - implement the timeout. ''' + On the other hand, if there are bytes available to read immediately, + all those bytes will be read (up to the buffer size). So, if the + buffer size is 1 megabyte and there is 1 megabyte of data available + to read, the buffer will be filled, regardless of timeout. + + This is a wrapper around os.read(). It uses select.select() or + select.poll() to implement the timeout. ''' if self.closed: raise ValueError('I/O operation on closed file.') + if self.use_poll: + def select(timeout): + return poll_ignore_interrupts([self.child_fd], timeout) + else: + def select(timeout): + return select_ignore_interrupts([self.child_fd], [], [], timeout)[0] + + # If there is data available to read right now, read as much as + # we can. We do this to increase performance if there are a lot + # of bytes to be read. This also avoids calling isalive() too + # often. See also: + # * https://github.com/pexpect/pexpect/pull/304 + # * http://trac.sagemath.org/ticket/10295 + if select(0): + try: + incoming = super(spawn, self).read_nonblocking(size) + except EOF: + # Maybe the child is dead: update some attributes in that case + self.isalive() + raise + while len(incoming) < size and select(0): + try: + incoming += super(spawn, self).read_nonblocking(size - len(incoming)) + except EOF: + # Maybe the child is dead: update some attributes in that case + self.isalive() + # Don't raise EOF, just return what we read so far. + return incoming + return incoming + 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(): - # timeout of 0 means "poll" - if self.use_poll: - r = poll_ignore_interrupts([self.child_fd], timeout) - else: - r, w, e = select_ignore_interrupts([self.child_fd], [], [], 0) - if not r: - self.flag_eof = True - raise EOF('End Of File (EOF). Braindead platform.') + # The process is dead, but there may or may not be data + # available to read. 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 that reason, it's important + # to do this check before calling select() with timeout. + if select(0): + return super(spawn, self).read_nonblocking(size) + self.flag_eof = True + raise EOF('End Of File (EOF). Braindead platform.') elif self.__irix_hack: # Irix takes a long time before it realizes a child was terminated. + # Make sure that the timeout is at least 2 seconds. # FIXME So does this mean Irix systems are forced to always have # FIXME a 2 second delay when calling read_nonblocking? That sucks. - if self.use_poll: - r = poll_ignore_interrupts([self.child_fd], timeout) - else: - r, w, e = select_ignore_interrupts([self.child_fd], [], [], 2) - if not r and not self.isalive(): - self.flag_eof = True - raise EOF('End Of File (EOF). Slow platform.') - if self.use_poll: - r = poll_ignore_interrupts([self.child_fd], timeout) - else: - r, w, e = select_ignore_interrupts( - [self.child_fd], [], [], timeout - ) - - if not r: - if not self.isalive(): - # 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. - self.flag_eof = True - raise EOF('End of File (EOF). Very slow platform.') - else: - raise TIMEOUT('Timeout exceeded.') + if timeout is not None and timeout < 2: + timeout = 2 - if self.child_fd in r: + # Because of the select(0) check above, we know that no data + # is available right now. But if a non-zero timeout is given + # (possibly timeout=None), we call select() with a timeout. + if (timeout != 0) and select(timeout): return super(spawn, self).read_nonblocking(size) - raise ExceptionPexpect('Reached an unexpected state.') # pragma: no cover + if not self.isalive(): + # 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. + self.flag_eof = True + raise EOF('End of File (EOF). Very slow platform.') + else: + raise TIMEOUT('Timeout exceeded.') def write(self, s): '''This is similar to send() except that there is no return value. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/pexpect/pxssh.py new/pexpect-4.7.0/pexpect/pxssh.py --- old/pexpect-4.6.0/pexpect/pxssh.py 2018-05-29 14:12:45.000000000 +0200 +++ new/pexpect-4.7.0/pexpect/pxssh.py 2019-04-07 03:54:51.000000000 +0200 @@ -109,7 +109,7 @@ username = raw_input('username: ') password = getpass.getpass('password: ') s.login (hostname, username, password) - + `debug_command_string` is only for the test suite to confirm that the string generated for SSH is correct, using this will not allow you to do anything other than get a string back from `pxssh.pxssh.login()`. @@ -118,12 +118,12 @@ def __init__ (self, timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None, ignore_sighup=True, echo=True, options={}, encoding=None, codec_errors='strict', - debug_command_string=False): + debug_command_string=False, use_poll=False): spawn.__init__(self, None, timeout=timeout, maxread=maxread, searchwindowsize=searchwindowsize, logfile=logfile, cwd=cwd, env=env, ignore_sighup=ignore_sighup, echo=echo, - encoding=encoding, codec_errors=codec_errors) + encoding=encoding, codec_errors=codec_errors, use_poll=use_poll) self.name = '<pxssh>' @@ -154,7 +154,7 @@ # Unsetting SSH_ASKPASS on the remote side doesn't disable it! Annoying! #self.SSH_OPTS = "-x -o'RSAAuthentication=no' -o 'PubkeyAuthentication=no'" self.force_password = False - + self.debug_command_string = debug_command_string # User defined SSH options, eg, @@ -220,7 +220,7 @@ can take 12 seconds. Low latency connections are more likely to fail with a low sync_multiplier. Best case sync time gets worse with a high sync multiplier (500 ms with default). ''' - + # All of these timing pace values are magic. # I came up with these based on what seemed reliable for # connecting to a heavily loaded machine I have. @@ -253,20 +253,19 @@ ### TODO: This is getting messy and I'm pretty sure this isn't perfect. ### TODO: I need to draw a flow chart for this. ### TODO: Unit tests for SSH tunnels, remote SSH command exec, disabling original prompt sync - def login (self, server, username, password='', terminal_type='ansi', + def login (self, server, username=None, password='', terminal_type='ansi', original_prompt=r"[#$]", login_timeout=10, port=None, auto_prompt_reset=True, ssh_key=None, quiet=True, sync_multiplier=1, check_local_ip=True, password_regex=r'(?i)(?:password:)|(?:passphrase for key)', ssh_tunnels={}, spawn_local_ssh=True, - sync_original_prompt=True, ssh_config=None): + sync_original_prompt=True, ssh_config=None, cmd='ssh'): '''This logs the user into the given server. - It uses - 'original_prompt' to try to find the prompt right after login. When it - finds the prompt it immediately tries to reset the prompt to something - more easily matched. The default 'original_prompt' is very optimistic - and is easily fooled. It's more reliable to try to match the original + It uses 'original_prompt' to try to find the prompt right after login. + When it finds the prompt it immediately tries to reset the prompt to + something more easily matched. The default 'original_prompt' is very + optimistic and is easily fooled. It's more reliable to try to match the original prompt as exactly as possible to prevent false matches by server strings such as the "Message Of The Day". On many systems you can disable the MOTD on the remote server by creating a zero-length file @@ -284,27 +283,31 @@ uses a unique prompt in the :meth:`prompt` method. If the original prompt is not reset then this will disable the :meth:`prompt` method unless you manually set the :attr:`PROMPT` attribute. - + Set ``password_regex`` if there is a MOTD message with `password` in it. Changing this is like playing in traffic, don't (p)expect it to match straight away. - + If you require to connect to another SSH server from the your original SSH connection set ``spawn_local_ssh`` to `False` and this will use your current session to do so. Setting this option to `False` and not having an active session will trigger an error. - + Set ``ssh_key`` to a file path to an SSH private key to use that SSH key for the session authentication. Set ``ssh_key`` to `True` to force passing the current SSH authentication socket to the desired ``hostname``. - + Set ``ssh_config`` to a file path string of an SSH client config file to pass that file to the client to handle itself. You may set any options you wish in here, however doing so will require you to post extra information that you may not want to if you run into issues. + + Alter the ``cmd`` to change the ssh client used, or to prepend it with network + namespaces. For example ```cmd="ip netns exec vlan2 ssh"``` to execute the ssh in + network namespace named ```vlan```. ''' - + session_regex_array = ["(?i)are you sure you want to continue connecting", original_prompt, password_regex, "(?i)permission denied", "(?i)terminal type", TIMEOUT] session_init_regex_array = [] session_init_regex_array.extend(session_regex_array) @@ -320,7 +323,7 @@ if ssh_config is not None: if spawn_local_ssh and not os.path.isfile(ssh_config): raise ExceptionPxssh('SSH config does not exist or is not a file.') - ssh_options = ssh_options + '-F ' + ssh_config + ssh_options = ssh_options + ' -F ' + ssh_config if port is not None: ssh_options = ssh_options + ' -p %s'%(str(port)) if ssh_key is not None: @@ -331,7 +334,7 @@ if spawn_local_ssh and not os.path.isfile(ssh_key): raise ExceptionPxssh('private ssh key does not exist or is not a file.') ssh_options = ssh_options + ' -i %s' % (ssh_key) - + # SSH tunnels, make sure you know what you're putting into the lists # under each heading. Do not expect these to open 100% of the time, # The port you're requesting might be bound. @@ -354,7 +357,42 @@ if spawn_local_ssh==False: tunnel = quote(str(tunnel)) ssh_options = ssh_options + ' -' + cmd_type + ' ' + str(tunnel) - cmd = "ssh %s -l %s %s" % (ssh_options, username, server) + + if username is not None: + ssh_options = ssh_options + ' -l ' + username + elif ssh_config is None: + raise TypeError('login() needs either a username or an ssh_config') + else: # make sure ssh_config has an entry for the server with a username + with open(ssh_config, 'rt') as f: + lines = [l.strip() for l in f.readlines()] + + server_regex = r'^Host\s+%s\s*$' % server + user_regex = r'^User\s+\w+\s*$' + config_has_server = False + server_has_username = False + for line in lines: + if not config_has_server and re.match(server_regex, line, re.IGNORECASE): + config_has_server = True + elif config_has_server and 'hostname' in line.lower(): + pass + elif config_has_server and 'host' in line.lower(): + server_has_username = False # insurance + break # we have left the relevant section + elif config_has_server and re.match(user_regex, line, re.IGNORECASE): + server_has_username = True + break + + if lines: + del line + + del lines + + if not config_has_server: + raise TypeError('login() ssh_config has no Host entry for %s' % server) + elif not server_has_username: + raise TypeError('login() ssh_config has no user entry for %s' % server) + + cmd += " %s %s" % (ssh_options, server) if self.debug_command_string: return(cmd) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/pexpect/replwrap.py new/pexpect-4.7.0/pexpect/replwrap.py --- old/pexpect-4.6.0/pexpect/replwrap.py 2018-02-10 13:16:58.000000000 +0100 +++ new/pexpect-4.7.0/pexpect/replwrap.py 2019-04-07 03:54:51.000000000 +0200 @@ -61,11 +61,11 @@ self.child.expect(orig_prompt) self.child.sendline(prompt_change) - def _expect_prompt(self, timeout=-1): + def _expect_prompt(self, timeout=-1, async_=False): return self.child.expect_exact([self.prompt, self.continuation_prompt], - timeout=timeout) + timeout=timeout, async_=async_) - def run_command(self, command, timeout=-1): + def run_command(self, command, timeout=-1, async_=False): """Send a command to the REPL, wait for and return output. :param str command: The command to send. Trailing newlines are not needed. @@ -75,6 +75,10 @@ :param int timeout: How long to wait for the next prompt. -1 means the default from the :class:`pexpect.spawn` object (default 30 seconds). None means to wait indefinitely. + :param bool async_: On Python 3.4, or Python 3.3 with asyncio + installed, passing ``async_=True`` will make this return an + :mod:`asyncio` Future, which you can yield from to get the same + result that this method would normally give directly. """ # Split up multiline commands and feed them in bit-by-bit cmdlines = command.splitlines() @@ -84,6 +88,10 @@ if not cmdlines: raise ValueError("No command was given") + if async_: + from ._async import repl_run_command_async + return repl_run_command_async(self, cmdlines, timeout) + res = [] self.child.sendline(cmdlines[0]) for line in cmdlines[1:]: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/pexpect.egg-info/PKG-INFO new/pexpect-4.7.0/pexpect.egg-info/PKG-INFO --- old/pexpect-4.6.0/pexpect.egg-info/PKG-INFO 1970-01-01 01:00:00.000000000 +0100 +++ new/pexpect-4.7.0/pexpect.egg-info/PKG-INFO 2019-04-07 03:54:58.000000000 +0200 @@ -0,0 +1,46 @@ +Metadata-Version: 1.1 +Name: pexpect +Version: 4.7.0 +Summary: Pexpect allows easy control of interactive console applications. +Home-page: https://pexpect.readthedocs.io/ +Author: Noah Spurrier; Thomas Kluyver; Jeff Quast +Author-email: noah@noah.org, thomas@kluyver.me.uk, contact@jeffquast.com +License: ISC license +Description: + Pexpect is a pure Python module for spawning child applications; controlling + them; and responding to expected patterns in their output. Pexpect works like + Don Libes' Expect. Pexpect allows your script to spawn a child application and + control it as if a human were typing commands. + + 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. + + The main features of Pexpect require the pty module in the Python standard + library, which is only available on Unix-like systems. Some features—waiting + for patterns from file descriptors or subprocesses—are also available on + Windows. + +Platform: UNIX +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: System Administrators +Classifier: License :: OSI Approved :: ISC License (ISCL) +Classifier: Operating System :: POSIX +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Topic :: Software Development +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Software Development :: Quality Assurance +Classifier: Topic :: Software Development :: Testing +Classifier: Topic :: System +Classifier: Topic :: System :: Archiving :: Packaging +Classifier: Topic :: System :: Installation/Setup +Classifier: Topic :: System :: Shells +Classifier: Topic :: System :: Software Distribution +Classifier: Topic :: Terminals diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/pexpect.egg-info/SOURCES.txt new/pexpect-4.7.0/pexpect.egg-info/SOURCES.txt --- old/pexpect-4.6.0/pexpect.egg-info/SOURCES.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/pexpect-4.7.0/pexpect.egg-info/SOURCES.txt 2019-04-07 03:54:58.000000000 +0200 @@ -0,0 +1,128 @@ +LICENSE +MANIFEST.in +README.rst +setup.cfg +setup.py +doc/FAQ.rst +doc/Makefile +doc/clean.css +doc/commonissues.rst +doc/conf.py +doc/examples.rst +doc/history.rst +doc/index.rst +doc/install.rst +doc/make.bat +doc/overview.rst +doc/requirements.txt +doc/api/fdpexpect.rst +doc/api/index.rst +doc/api/pexpect.rst +doc/api/popen_spawn.rst +doc/api/pxssh.rst +doc/api/replwrap.rst +doc/sphinxext/github.py +examples/README +examples/astat.py +examples/cgishell.cgi +examples/chess.py +examples/chess2.py +examples/chess3.py +examples/df.py +examples/ftp.py +examples/hive.py +examples/monitor.py +examples/passmass.py +examples/python.py +examples/script.py +examples/ssh_tunnel.py +examples/table_test.html +examples/topip.py +examples/uptime.py +pexpect/ANSI.py +pexpect/FSM.py +pexpect/__init__.py +pexpect/_async.py +pexpect/bashrc.sh +pexpect/exceptions.py +pexpect/expect.py +pexpect/fdpexpect.py +pexpect/popen_spawn.py +pexpect/pty_spawn.py +pexpect/pxssh.py +pexpect/replwrap.py +pexpect/run.py +pexpect/screen.py +pexpect/spawnbase.py +pexpect/utils.py +pexpect.egg-info/PKG-INFO +pexpect.egg-info/SOURCES.txt +pexpect.egg-info/dependency_links.txt +pexpect.egg-info/requires.txt +pexpect.egg-info/top_level.txt +tests/PexpectTestCase.py +tests/README +tests/TESTDATA.txt +tests/__init__.py +tests/adhoc.py +tests/alarm_die.py +tests/bambi.vt +tests/depricated_test_filedescriptor.py +tests/echo_w_prompt.py +tests/echo_wait.py +tests/exit1.py +tests/exit667.c +tests/getch.py +tests/globe.vt +tests/interact.py +tests/list100.py +tests/needs_kill.py +tests/pexpectTest.py +tests/qa.py +tests/sigwinch_report.py +tests/sleep_for.py +tests/swapcase_echo.py +tests/test_FSM.py +tests/test_ansi.py +tests/test_async.py +tests/test_command_list_split.py +tests/test_constructor.py +tests/test_ctrl_chars.py +tests/test_delay.py +tests/test_destructor.py +tests/test_dotall.py +tests/test_env.py +tests/test_expect.py +tests/test_filedescriptor.py +tests/test_interact.py +tests/test_isalive.py +tests/test_log.py +tests/test_misc.py +tests/test_missing_command.py +tests/test_performance.py +tests/test_pickling.py +tests/test_popen_spawn.py +tests/test_pxssh.py +tests/test_replwrap.py +tests/test_repr.py +tests/test_run.py +tests/test_run_out_of_pty.py +tests/test_screen.py +tests/test_socket.py +tests/test_timeout_pattern.py +tests/test_unicode.py +tests/test_which.py +tests/test_winsize.py +tests/tetris.data +tests/ticker.py +tests/torturet.vt +tests/utils.py +tests/fakessh/ssh +tests/platform_checks/README +tests/platform_checks/check.py +tests/platform_checks/check2.py +tests/platform_checks/check_control_terminal.py +tests/platform_checks/check_handler.py +tests/platform_checks/check_read.py +tests/platform_checks/check_signals.py +tests/platform_checks/CSIGNALTEST/test.c \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/pexpect.egg-info/dependency_links.txt new/pexpect-4.7.0/pexpect.egg-info/dependency_links.txt --- old/pexpect-4.6.0/pexpect.egg-info/dependency_links.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/pexpect-4.7.0/pexpect.egg-info/dependency_links.txt 2019-04-07 03:54:58.000000000 +0200 @@ -0,0 +1 @@ + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/pexpect.egg-info/requires.txt new/pexpect-4.7.0/pexpect.egg-info/requires.txt --- old/pexpect-4.6.0/pexpect.egg-info/requires.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/pexpect-4.7.0/pexpect.egg-info/requires.txt 2019-04-07 03:54:58.000000000 +0200 @@ -0,0 +1 @@ +ptyprocess>=0.5 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/pexpect.egg-info/top_level.txt new/pexpect-4.7.0/pexpect.egg-info/top_level.txt --- old/pexpect-4.6.0/pexpect.egg-info/top_level.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/pexpect-4.7.0/pexpect.egg-info/top_level.txt 2019-04-07 03:54:58.000000000 +0200 @@ -0,0 +1 @@ +pexpect diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/setup.cfg new/pexpect-4.7.0/setup.cfg --- old/pexpect-4.6.0/setup.cfg 2018-04-11 21:03:57.000000000 +0200 +++ new/pexpect-4.7.0/setup.cfg 2019-04-07 03:54:58.000000000 +0200 @@ -2,4 +2,9 @@ norecursedirs = .git [bdist_wheel] -universal=1 +universal = 1 + +[egg_info] +tag_build = +tag_date = 0 + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/tests/fakessh/ssh new/pexpect-4.7.0/tests/fakessh/ssh --- old/pexpect-4.6.0/tests/fakessh/ssh 2016-08-21 21:57:33.000000000 +0200 +++ new/pexpect-4.7.0/tests/fakessh/ssh 2019-04-07 03:54:52.000000000 +0200 @@ -3,13 +3,52 @@ import getpass import sys +import getopt PY3 = (sys.version_info[0] >= 3) if not PY3: input = raw_input -server = sys.argv[-1] -if server == 'noserver': - print('No route to host') +ssh_usage = "usage: ssh [-2qV] [-c cipher_spec] [-l login_name]\r\n" \ + + " hostname" + +cipher_valid_list = ['aes128-ctr', 'aes192-ctr', 'aes256-ctr', 'arcfour256', 'arcfour128', \ + 'aes128-cbc','3des-cbc','blowfish-cbc','cast128-cbc','aes192-cbc', \ + 'aes256-cbc','arcfour'] + +try: + server = sys.argv[-1] + if server == 'noserver': + print('No route to host') + sys.exit(1) + + elif len(sys.argv) < 2: + print(ssh_usage) + sys.exit(1) + + cipher = '' + cipher_list = [] + fullCmdArguments = sys.argv + argumentList = fullCmdArguments[1:] + unixOptions = "2qVc:l" + arguments, values = getopt.getopt(argumentList, unixOptions) + for currentArgument, currentValue in arguments: + if currentArgument in ("-2"): + pass + elif currentArgument in ("-V"): + print("Mock SSH client version 0.2") + sys.exit(1) + elif currentArgument in ("-c"): + cipher = currentValue + cipher_list = cipher.split(",") + for cipher_item in cipher_list: + if cipher_item not in cipher_valid_list: + print("Unknown cipher type '" + str(cipher_item) + "'") + sys.exit(1) + + +except Exception as e: + print(ssh_usage) + print('error = ' + str(e)) sys.exit(1) print("Mock SSH client for tests. Do not enter real security info.") @@ -31,4 +70,5 @@ elif cmd == 'echo $?': print(0) elif cmd in ('exit', 'logout'): + print('Closed connection') break diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/tests/test_async.py new/pexpect-4.7.0/tests/test_async.py --- old/pexpect-4.6.0/tests/test_async.py 2018-02-10 13:16:58.000000000 +0100 +++ new/pexpect-4.7.0/tests/test_async.py 2019-04-07 03:54:51.000000000 +0200 @@ -8,6 +8,7 @@ import unittest import pexpect +from pexpect import replwrap from .PexpectTestCase import PexpectTestCase def run(coro): @@ -27,7 +28,7 @@ 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) assert run(coro) == 1 @@ -68,3 +69,29 @@ 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 + + def test_async_replwrap(self): + bash = replwrap.bash() + coro = bash.run_command("time", async_=True) + res = run(coro) + assert 'real' in res, res + + def test_async_replwrap_multiline(self): + bash = replwrap.bash() + coro = bash.run_command("echo '1 2\n3 4'", async_=True) + res = run(coro) + self.assertEqual(res.strip().splitlines(), ['1 2', '3 4']) + + # Should raise ValueError if input is incomplete + coro = bash.run_command("echo '5 6", async_=True) + try: + run(coro) + except ValueError: + pass + else: + assert False, "Didn't raise ValueError for incomplete input" + + # Check that the REPL was reset (SIGINT) after the incomplete input + coro = bash.run_command("echo '1 2\n3 4'", async_=True) + res = run(coro) + self.assertEqual(res.strip().splitlines(), ['1 2', '3 4']) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/tests/test_expect.py new/pexpect-4.7.0/tests/test_expect.py --- old/pexpect-4.6.0/tests/test_expect.py 2018-04-11 21:03:57.000000000 +0200 +++ new/pexpect-4.7.0/tests/test_expect.py 2019-04-07 03:54:51.000000000 +0200 @@ -411,7 +411,7 @@ def test_before_across_chunks(self): # https://github.com/pexpect/pexpect/issues/478 child = pexpect.spawn( - '''/bin/bash -c "openssl rand -base64 {} | head -500 | nl --number-format=rz --number-width=5 2>&1 ; echo 'PATTERN!!!'"'''.format(1024 * 1024 * 2), + '''/bin/bash -c "openssl rand -base64 {} 2>/dev/null | head -500 | nl --number-format=rz --number-width=5 2>&1 ; echo 'PATTERN!!!'"'''.format(1024 * 1024 * 2), searchwindowsize=128 ) child.expect(['PATTERN']) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/tests/test_pxssh.py new/pexpect-4.7.0/tests/test_pxssh.py --- old/pexpect-4.6.0/tests/test_pxssh.py 2018-05-29 14:12:45.000000000 +0200 +++ new/pexpect-4.7.0/tests/test_pxssh.py 2019-04-07 03:54:51.000000000 +0200 @@ -87,11 +87,82 @@ def test_ssh_config_passing_string(self): ssh = pxssh.pxssh(debug_command_string=True) - (temp_file,config_path) = tempfile.mkstemp() + temp_file = tempfile.NamedTemporaryFile() + config_path = temp_file.name string = ssh.login('server', 'me', password='s3cret', spawn_local_ssh=False, ssh_config=config_path) if not '-F '+config_path in string: assert False, 'String generated from SSH config passing is incorrect.' + def test_username_or_ssh_config(self): + try: + ssh = pxssh.pxssh(debug_command_string=True) + temp_file = tempfile.NamedTemporaryFile() + config_path = temp_file.name + string = ssh.login('server') + raise AssertionError('Should have failed due to missing username and missing ssh_config.') + except TypeError: + pass + + def test_ssh_config_user(self): + ssh = pxssh.pxssh(debug_command_string=True) + temp_file = tempfile.NamedTemporaryFile() + config_path = temp_file.name + temp_file.write(b'HosT server\n' + b'UsEr me\n' + b'hOSt not-server\n') + temp_file.seek(0) + string = ssh.login('server', ssh_config=config_path) + + def test_ssh_config_no_username_empty_config(self): + ssh = pxssh.pxssh(debug_command_string=True) + temp_file = tempfile.NamedTemporaryFile() + config_path = temp_file.name + try: + string = ssh.login('server', ssh_config=config_path) + raise AssertionError('Should have failed due to no Host.') + except TypeError: + pass + + def test_ssh_config_wrong_Host(self): + ssh = pxssh.pxssh(debug_command_string=True) + temp_file = tempfile.NamedTemporaryFile() + config_path = temp_file.name + temp_file.write(b'Host not-server\n' + b'Host also-not-server\n') + temp_file.seek(0) + try: + string = ssh.login('server', ssh_config=config_path) + raise AssertionError('Should have failed due to no matching Host.') + except TypeError: + pass + + def test_ssh_config_no_user(self): + ssh = pxssh.pxssh(debug_command_string=True) + temp_file = tempfile.NamedTemporaryFile() + config_path = temp_file.name + temp_file.write(b'Host server\n' + b'Host not-server\n') + temp_file.seek(0) + try: + string = ssh.login('server', ssh_config=config_path) + raise AssertionError('Should have failed due to no user.') + except TypeError: + pass + + def test_ssh_config_empty_user(self): + ssh = pxssh.pxssh(debug_command_string=True) + temp_file = tempfile.NamedTemporaryFile() + config_path = temp_file.name + temp_file.write(b'Host server\n' + b'user \n' + b'Host not-server\n') + temp_file.seek(0) + try: + string = ssh.login('server', ssh_config=config_path) + raise AssertionError('Should have failed due to empty user.') + except TypeError: + pass + def test_ssh_key_string(self): ssh = pxssh.pxssh(debug_command_string=True) confirmation_strings = 0 @@ -105,7 +176,8 @@ assert False, 'String generated from forcing the SSH agent sock is incorrect.' confirmation_strings = 0 - (temp_file,ssh_key) = tempfile.mkstemp() + temp_file = tempfile.NamedTemporaryFile() + ssh_key = temp_file.name confirmation_array = [' -i '+ssh_key] string = ssh.login('server', 'me', password='s3cret', ssh_key=ssh_key) for confirmation in confirmation_array: @@ -115,6 +187,86 @@ if confirmation_strings!=len(confirmation_array): assert False, 'String generated from adding an SSH key is incorrect.' + def test_custom_ssh_cmd_debug(self): + ssh = pxssh.pxssh(debug_command_string=True) + cipher_string = '-c aes128-ctr,aes192-ctr,aes256-ctr,arcfour256,arcfour128,' \ + + 'aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,' \ + + 'aes256-cbc,arcfour' + confirmation_strings = 0 + confirmation_array = [cipher_string, '-2'] + string = ssh.login('server', 'me', password='s3cret', cmd='ssh ' + cipher_string + ' -2') + for confirmation in confirmation_array: + if confirmation in string: + confirmation_strings+=1 + + if confirmation_strings!=len(confirmation_array): + assert False, 'String generated for custom ssh client command is incorrect.' + + def test_custom_ssh_cmd_debug(self): + ssh = pxssh.pxssh(debug_command_string=True) + cipher_string = '-c aes128-ctr,aes192-ctr,aes256-ctr,arcfour256,arcfour128,' \ + + 'aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,' \ + + 'aes256-cbc,arcfour' + confirmation_strings = 0 + confirmation_array = [cipher_string, '-2'] + string = ssh.login('server', 'me', password='s3cret', cmd='ssh ' + cipher_string + ' -2') + for confirmation in confirmation_array: + if confirmation in string: + confirmation_strings+=1 + + if confirmation_strings!=len(confirmation_array): + assert False, 'String generated for custom ssh client command is incorrect.' + + def test_failed_custom_ssh_cmd_debug(self): + ssh = pxssh.pxssh(debug_command_string=True) + cipher_string = '-c invalid_cipher' + confirmation_strings = 0 + confirmation_array = [cipher_string, '-2'] + string = ssh.login('server', 'me', password='s3cret', cmd='ssh ' + cipher_string + ' -2') + for confirmation in confirmation_array: + if confirmation in string: + confirmation_strings+=1 + + if confirmation_strings!=len(confirmation_array): + assert False, 'String generated for custom ssh client command is incorrect.' + + def test_custom_ssh_cmd(self): + try: + ssh = pxssh.pxssh() + cipher_string = '-c aes128-ctr,aes192-ctr,aes256-ctr,arcfour256,arcfour128,' \ + + 'aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,' \ + + 'aes256-cbc,arcfour' + result = ssh.login('server', 'me', password='s3cret', cmd='ssh ' + cipher_string + ' -2') + + ssh.PROMPT = r'Closed connection' + ssh.sendline('exit') + ssh.prompt(timeout=5) + string = str(ssh.before) + str(ssh.after) + + if 'Closed connection' not in string: + assert False, 'should have logged into Mock SSH client and exited' + except pxssh.ExceptionPxssh as e: + assert False, 'should not have raised exception, pxssh.ExceptionPxssh' + else: + pass + + def test_failed_custom_ssh_cmd(self): + try: + ssh = pxssh.pxssh() + cipher_string = '-c invalid_cipher' + result = ssh.login('server', 'me', password='s3cret', cmd='ssh ' + cipher_string + ' -2') + + ssh.PROMPT = r'Closed connection' + ssh.sendline('exit') + ssh.prompt(timeout=5) + string = str(ssh.before) + str(ssh.after) + + if 'Closed connection' not in string: + assert False, 'should not have completed logging into Mock SSH client and exited' + except pxssh.ExceptionPxssh as e: + pass + else: + assert False, 'should have raised exception, pxssh.ExceptionPxssh' if __name__ == '__main__': unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pexpect-4.6.0/tests/test_replwrap.py new/pexpect-4.7.0/tests/test_replwrap.py --- old/pexpect-4.6.0/tests/test_replwrap.py 2016-08-21 21:57:33.000000000 +0200 +++ new/pexpect-4.7.0/tests/test_replwrap.py 2019-04-07 03:54:51.000000000 +0200 @@ -24,8 +24,15 @@ def test_bash(self): bash = replwrap.bash() - res = bash.run_command("time") - assert 'real' in res, res + res = bash.run_command("alias") + assert 'alias' in res, res + + try: + bash.run_command('') + except ValueError: + pass + else: + assert False, "Didn't raise ValueError for empty input" def test_pager_as_cat(self): " PAGER is set to cat, to prevent timeout in ``man sleep``. " @@ -78,7 +85,7 @@ self.assertEqual(res.strip().splitlines(), ['1 2', '3 4']) def test_existing_spawn(self): - child = pexpect.spawn("bash", timeout=5, echo=False, encoding='utf-8') + child = pexpect.spawn("bash", timeout=5, encoding='utf-8') repl = replwrap.REPLWrapper(child, re.compile('[$#]'), "PS1='{0}' PS2='{1}' " "PROMPT_COMMAND=''")
participants (1)
-
root