[third-party] Update pexpect to 4.6.
Reviewers: labath, jdevlieghere Subscribers: lldb-commits Tags: #llvm Differential Revision: https://reviews.llvm.org/D59159 llvm-svn: 355967
This commit is contained in:
parent
e2b8c40a77
commit
205fd03a27
|
@ -1,370 +0,0 @@
|
|||
"""This implements an ANSI terminal emulator as a subclass of screen.
|
||||
|
||||
$Id: ANSI.py 491 2007-12-16 20:04:57Z noah $
|
||||
"""
|
||||
# references:
|
||||
# http://www.retards.org/terminals/vt102.html
|
||||
# http://vt100.net/docs/vt102-ug/contents.html
|
||||
# http://vt100.net/docs/vt220-rm/
|
||||
# http://www.termsys.demon.co.uk/vtansi.htm
|
||||
|
||||
import screen
|
||||
import FSM
|
||||
import copy
|
||||
import string
|
||||
|
||||
|
||||
def Emit(fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.write_ch(fsm.input_symbol)
|
||||
|
||||
|
||||
def StartNumber(fsm):
|
||||
|
||||
fsm.memory.append(fsm.input_symbol)
|
||||
|
||||
|
||||
def BuildNumber(fsm):
|
||||
|
||||
ns = fsm.memory.pop()
|
||||
ns = ns + fsm.input_symbol
|
||||
fsm.memory.append(ns)
|
||||
|
||||
|
||||
def DoBackOne(fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_back()
|
||||
|
||||
|
||||
def DoBack(fsm):
|
||||
|
||||
count = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_back(count)
|
||||
|
||||
|
||||
def DoDownOne(fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_down()
|
||||
|
||||
|
||||
def DoDown(fsm):
|
||||
|
||||
count = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_down(count)
|
||||
|
||||
|
||||
def DoForwardOne(fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_forward()
|
||||
|
||||
|
||||
def DoForward(fsm):
|
||||
|
||||
count = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_forward(count)
|
||||
|
||||
|
||||
def DoUpReverse(fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_up_reverse()
|
||||
|
||||
|
||||
def DoUpOne(fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_up()
|
||||
|
||||
|
||||
def DoUp(fsm):
|
||||
|
||||
count = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_up(count)
|
||||
|
||||
|
||||
def DoHome(fsm):
|
||||
|
||||
c = int(fsm.memory.pop())
|
||||
r = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_home(r, c)
|
||||
|
||||
|
||||
def DoHomeOrigin(fsm):
|
||||
|
||||
c = 1
|
||||
r = 1
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_home(r, c)
|
||||
|
||||
|
||||
def DoEraseDown(fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.erase_down()
|
||||
|
||||
|
||||
def DoErase(fsm):
|
||||
|
||||
arg = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
if arg == 0:
|
||||
screen.erase_down()
|
||||
elif arg == 1:
|
||||
screen.erase_up()
|
||||
elif arg == 2:
|
||||
screen.erase_screen()
|
||||
|
||||
|
||||
def DoEraseEndOfLine(fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.erase_end_of_line()
|
||||
|
||||
|
||||
def DoEraseLine(fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
if arg == 0:
|
||||
screen.end_of_line()
|
||||
elif arg == 1:
|
||||
screen.start_of_line()
|
||||
elif arg == 2:
|
||||
screen.erase_line()
|
||||
|
||||
|
||||
def DoEnableScroll(fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.scroll_screen()
|
||||
|
||||
|
||||
def DoCursorSave(fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_save_attrs()
|
||||
|
||||
|
||||
def DoCursorRestore(fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_restore_attrs()
|
||||
|
||||
|
||||
def DoScrollRegion(fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
r2 = int(fsm.memory.pop())
|
||||
r1 = int(fsm.memory.pop())
|
||||
screen.scroll_screen_rows(r1, r2)
|
||||
|
||||
|
||||
def DoMode(fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
mode = fsm.memory.pop() # Should be 4
|
||||
# screen.setReplaceMode ()
|
||||
|
||||
|
||||
def Log(fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
fsm.memory = [screen]
|
||||
fout = open('log', 'a')
|
||||
fout.write(fsm.input_symbol + ',' + fsm.current_state + '\n')
|
||||
fout.close()
|
||||
|
||||
|
||||
class term (screen.screen):
|
||||
"""This is a placeholder.
|
||||
In theory I might want to add other terminal types.
|
||||
"""
|
||||
|
||||
def __init__(self, r=24, c=80):
|
||||
screen.screen.__init__(self, r, c)
|
||||
|
||||
|
||||
class ANSI (term):
|
||||
|
||||
"""This class encapsulates a generic terminal. It filters a stream and
|
||||
maintains the state of a screen object. """
|
||||
|
||||
def __init__(self, r=24, c=80):
|
||||
|
||||
term.__init__(self, r, c)
|
||||
|
||||
#self.screen = screen (24,80)
|
||||
self.state = FSM.FSM('INIT', [self])
|
||||
self.state.set_default_transition(Log, 'INIT')
|
||||
self.state.add_transition_any('INIT', Emit, 'INIT')
|
||||
self.state.add_transition('\x1b', 'INIT', None, 'ESC')
|
||||
self.state.add_transition_any('ESC', Log, 'INIT')
|
||||
self.state.add_transition('(', 'ESC', None, 'G0SCS')
|
||||
self.state.add_transition(')', 'ESC', None, 'G1SCS')
|
||||
self.state.add_transition_list('AB012', 'G0SCS', None, 'INIT')
|
||||
self.state.add_transition_list('AB012', 'G1SCS', None, 'INIT')
|
||||
self.state.add_transition('7', 'ESC', DoCursorSave, 'INIT')
|
||||
self.state.add_transition('8', 'ESC', DoCursorRestore, 'INIT')
|
||||
self.state.add_transition('M', 'ESC', DoUpReverse, 'INIT')
|
||||
self.state.add_transition('>', 'ESC', DoUpReverse, 'INIT')
|
||||
self.state.add_transition('<', 'ESC', DoUpReverse, 'INIT')
|
||||
# Selects application keypad.
|
||||
self.state.add_transition('=', 'ESC', None, 'INIT')
|
||||
self.state.add_transition('#', 'ESC', None, 'GRAPHICS_POUND')
|
||||
self.state.add_transition_any('GRAPHICS_POUND', None, 'INIT')
|
||||
self.state.add_transition('[', 'ESC', None, 'ELB')
|
||||
# ELB means Escape Left Bracket. That is ^[[
|
||||
self.state.add_transition('H', 'ELB', DoHomeOrigin, 'INIT')
|
||||
self.state.add_transition('D', 'ELB', DoBackOne, 'INIT')
|
||||
self.state.add_transition('B', 'ELB', DoDownOne, 'INIT')
|
||||
self.state.add_transition('C', 'ELB', DoForwardOne, 'INIT')
|
||||
self.state.add_transition('A', 'ELB', DoUpOne, 'INIT')
|
||||
self.state.add_transition('J', 'ELB', DoEraseDown, 'INIT')
|
||||
self.state.add_transition('K', 'ELB', DoEraseEndOfLine, 'INIT')
|
||||
self.state.add_transition('r', 'ELB', DoEnableScroll, 'INIT')
|
||||
self.state.add_transition('m', 'ELB', None, 'INIT')
|
||||
self.state.add_transition('?', 'ELB', None, 'MODECRAP')
|
||||
self.state.add_transition_list(
|
||||
string.digits, 'ELB', StartNumber, 'NUMBER_1')
|
||||
self.state.add_transition_list(
|
||||
string.digits, 'NUMBER_1', BuildNumber, 'NUMBER_1')
|
||||
self.state.add_transition('D', 'NUMBER_1', DoBack, 'INIT')
|
||||
self.state.add_transition('B', 'NUMBER_1', DoDown, 'INIT')
|
||||
self.state.add_transition('C', 'NUMBER_1', DoForward, 'INIT')
|
||||
self.state.add_transition('A', 'NUMBER_1', DoUp, 'INIT')
|
||||
self.state.add_transition('J', 'NUMBER_1', DoErase, 'INIT')
|
||||
self.state.add_transition('K', 'NUMBER_1', DoEraseLine, 'INIT')
|
||||
self.state.add_transition('l', 'NUMBER_1', DoMode, 'INIT')
|
||||
# It gets worse... the 'm' code can have infinite number of
|
||||
# number;number;number before it. I've never seen more than two,
|
||||
# but the specs say it's allowed. crap!
|
||||
self.state.add_transition('m', 'NUMBER_1', None, 'INIT')
|
||||
# LED control. Same problem as 'm' code.
|
||||
self.state.add_transition('q', 'NUMBER_1', None, 'INIT')
|
||||
|
||||
# \E[?47h appears to be "switch to alternate screen"
|
||||
# \E[?47l restores alternate screen... I think.
|
||||
self.state.add_transition_list(
|
||||
string.digits, 'MODECRAP', StartNumber, 'MODECRAP_NUM')
|
||||
self.state.add_transition_list(
|
||||
string.digits,
|
||||
'MODECRAP_NUM',
|
||||
BuildNumber,
|
||||
'MODECRAP_NUM')
|
||||
self.state.add_transition('l', 'MODECRAP_NUM', None, 'INIT')
|
||||
self.state.add_transition('h', 'MODECRAP_NUM', None, 'INIT')
|
||||
|
||||
# RM Reset Mode Esc [ Ps l none
|
||||
self.state.add_transition(';', 'NUMBER_1', None, 'SEMICOLON')
|
||||
self.state.add_transition_any('SEMICOLON', Log, 'INIT')
|
||||
self.state.add_transition_list(
|
||||
string.digits, 'SEMICOLON', StartNumber, 'NUMBER_2')
|
||||
self.state.add_transition_list(
|
||||
string.digits, 'NUMBER_2', BuildNumber, 'NUMBER_2')
|
||||
self.state.add_transition_any('NUMBER_2', Log, 'INIT')
|
||||
self.state.add_transition('H', 'NUMBER_2', DoHome, 'INIT')
|
||||
self.state.add_transition('f', 'NUMBER_2', DoHome, 'INIT')
|
||||
self.state.add_transition('r', 'NUMBER_2', DoScrollRegion, 'INIT')
|
||||
# It gets worse... the 'm' code can have infinite number of
|
||||
# number;number;number before it. I've never seen more than two,
|
||||
# but the specs say it's allowed. crap!
|
||||
self.state.add_transition('m', 'NUMBER_2', None, 'INIT')
|
||||
# LED control. Same problem as 'm' code.
|
||||
self.state.add_transition('q', 'NUMBER_2', None, 'INIT')
|
||||
|
||||
def process(self, c):
|
||||
|
||||
self.state.process(c)
|
||||
|
||||
def process_list(self, l):
|
||||
|
||||
self.write(l)
|
||||
|
||||
def write(self, s):
|
||||
|
||||
for c in s:
|
||||
self.process(c)
|
||||
|
||||
def flush(self):
|
||||
|
||||
pass
|
||||
|
||||
def write_ch(self, ch):
|
||||
"""This puts a character at the current cursor position. cursor
|
||||
position if moved forward with wrap-around, but no scrolling is done if
|
||||
the cursor hits the lower-right corner of the screen. """
|
||||
|
||||
#\r and \n both produce a call to crlf().
|
||||
ch = ch[0]
|
||||
|
||||
if ch == '\r':
|
||||
# self.crlf()
|
||||
return
|
||||
if ch == '\n':
|
||||
self.crlf()
|
||||
return
|
||||
if ch == chr(screen.BS):
|
||||
self.cursor_back()
|
||||
self.put_abs(self.cur_r, self.cur_c, ' ')
|
||||
return
|
||||
|
||||
if ch not in string.printable:
|
||||
fout = open('log', 'a')
|
||||
fout.write('Nonprint: ' + str(ord(ch)) + '\n')
|
||||
fout.close()
|
||||
return
|
||||
self.put_abs(self.cur_r, self.cur_c, ch)
|
||||
old_r = self.cur_r
|
||||
old_c = self.cur_c
|
||||
self.cursor_forward()
|
||||
if old_c == self.cur_c:
|
||||
self.cursor_down()
|
||||
if old_r != self.cur_r:
|
||||
self.cursor_home(self.cur_r, 1)
|
||||
else:
|
||||
self.scroll_up()
|
||||
self.cursor_home(self.cur_r, 1)
|
||||
self.erase_line()
|
||||
|
||||
# def test (self):
|
||||
#
|
||||
# import sys
|
||||
# write_text = 'I\'ve got a ferret sticking up my nose.\n' + \
|
||||
# '(He\'s got a ferret sticking up his nose.)\n' + \
|
||||
# 'How it got there I can\'t tell\n' + \
|
||||
# 'But now it\'s there it hurts like hell\n' + \
|
||||
# 'And what is more it radically affects my sense of smell.\n' + \
|
||||
# '(His sense of smell.)\n' + \
|
||||
# 'I can see a bare-bottomed mandril.\n' + \
|
||||
# '(Slyly eyeing his other nostril.)\n' + \
|
||||
# 'If it jumps inside there too I really don\'t know what to do\n' + \
|
||||
# 'I\'ll be the proud posessor of a kind of nasal zoo.\n' + \
|
||||
# '(A nasal zoo.)\n' + \
|
||||
# 'I\'ve got a ferret sticking up my nose.\n' + \
|
||||
# '(And what is worst of all it constantly explodes.)\n' + \
|
||||
# '"Ferrets don\'t explode," you say\n' + \
|
||||
# 'But it happened nine times yesterday\n' + \
|
||||
# 'And I should know for each time I was standing in the way.\n' + \
|
||||
# 'I\'ve got a ferret sticking up my nose.\n' + \
|
||||
# '(He\'s got a ferret sticking up his nose.)\n' + \
|
||||
# 'How it got there I can\'t tell\n' + \
|
||||
# 'But now it\'s there it hurts like hell\n' + \
|
||||
# 'And what is more it radically affects my sense of smell.\n' + \
|
||||
# '(His sense of smell.)'
|
||||
# self.fill('.')
|
||||
# self.cursor_home()
|
||||
# for c in write_text:
|
||||
# self.write_ch (c)
|
||||
# print str(self)
|
||||
#
|
||||
# if __name__ == '__main__':
|
||||
# t = ANSI(6,65)
|
||||
# t.test()
|
|
@ -1,31 +0,0 @@
|
|||
Installation
|
||||
------------
|
||||
This is a standard Python Distutil distribution. To install simply run:
|
||||
|
||||
python setup.py install
|
||||
|
||||
This makes Pexpect available to any script on the machine. You need
|
||||
root access to install it this way. If you do not have root access or
|
||||
if you do not wish to install Pexpect so that is available to any script
|
||||
then you can just copy the pexpect.py file to same directory as your script.
|
||||
|
||||
Trouble on Debian and Ubuntu
|
||||
----------------------------
|
||||
For some stupid reason Debian Linux does not include the distutils module
|
||||
in the standard 'python' package. Instead, the distutils module is packaged
|
||||
separately in the 'python-dev' package. So to add distutils back
|
||||
into Python, simply use aptitude or apt-get to install 'python-dev'.
|
||||
As root, run this command:
|
||||
apt-get install python-dev
|
||||
Why they do this is mysterious because:
|
||||
- It breaks the Python model of "batteries included".
|
||||
'distutils' isn't an extra or optional module --
|
||||
it's parts of the Standard Python Library.
|
||||
- The Debian 'python-dev' package is a microscopic 50K installed.
|
||||
So what are they saving?
|
||||
- Distutils is not only interesting to developers. Many non-development
|
||||
oriented Python packages use 'distutils' to install applications.
|
||||
- As far as I can tell, the package maintainers must go through
|
||||
more trouble to remove 'distutils' from the standard Python
|
||||
distribution than it would take just to leave it in.
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
Free, open source, and all that good stuff.
|
||||
Pexpect Copyright (c) 2008 Noah Spurrier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
Metadata-Version: 1.0
|
||||
Name: pexpect
|
||||
Version: 2.4
|
||||
Summary: Pexpect is a pure Python Expect. It allows easy control of other applications.
|
||||
Home-page: http://pexpect.sourceforge.net/
|
||||
Author: Noah Spurrier
|
||||
Author-email: noah@noah.org
|
||||
License: MIT license
|
||||
Description: UNKNOWN
|
||||
Platform: UNIX
|
|
@ -1,45 +0,0 @@
|
|||
Pexpect is a Pure Python Expect-like module
|
||||
|
||||
Pexpect makes Python a better tool for controlling other applications.
|
||||
|
||||
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. Unlike other Expect-like modules for
|
||||
Python, Pexpect does not require TCL or Expect nor does it require C
|
||||
extensions to be compiled. It should work on any platform that supports the
|
||||
standard Python pty module. The Pexpect interface was designed to be easy to use.
|
||||
|
||||
If you want to work with the development version of the source code then please
|
||||
read the DEVELOPERS document in the root of the source code tree.
|
||||
|
||||
Free, open source, and all that good stuff.
|
||||
Pexpect Copyright (c) 2008 Noah Spurrier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
Noah Spurrier
|
||||
http://pexpect.sourceforge.net/
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 322 B |
|
@ -1,135 +0,0 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Pexpect - Examples</title>
|
||||
<link rel="stylesheet" href="clean.css" type="text/css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
|
||||
<meta name="Author" content="Noah Spurrier">
|
||||
<meta name="Keywords"
|
||||
content="pexpect, Noah Spurrier, Python, Libes, TCL, Expect, pipe, popen, pyExpect, expectpy, expect-like, expect-alike, expect like">
|
||||
<meta name="Description" content="Examples for using Pexpect.">
|
||||
</head>
|
||||
<body bgcolor="#ffffff" text="#000000">
|
||||
<div id="Header">
|
||||
<h1>Pexpect Examples</h1>
|
||||
</div>
|
||||
<div id="Content">
|
||||
|
||||
<p><span class="code">hive.py</span></p>
|
||||
<p><blockquote>
|
||||
This script creates SSH connections to a list of hosts that
|
||||
you provide. Then you are given a command line prompt. Each
|
||||
shell command that you enter is sent to all the hosts. The
|
||||
response from each host is collected and printed. For example,
|
||||
you could connect to a dozen different machines and reboot
|
||||
them all at once.
|
||||
</p></blockquote>
|
||||
|
||||
<p><span class="code">script.py</span></p>
|
||||
<p><blockquote>
|
||||
This implements a command similar to the classic BSD
|
||||
"script" command.
|
||||
This will start a subshell and log all input and
|
||||
output to a file.
|
||||
This demonstrates the interact() method of Pexpect.
|
||||
</p></blockquote>
|
||||
|
||||
<p><span class="code">fix_cvs_files.py</span></p>
|
||||
<p><blockquote>
|
||||
This is for cleaning up binary files improperly
|
||||
added to CVS.
|
||||
This script scans the given path to find binary
|
||||
files;
|
||||
checks with CVS to see if the sticky options are set
|
||||
to -kb;
|
||||
finally if sticky options are not -kb then uses 'cvs
|
||||
admin' to
|
||||
set the -kb option.
|
||||
</p></blockquote>
|
||||
|
||||
<p><span class="code">ftp.py</span></p>
|
||||
<p><blockquote>
|
||||
This demonstrates an FTP "bookmark".
|
||||
This connects to an ftp site; does a few ftp stuff;
|
||||
and then gives the user
|
||||
interactive control over the session. In this case
|
||||
the "bookmark" is to a
|
||||
directory on the OpenBSD ftp server. It puts you in
|
||||
the i386 packages
|
||||
directory. You can easily modify this for other
|
||||
sites.
|
||||
This demonstrates the interact() method of Pexpect.
|
||||
</p></blockquote>
|
||||
|
||||
<p><span class="code">monitor.py</span></p>
|
||||
<p><blockquote>
|
||||
This runs a sequence of commands on a remote host
|
||||
using SSH.
|
||||
It runs a simple system checks such as uptime and
|
||||
free to monitor
|
||||
the state of the remote host.
|
||||
</p></blockquote>
|
||||
|
||||
<p><span class="code">passmass.py</span></p>
|
||||
<p><blockquote>
|
||||
This will login to each given server and change the
|
||||
password of the
|
||||
given user. This demonstrates scripting logins and
|
||||
passwords.
|
||||
</p></blockquote>
|
||||
|
||||
<p><span class="code">python.py</span></p>
|
||||
<p><blockquote>
|
||||
This starts the python interpreter and prints the
|
||||
greeting message backwards.
|
||||
It then gives the user iteractive control of Python.
|
||||
It's pretty useless!
|
||||
</p></blockquote>
|
||||
|
||||
<p><span class="code">rippy.py</span></p>
|
||||
<p><blockquote>
|
||||
This is a wizard for mencoder. It greatly simplifies
|
||||
the process of
|
||||
ripping a DVD to Divx (mpeg4) format. It can
|
||||
transcode from any
|
||||
video file to another. It has options for resampling
|
||||
the audio stream;
|
||||
removing interlace artifacts, fitting to a target
|
||||
file size, etc.
|
||||
There are lots of options, but the process is simple
|
||||
and easy to use.
|
||||
</p></blockquote>
|
||||
|
||||
<p><span class="code">sshls.py</span></p>
|
||||
<p><blockquote>
|
||||
This lists a directory on a remote machine.
|
||||
</p></blockquote>
|
||||
<p><span class="code">ssh_tunnel.py</span></p>
|
||||
<p><blockquote>
|
||||
This starts an SSH tunnel to a remote machine. It
|
||||
monitors the connection
|
||||
and restarts the tunnel if it goes down.
|
||||
</p></blockquote>
|
||||
<p><span class="code">uptime.py</span></p>
|
||||
<p><blockquote>
|
||||
This will run the uptime command and parse the
|
||||
output into variables.
|
||||
This demonstrates using a single regular expression
|
||||
to match the output
|
||||
of a command and capturing different variable in
|
||||
match groups.
|
||||
The grouping regular expression handles a wide variety of different
|
||||
uptime formats.
|
||||
</blockquote>
|
||||
|
||||
<p>
|
||||
<a href="http://sourceforge.net/projects/pexpect/"
|
||||
title="The Pexpect project page on SourceForge.net"> <img
|
||||
src="http://sourceforge.net/sflogo.php?group_id=59762&type=5"
|
||||
alt="The Pexpect project page on SourceForge.net" border="0"
|
||||
height="31" width="105"> </a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,868 +0,0 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Pexpect - a Pure Python Expect-like module</title>
|
||||
<link rel="stylesheet" href="clean.css" type="text/css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
|
||||
<meta name="Author" content="Noah Spurrier">
|
||||
<meta name="Keywords"
|
||||
content="pexpect, Noah Spurrier, pypect, Python, Libes, TCL, Expect, pipe, popen, pyExpect, expectpy, expect-like, expect-alike, expect like">
|
||||
<meta name="Description"
|
||||
content="Pexpect is a pure Python Expect-like module. Pexpect makes Python a better tool for controlling other applications.">
|
||||
</head>
|
||||
<body bgcolor="#ffffff" text="#000000">
|
||||
<div id="Header">
|
||||
<h1>Pexpect version 2.4<br>
|
||||
a Pure Python Expect-like module
|
||||
</h1>
|
||||
</div>
|
||||
<div id="Content">
|
||||
<p>Pexpect makes Python a better tool for controlling other
|
||||
applications.</p>
|
||||
<p>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.</p>
|
||||
<p>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. Unlike
|
||||
other Expect-like modules for Python, Pexpect does not require TCL or
|
||||
Expect nor does it require C extensions to be compiled. It should work
|
||||
on any platform that supports the standard Python pty module. The
|
||||
Pexpect interface was designed to be easy to use.</p>
|
||||
<table border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="right" valign="top">Send questions to:</td>
|
||||
<td align="left"><a href="http://www.noah.org/email/"><img
|
||||
src="email.png" alt="Click to send email." border="0" height="16"
|
||||
width="100"></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr noshade="noshade" size="1">
|
||||
<h1><a name="license"></a>License: MIT style</h1>
|
||||
<p>
|
||||
Free, open source, and all that good stuff.<br>
|
||||
<br>
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:<br>
|
||||
<br>
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.<br>
|
||||
<br>
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.<br>
|
||||
<br>
|
||||
Pexpect Copyright (c) 2008 Noah Spurrier<br>
|
||||
http://pexpect.sourceforge.net/
|
||||
</p>
|
||||
|
||||
<hr noshade="noshade" size="1">
|
||||
<h1><a name="download"></a><a
|
||||
href="http://sourceforge.net/project/showfiles.php?group_id=59762">Download</a></h1>
|
||||
<p>Download the <a
|
||||
href="http://sourceforge.net/project/showfiles.php?group_id=59762">
|
||||
current version here</a> from the SourceForge site. Grab the current Pexpect tarball.
|
||||
</p>
|
||||
<h2>Installing Pexpect</h2>
|
||||
<p>The Pexpect tarball is a standard Python Distutil distribution.</p>
|
||||
<ol>
|
||||
<li>download <span class="code">pexpect-2.4.tar.gz</span></li>
|
||||
<li><span class="code">tar zxf pexpect-2.4.tar.gz</span></li>
|
||||
<li><span class="code">cd pexpect-2.4</span></li>
|
||||
<li><span class="code">python setup.py install</span> <i>do this as root</i></li>
|
||||
</ol>
|
||||
<h2>Examples</h2>
|
||||
<p>
|
||||
Under the <span class="code">pexpect-2.4</span> directory you should find
|
||||
the <span class="code">examples</span> directory.
|
||||
This is the best way to learn to use Pexpect.
|
||||
See the descriptions of <a href="examples.html">Pexpect Examples</a>.
|
||||
</p>
|
||||
<h2><a name="doc"></a>API Documentation</h2>
|
||||
<p>
|
||||
<blockquote>
|
||||
<a href="pexpect.html">pexpect</a> This is the main module that you want.<br>
|
||||
<a href="pxssh.html">pxssh</a> Pexpect SSH is an extension of 'pexpect.spawn' that specializes in SSH.<br>
|
||||
</blockquote>
|
||||
the following are experimental extensions to Pexpect<br>
|
||||
<blockquote>
|
||||
<a href="fdpexpect.html">fdpexpect</a> fdpexpect extension of 'pexpect.spawn' that uses an open file descriptor.<br>
|
||||
<a href="screen.html">SCREEN</a> This represents a virtual 'screen'.<br>
|
||||
<a href="ANSI.html">ANSI</a> This parses ANSI/VT-100 terminal escape codes.<br>
|
||||
<a href="FSM.html">FSM</a> This is a finite state machine used by ANSI.<br>
|
||||
</blockquote>
|
||||
</p>
|
||||
<hr noshade="noshade" size="1">
|
||||
<h1><a name="status"></a>Project Status</h1>
|
||||
<p>Automated pyunit tests reach over 80%
|
||||
code coverage on pexpect.py. I regularly test on Linux and BSD
|
||||
platforms. I try to test on Solaris and Irix.
|
||||
</p>
|
||||
<hr noshade="noshade" size="1">
|
||||
<h1><a name="requirements"></a>Requirements for use of Pexpect</h1>
|
||||
<h2>Python</h2>
|
||||
<blockquote>
|
||||
<p>Pexpect was written and tested with Python 2.4. It should work on
|
||||
earlier versions that have the <span class="code">pty</span> module. I
|
||||
sometimes even manually test it with Python 1.5.2, but I can't easily
|
||||
run the PyUnit test framework against Python 1.5.2, so I have less
|
||||
confidence in Pexpect on Python 1.5.2.</p>
|
||||
</blockquote>
|
||||
<h2>pty module</h2>
|
||||
<blockquote>
|
||||
<p>Any POSIX system (UNIX) with a working <span class="code">pty</span>
|
||||
module should be able to run Pexpect. The <span class="code">pty</span>
|
||||
module is part of the Standard Python Library, so if you are running on
|
||||
a POSIX system you should have it. The <span class="code">pty</span>
|
||||
module does not run the same on all platforms. It should be solid on Linux
|
||||
and BSD systems. I have taken effort to try to smooth the wrinkles out of the different platforms. To learn more
|
||||
about the wrinkles see <a href="#bugs">Bugs</a> and <a href="#testing">Testing</a>.</p>
|
||||
</blockquote>
|
||||
<p>Pexpect does not currently work on the standard Windows Python (see
|
||||
the pty requirement); however, it seems to work fine using <a
|
||||
href="http://www.cygwin.com/">Cygwin</a>. It is possible to build
|
||||
something like a pty for Windows, but it would have to use a different
|
||||
technique that I am still investigating. I know it's possible because
|
||||
Libes' Expect was ported to Windows. <i>If you have any ideas or
|
||||
skills to contribute in this area then I would really appreciate some
|
||||
tips on how to approach this problem.</i> </p>
|
||||
<hr noshade="noshade" size="1">
|
||||
<h1><a name="overview"></a>Overview</h1>
|
||||
<p>Pexpect can be used for automating interactive applications such as
|
||||
ssh, ftp, mencoder, passwd, etc. The Pexpect interface was designed to be
|
||||
easy to use. Here is an example of Pexpect in action:</p>
|
||||
<blockquote>
|
||||
<pre class="code"># This connects to the openbsd ftp site and<br># downloads the recursive directory listing.<br>import pexpect<br>child = pexpect.spawn ('ftp ftp.openbsd.org')<br>child.expect ('Name .*: ')<br>child.sendline ('anonymous')<br>child.expect ('Password:')<br>child.sendline ('noah@example.com')<br>child.expect ('ftp> ')<br>child.sendline ('cd pub')<br>child.expect('ftp> ')<br>child.sendline ('get ls-lR.gz')<br>child.expect('ftp> ')<br>child.sendline ('bye')<br></pre>
|
||||
</blockquote>
|
||||
<p> Obviously you could write an ftp client using Python's own <span
|
||||
class="code">ftplib</span> module, but this is just a demonstration.
|
||||
You can use this technique with any application. This is especially
|
||||
handy if you are writing automated test tools.</p>
|
||||
|
||||
<p>There are two important methods in Pexpect -- <span class="code"><b>expect()</b></span>
|
||||
and <span class="code"><b>send()</b></span> (or <span class="code">sendline()</span>
|
||||
which is like <span class="code">send()</span> with a linefeed).
|
||||
The <span class="code">expect()</span> method waits for the child application
|
||||
to return a given string. The string you specify is a regular expression, so
|
||||
you can match complicated patterns. The <span class="code"><b>send()</b></span> method
|
||||
writes a string to the child application. From the child's point of
|
||||
view it looks just like someone typed the text from a terminal. After
|
||||
each call to <span class="code"><b>expect()</b></span> the <span
|
||||
class="code"><b>before</b></span> and <span class="code"><b>after</b></span>
|
||||
properties will be set to the text printed by child application. The <span
|
||||
class="code"><b>before</b></span> property will contain all text up to
|
||||
the expected string pattern. The <span class="code"><b>after</b></span> string
|
||||
will contain the text that was matched by the expected pattern.
|
||||
The <span class="code">match</span> property is set to the <span class="code">re MatchObject</span>.
|
||||
</p>
|
||||
|
||||
<p>An example of Pexpect in action may make things more clear. This example uses
|
||||
<span class="code">ftp</span> to login to the OpenBSD site; list files
|
||||
in a directory; and then pass interactive control of the ftp session to
|
||||
the human user.</p>
|
||||
<blockquote>
|
||||
<pre class="code">import pexpect<br>child = pexpect.spawn ('ftp ftp.openbsd.org')<br>child.expect ('Name .*: ')<br>child.sendline ('anonymous')<br>child.expect ('Password:')<br>child.sendline ('noah@example.com')<br>child.expect ('ftp> ')<br>child.sendline ('ls /pub/OpenBSD/')<br>child.expect ('ftp> ')<br>print child.before # Print the result of the ls command.<br>child.interact() # Give control of the child to the user.<br></pre>
|
||||
</blockquote>
|
||||
<h2>Special EOF and TIMEOUT patterns</h2>
|
||||
<p>
|
||||
There are two special patterns to match the End Of File or a Timeout condition.
|
||||
You you can pass these patterns to <span class="code">expect()</span>.
|
||||
These patterns are not regular expressions. Use them like predefined constants.
|
||||
</p>
|
||||
<p>If the child has died and you have read all the child's output then ordinarily
|
||||
<span class="code">expect()</span> will raise an <span class="code">EOF</span>
|
||||
exception. You can read everything up to the EOF without generating an
|
||||
exception by using the EOF pattern <span class="code">expect(pexpect.EOF)</span>.
|
||||
In this case everything the child has output will be available in the <span
|
||||
class="code">before</span> property.</p>
|
||||
<p>The pattern given to <span class="code">expect()</span> may be a
|
||||
regular expression or it may also be a <b>list</b> of regular expressions.
|
||||
This allows you to match multiple optional responses. The <span class="code">expect()</span>
|
||||
method returns the index of the pattern that was matched. For example,
|
||||
say you wanted to login to a server. After entering a password you
|
||||
could get various responses from the server -- your password could be
|
||||
rejected; or you could be allowed in and asked for your terminal type;
|
||||
or you could be let right in and given a command prompt. The following
|
||||
code fragment gives an example of this:</p>
|
||||
<blockquote>
|
||||
<pre class="code">child.expect('password:')<br>child.sendline (my_secret_password)<br># We expect any of these three patterns...<br>i = child.expect (['Permission denied', 'Terminal type', '[#\$] '])<br>if i==0:<br> print 'Permission denied on host. Can't login'<br> child.kill(0)<br>elif i==2:<br> print 'Login OK... need to send terminal type.'<br> child.sendline('vt100')<br> child.expect ('[#\$] ')<br>elif i==3:<br> print 'Login OK.'<br> print 'Shell command prompt', child.after</pre>
|
||||
</blockquote>
|
||||
<p>If nothing matches an expected pattern then expect will eventually
|
||||
raise a TIMEOUT exception. The default time is 30 seconds, but you can
|
||||
change this by passing a timeout argument to expect():</p>
|
||||
<blockquote>
|
||||
<pre class="code"># Wait no more than 2 minutes (120 seconds) for password prompt.<br>child.expect('password:', timeout=120)</pre>
|
||||
</blockquote>
|
||||
<h2>Find the end of line -- CR/LF conventions<br>
|
||||
Matching at the end of a line can be tricky<br>
|
||||
$ regex pattern is useless.<br>
|
||||
</h2>
|
||||
<p>Pexpect matches regular expressions a little differently than what
|
||||
you might be used to.
|
||||
</p>
|
||||
<p><i><b>The $ pattern for end of line match is useless</b></i>.
|
||||
The $ matches the end of string, but Pexpect reads from the child
|
||||
one character at a time, so each character looks like the end of a line.
|
||||
Pexpect can't do a look-ahead into the child's output stream.
|
||||
In general you would have this situation when using regular expressions
|
||||
with any stream.<br>
|
||||
<i>Note, pexpect does have an internal buffer, so reads are faster
|
||||
than one character at a time, but from the user's perspective the regex
|
||||
patterns test happens one character at a time.</i></p>
|
||||
<p>The best way to match the end of a line is to look for the
|
||||
newline: "\r\n" (CR/LF). Yes, that does appear to be DOS-style.
|
||||
It may surprise some UNIX people to learn that terminal TTY device drivers
|
||||
(dumb, vt100, ANSI, xterm, etc.) all use the CR/LF combination to signify
|
||||
the end of line. Pexpect uses a Pseudo-TTY device to talk to the child application, so
|
||||
when the child app prints "\n" you actually see "\r\n".
|
||||
</p>
|
||||
<p><b>UNIX uses just linefeeds to end lines of text, but not when it
|
||||
comes to TTY devices!</b> TTY devices are more like the Windows world.
|
||||
Each line of text end with a CR/LF combination. When you intercept data
|
||||
from a UNIX command from a TTY device you will find that the TTY device
|
||||
outputs a CR/LF combination. A UNIX command may only write a linefeed
|
||||
(\n), but the TTY device driver converts it to CR/LF. This means that
|
||||
your terminal will see lines end with CR/LF (hex <span class="code">0D 0A</span>).
|
||||
Since Pexpect emulates a terminal, to match ends of lines you have to
|
||||
expect the CR/LF combination.</p>
|
||||
<blockquote>
|
||||
<p class="code">child.expect ('\r\n')</p>
|
||||
</blockquote>
|
||||
<p>If you just need to skip past a new line then <span class="code">expect
|
||||
('\n')</span> by itself will work, but if you are expecting a specific
|
||||
pattern before the end of line then you need to explicitly look for the
|
||||
\r. For example the following expects a word at the end of a line:</p>
|
||||
<blockquote>
|
||||
<p class="code">child.expect ('\w+\r\n')</p>
|
||||
</blockquote>
|
||||
<p>But the following would both fail:</p>
|
||||
<blockquote>
|
||||
<p class="code">child.expect ('\w+\n')</p>
|
||||
</blockquote>
|
||||
<p>And as explained before, trying to use '$' to match the end of line
|
||||
would not work either:</p>
|
||||
<blockquote>
|
||||
<p class="code">child.expect ('\w+$')</p>
|
||||
</blockquote>
|
||||
<p>So if you need to explicitly look for the END OF LINE, you want to
|
||||
look for the CR/LF combination -- not just the LF and not the $ pattern.</p>
|
||||
<p>This problem is not limited to Pexpect. This problem happens any
|
||||
time you try to perform a regular expression match on a stream. Regular
|
||||
expressions need to look ahead. With a stream it is hard to look ahead
|
||||
because the process generating the stream may not be finished. There is no
|
||||
way to know if the process has paused momentarily or is finished and
|
||||
waiting for you. <font color="#cc0000">Pexpect must implicitly always
|
||||
do a NON greedy match (minimal) at the end of a input {### already said
|
||||
this}.</font> </p>
|
||||
<p>Pexpect compiles all regular expressions with the DOTALL flag. With
|
||||
the DOTALL flag a "." will match a newline. See the Python <a
|
||||
href="http://www.python.org/doc/current/lib/node115.html#l2h-733">documentation</a></p>
|
||||
<h2>Beware of + and * at the end of input.</h2>
|
||||
<p>Remember that any time you try to match a pattern that needs
|
||||
look-ahead that you will always get a minimal match (non greedy). For
|
||||
example, the following will always return just one character:</p>
|
||||
<blockquote>
|
||||
<p class="code">child.expect ('.+')</p>
|
||||
</blockquote>
|
||||
<p>This example will match successfully, but will always return no
|
||||
characters:</p>
|
||||
<blockquote>
|
||||
<p class="code">child.expect ('.*')</p>
|
||||
</blockquote>
|
||||
<p>Generally any star * expression will match as little as possible</p>
|
||||
<p>One thing you can do is to try to force a non-ambiguous character at
|
||||
the end of your <span class="code">\d+</span> pattern. Expect that
|
||||
character to delimit the string. For example, you might try making the
|
||||
end of your pattrn be <span class="code">\D+</span> instead of <span
|
||||
class="code">\D*</span>. That means number digits alone would not
|
||||
satisfy the (<span class="code">\d+</span>) pattern. You would need
|
||||
some number(s) and at least one <span class="code">\D</span> at the
|
||||
end. </p>
|
||||
<h2>Matching groups</h2>
|
||||
<p>You can group regular expression using parenthesis. After a match,
|
||||
the <span class="code">match</span> parameter of the spawn object will
|
||||
contain the Python Match object. </p>
|
||||
<h2>Examples</h2>
|
||||
<p>Using "match" and groups...</p>
|
||||
<h2>Debugging</h2>
|
||||
<p>If you get the string value of a pexpect.spawn object you will get
|
||||
lots of useful debugging information. For debugging it's very useful to
|
||||
use the following pattern:</p>
|
||||
<p>try:<br>
|
||||
i = child.expect ([pattern1, pattern2, pattern3,
|
||||
etc])<br>
|
||||
except:<br>
|
||||
print "Exception was thrown"<br>
|
||||
print "debug information:"<br>
|
||||
print str(child)<br>
|
||||
</p>
|
||||
<p>It is also useful to log the child's input and out to a file or the
|
||||
screen. The following will turn on logging and send output to stdout
|
||||
(the screen).<br>
|
||||
</p>
|
||||
<p> child = pexpect.spawn (foo)<br>
|
||||
child.logfile = sys.stdout<br>
|
||||
<br>
|
||||
</p>
|
||||
<hr noshade="noshade" size="1">
|
||||
<h1>Exceptions</h1>
|
||||
<p><b>EOF</b></p>
|
||||
<p>Note that two flavors of EOF Exception may be thrown. They are
|
||||
virtually identical except for the message string. For practical
|
||||
purposes you should have no need to distinguish between them, but they
|
||||
do give a little extra information about what type of platform you are
|
||||
running. The two messages are:</p>
|
||||
<blockquote>
|
||||
<p class="code">End Of File (EOF) in read(). Exception style platform.</p>
|
||||
<p class="code">End Of File (EOF) in read(). Empty string style
|
||||
platform.</p>
|
||||
</blockquote>
|
||||
<p>Some UNIX platforms will throw an exception when you try to read
|
||||
from a file descriptor in the EOF state. Other UNIX platforms instead
|
||||
quietly return an empty string to indicate that the EOF state has been
|
||||
reached.</p>
|
||||
<p><b>Expecting EOF</b></p>
|
||||
<p>If you wish to read up to the end of the child's output without
|
||||
generating an <span class="code">EOF</span> exception then use the <span
|
||||
class="code">expect(pexpect.EOF)</span> method.</p>
|
||||
<p><b>TIMEOUT</b></p>
|
||||
<p>The <span class="code">expect()</span> and <span class="code">read()</span>
|
||||
methods will also timeout if the child does not generate any output for
|
||||
a given amount of time. If this happens they will raise a <span
|
||||
class="code">TIMEOUT</span> exception. You can have these method
|
||||
ignore a timeout and block indefinitely by passing None for the timeout
|
||||
parameter.</p>
|
||||
<blockquote>
|
||||
<p class="code">child.expect(pexpect.EOF, timeout=None)</p>
|
||||
</blockquote>
|
||||
<hr noshade="noshade" size="1">
|
||||
<h1><a name="faq"></a>FAQ</h1>
|
||||
<p><b>Q: Why don't shell pipe and redirect (| and >) work when I
|
||||
spawn a command?</b></p>
|
||||
<p>
|
||||
|
||||
A: Remember that Pexpect does NOT interpret shell meta characters such as
|
||||
redirect, pipe, or wild cards (>, |, or *). That's done by a shell not the
|
||||
command you are spawning. 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:
|
||||
|
||||
<pre>
|
||||
child = pexpect.spawn('/bin/sh -c "ls -l | grep LOG > log_list.txt"')
|
||||
child.expect(pexpect.EOF)
|
||||
</pre>
|
||||
|
||||
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:
|
||||
|
||||
<pre>
|
||||
shell_cmd = 'ls -l | grep LOG > log_list.txt'
|
||||
child = pexpect.spawn ('/bin/sh', ['-c', shell_cmd])
|
||||
child.expect (pexpect.EOF)
|
||||
</pre>
|
||||
|
||||
</p>
|
||||
<p><b>Q: Isn't there already a Python Expect?</b></p>
|
||||
<p>A: Yes, there are several of them. They usually require you to
|
||||
compile C. I wanted something that was pure Python and preferably a
|
||||
single module that was simple to install. I also wanted something that
|
||||
was easy to use. This pure Python expect only recently became possible
|
||||
with the introduction of the pty module in the standard Python library.
|
||||
Previously C extensions were required.</p>
|
||||
|
||||
<p><strong>Q: The before and after properties sound weird.</strong></p>
|
||||
<p>Originally I was going to model Pexpect more after Expect, but then
|
||||
I found that I could never remember how to get the context of the stuff
|
||||
I was trying to parse. I hate having to read my own documentation. I
|
||||
decided that it was easier for me to remember what before and after
|
||||
was. It just so happens that this is how the -B and -A options in grep
|
||||
works, so that made it even easier for me to remember. Whatever makes
|
||||
my life easier is what's best.</p>
|
||||
|
||||
<p><b>Q: Why not just use Expect?</b></p>
|
||||
<p>A: I love it. It's great. I has bailed me out of some real jams, but
|
||||
I wanted something that would do 90% of what I need from Expect; be 10%
|
||||
of the size; and allow me to write my code in Python instead of TCL.
|
||||
Pexpect is not nearly as big as Expect, but Pexpect does everything I
|
||||
have ever used Expect for.
|
||||
<!-- :-P If I liked TCL then you wouldn't be reading this. My appologies to Don Libes -- Expect is cool, TK is cool, but TCL is only slightly better than Perl in my book. Hopefully after Expyct is done I will not need to use Expect anymore -- except for that lovely autoexpect tool. Damn, I wish I had that! --> </p>
|
||||
|
||||
<p><b>Q: Why not just use a pipe (popen())?</b></p>
|
||||
<p>A: A pipe works fine for getting the output to non-interactive
|
||||
programs. If you just want to get the output from <span class="code">ls</span>,
|
||||
<span class="code">uname</span>, or <span class="code">ping</span>
|
||||
then this works. Pipes do not work very well for interactive programs
|
||||
and pipes will almost certainly fail for most applications that ask for
|
||||
passwords such as telnet, ftp, or ssh.</p>
|
||||
<p>There are two reasons for this. </p>
|
||||
<p>First an application may bypass stdout and print directly to its
|
||||
controlling TTY. Something like SSH will do this when it asks you for a
|
||||
password. This is why you cannot redirect the password prompt because
|
||||
it does not go through stdout or stderr.</p>
|
||||
<p>The second reason is because most applications are built using the C
|
||||
Standard IO Library (anything that uses <span class="code">#include
|
||||
<stdio.h></span>). One of the features of the stdio library is
|
||||
that it buffers all input and output. Normally output is <b><i>line
|
||||
buffered</i></b> when a program is printing to a TTY (your terminal
|
||||
screen). Every time the program prints a line-feed the currently
|
||||
buffered data will get printed to your screen. The problem comes when
|
||||
you connect a pipe. The stdio library is smart and can tell that it is
|
||||
printing to a pipe instead of a TTY. In that case it switches from line
|
||||
buffer mode to <i><b>block buffered</b></i>. In this mode the
|
||||
currently buffered data is flushed when the buffer is full. This causes
|
||||
most interactive programs to deadlock. Block buffering is more
|
||||
efficient when writing to disks and pipes. Take the situation where a
|
||||
program prints a message "Enter your user name:\n" and then waits for
|
||||
you type type something. In block buffered mode, the stdio library will
|
||||
not put the message into the pipe even though a linefeed is printed.
|
||||
The result is that you never receive the message, yet the child
|
||||
application will sit and wait for you to type a response. Don't confuse
|
||||
the stdio lib's buffer with the pipe's buffer. The pipe buffer is
|
||||
another area that can cause problems. You could flush the input side of
|
||||
a pipe, whereas you have no control over the stdio library buffer. </p>
|
||||
<p>More information: the Standard IO library has three states for a
|
||||
FILE *. These are: _IOFBF for block buffered; _IOLBF for line buffered;
|
||||
and _IONBF for unbuffered. The STDIO lib will use block buffering when
|
||||
talking to a block file descriptor such as a pipe. This is usually not
|
||||
helpful for interactive programs. Short of recompiling your program to
|
||||
include fflush() everywhere or recompiling a custom stdio library there
|
||||
is not much a controlling application can do about this if talking over
|
||||
a pipe.</p>
|
||||
<p> The program may have put data in its output that remains unflushed
|
||||
because the output buffer is not full; then the program will go and
|
||||
deadlock while waiting for input -- because you never send it any
|
||||
because you are still waiting for its output (still stuck in the
|
||||
STDIO's output buffer).</p>
|
||||
<p>The answer is to use a pseudo-tty. A TTY device will force <i><b>line</b></i>
|
||||
buffering (as opposed to block buffering). Line buffering means that
|
||||
you will get each line when the child program sends a line feed. This
|
||||
corresponds to the way most interactive programs operate -- send a line
|
||||
of output then wait for a line of input.</p>
|
||||
<p>I put "answer" in quotes because it's ugly solution and because
|
||||
there is no POSIX standard for pseudo-TTY devices (even though they
|
||||
have a TTY standard...). What would make more sense to me would be to
|
||||
have some way to set a mode on a file descriptor so that it will tell
|
||||
the STDIO to be line-buffered. I have investigated, and I don't think
|
||||
there is a way to set the buffered state of a child process. The STDIO
|
||||
Library does not maintain any external state in the kernel or whatnot,
|
||||
so I don't think there is any way for you to alter it. I'm not quite
|
||||
sure how this line-buffered/block-buffered state change happens
|
||||
internally in the STDIO library. I think the STDIO lib looks at the
|
||||
file descriptor and decides to change behavior based on whether it's a
|
||||
TTY or a block file (see isatty()).</p>
|
||||
<p>I hope that this qualifies as helpful.</p>
|
||||
|
||||
<h1>Don't use a pipe to control another application...</h1>
|
||||
<p>Pexpect may seem similar to <span class="code">os.popen()</span> or
|
||||
<span class="code">commands</span> module. The main difference is that
|
||||
Pexpect (like Expect) uses a pseudo-TTY to talk to the child
|
||||
application. Most applications do no work well through the system()
|
||||
call or through pipes. And probably all applications that ask a user to
|
||||
type in a password will fail. These applications bypass the stdin and
|
||||
read directly from the TTY device. Many applications do not explicitly
|
||||
flush their output buffers. This causes deadlocks if you try to control
|
||||
an interactive application using a pipe. What happens is that most UNIX
|
||||
applications use the stdio (#include <stdio.h>) for input and
|
||||
output. The stdio library behaves differently depending on where the
|
||||
output is going. There is no way to control this behavior from the
|
||||
client end.<br>
|
||||
</p>
|
||||
|
||||
<p><b>Q: Can I do screen scraping with this thing?</b></p>
|
||||
<p>A: That depends. If your application just does line-oriented output
|
||||
then this is easy. If it does screen-oriented output then it may work,
|
||||
but it could be hard. For example, trying to scrape data from the 'top'
|
||||
command would be hard. The top command repaints the text window. </p>
|
||||
<p>I am working on an ANSI / VT100 terminal emulator that will have
|
||||
methods to get characters from an arbitrary X,Y coordinate of the
|
||||
virtual screen. It works and you can play with it, but I have no
|
||||
working examples at this time.</p>
|
||||
<hr noshade="noshade" size="1">
|
||||
<h1><a name="bugs"></a>Bugs</h1>
|
||||
<h2>Threads</h2>
|
||||
<p>On Linux (RH 8) you cannot spawn a child from a different thread and
|
||||
pass the handle back to a worker thread. The child is successfully
|
||||
spawned but you can't interact with it. The only way to make it work is
|
||||
to spawn and interact with the child all in the same thread. [Adam
|
||||
Kerrison] </p>
|
||||
<h2><a name="echo_bug"></a>Timing issue with send() and sendline()</h2>
|
||||
<p>This problem has been addressed and should not effect most users.</p>
|
||||
<p>It is sometimes possible to read an echo of the string sent with <span
|
||||
class="code">send()</span> and <span class="code">sendline()</span>.
|
||||
If you call <span class="code">sendline()</span> and then immediately
|
||||
call <span class="code">readline()</span> you may get part of your
|
||||
output echoed back. You may read back what you just wrote even if the
|
||||
child application does not explicitly echo it. Timing is critical. This
|
||||
could be a security issue when talking to an application that asks for
|
||||
a password; otherwise, this does not seem like a big deal. <i>But why
|
||||
do TTYs do this</i>?</p>
|
||||
<p>People usually report this when they are trying to control SSH or
|
||||
some other login. For example, if your code looks something like this: </p>
|
||||
<pre class="code">child.expect ('[pP]assword:')<br>child.sendline (my_password)</pre>
|
||||
<p><br>
|
||||
<blockquote>
|
||||
1. SSH prints "password:" prompt to the user.<br>
|
||||
2. SSH turns off echo on the TTY device.<br>
|
||||
3. SSH waits for user to enter a password.<br>
|
||||
</blockquote>
|
||||
When scripting with Pexpect what can happen is that Pexpect will response to the "password:" prompt
|
||||
before SSH has had time to turn off TTY echo. In other words, Pexpect sends the password between
|
||||
steps 1. and 2., so the password gets echoed back to the TTY. I would call this an SSH bug.
|
||||
</p>
|
||||
<p>
|
||||
Pexpect now automatically adds a short delay before sending data to a child process.
|
||||
This more closely mimics what happens in the usual human-to-app interaction.
|
||||
The delay can be tuned with the 'delaybeforesend' attribute of the spawn class.
|
||||
In general, this fixes the problem for everyone and so this should not be an issue
|
||||
for most users. For some applications you might with to turn it off.
|
||||
child = pexpect.spawn ("ssh user@example.com")
|
||||
child.delaybeforesend = 0
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
<p>Try changing it to look like the following. I know that this fix
|
||||
does not look correct, but it works. I have not figured out exactly
|
||||
what is happening. You would think that the sleep should be after the
|
||||
sendline(). The fact that the sleep helps when it's between the
|
||||
expect() and the sendline() must be a clue.</p>
|
||||
<pre class="code">child.expect ('[pP]assword:')<br>child.sendline (my_password)</pre>
|
||||
<h2>Timing issue with isalive()</h2>
|
||||
<p>Reading the state of isalive() immediately after a child exits may
|
||||
sometimes return 1. This is a race condition. The child has closed its
|
||||
file descriptor, but has not yet fully exited before Pexpect's
|
||||
isalive() executes. Addings a slight delay before the isalive() will
|
||||
help. In the following example <span class="code">isalive()</span>
|
||||
sometimes returns 1:</p>
|
||||
<blockquote>
|
||||
<pre class="code">child = pexpect.spawn('ls')<br>child.expect(pexpect.EOF)<br>print child.isalive()</pre>
|
||||
</blockquote>
|
||||
<p>But if there is any delay before the call to <span class="code">isalive()</span>
|
||||
then it will always return 0 as expected.</p>
|
||||
<blockquote>
|
||||
<pre class="code">child = pexpect.spawn('ls')<br>child.expect(pexpect.EOF)<br>time.sleep(0.1)<br>print child.isalive()</pre>
|
||||
</blockquote>
|
||||
|
||||
<h2>Truncated output just before child exits</h2>
|
||||
<p><i>So far I have seen this only on older versions of <b>Apple's MacOS X</b>.</i>
|
||||
If the child application quits it may not flush its output buffer. This
|
||||
means that your Pexpect application will receive an EOF even though it
|
||||
should have received a little more data before the child died. This is
|
||||
not generally a problem when talking to interactive child applications.
|
||||
One example where it is a problem is when trying to read output from a
|
||||
program like '<span class="code">ls</span>'. You may receive most of
|
||||
the directory listing, but the last few lines will get lost before you
|
||||
receive an EOF. The reason for this is that '<span class="code">ls</span>'
|
||||
runs; completes its task; and then exits. The buffer is not flushed
|
||||
before exit so the last few lines are lost. The following example
|
||||
demonstrates the problem:</p>
|
||||
<p> </p>
|
||||
<blockquote>
|
||||
<pre class="code">child = pexpect.spawn ('ls -l')<br>child.expect (pexpect.EOF)<br>print child.before <br> </pre>
|
||||
</blockquote>
|
||||
<p></p>
|
||||
|
||||
<h2>Controlling SSH on Solaris</h2>
|
||||
<p>Pexpect does not yet work perfectly on Solaris.
|
||||
One common problem is that SSH sometimes will not allow TTY password
|
||||
authentication. For example, you may expect SSH to ask you for a
|
||||
password using code like this:
|
||||
</p>
|
||||
<pre class="code">child = pexpect.spawn ('ssh user@example.com')<br>child.expect ('assword')<br>child.sendline ('mypassword')<br></pre>
|
||||
You may see the following error come back from a spawned
|
||||
child SSH:
|
||||
<p></p>
|
||||
<blockquote>Permission denied (publickey,keyboard-interactive). </blockquote>
|
||||
<p>
|
||||
This means that SSH thinks it can't access the TTY to ask you for your
|
||||
password.
|
||||
The only solution I have found is to use public key authentication with
|
||||
SSH.
|
||||
This bypasses the need for a password. I'm not happy with this
|
||||
solution.
|
||||
The problem is due to poor support for Solaris Pseudo TTYs in the
|
||||
Python
|
||||
Standard Library. </p>
|
||||
<hr noshade="noshade" size="1">
|
||||
<h1><a name="changes"></a>CHANGES</h1>
|
||||
<h2>Current Release</h2>
|
||||
<p>Fixed OSError exception when a pexpect object is cleaned up.
|
||||
Previously you might have seen this exception:</p>
|
||||
<blockquote>
|
||||
<pre class="code">Exception exceptions.OSError: (10, 'No child processes') <br>in <bound method spawn.__del__ of<br><pexpect.spawn instance at 0xd248c>> ignored</pre>
|
||||
</blockquote>
|
||||
<p>You should not see that anymore. Thanks to Michael Surette.</p>
|
||||
<p>Added support for buffering reads. This greatly improves speed when
|
||||
trying to match long output from a child process. When you create an
|
||||
instance of the spawn object you can then set a buffer size. For now
|
||||
you MUST do the following to turn on buffering -- it may be on by
|
||||
default in future version.</p>
|
||||
<blockquote>
|
||||
<pre class="code">child = pexpect.spawn ('my_command')<br>child.maxread=1000 # Sets buffer to 1000 characters.</pre>
|
||||
</blockquote>
|
||||
<div>
|
||||
<p>I made a subtle change to the way TIMEOUT and EOF exceptions behave.
|
||||
Previously you could either expect these states in which case pexpect
|
||||
will not raise an exception, or you could just let pexpect raise an
|
||||
exception when these states were encountered. If you expected the
|
||||
states then the 'before' property was set to everything before the
|
||||
state was encountered, but if you let pexpect raise the exception then
|
||||
'before' was not set. Now the 'before' property will get set either way
|
||||
you choose to handle these states.</p>
|
||||
<h2><i>Older changes...</i></h2>
|
||||
<p>The spawn object now provides iterators for a <i>file-like interface</i>.
|
||||
This makes Pexpect a more complete file-like object. You can now write
|
||||
code like this:</p>
|
||||
<blockquote>
|
||||
<pre class="code">child = pexpect.spawn ('ls -l')<br>for line in child:<br> print line<br></pre>
|
||||
</blockquote>
|
||||
<p>I added the attribute <span class="code">exitstatus</span>. This
|
||||
will give the exit code returned by the child process. This will be set
|
||||
to <span class="code">None</span> while the child is still alive. When
|
||||
<span class="code">isalive()</span> returns 0 then <span class="code">exitstatus</span>
|
||||
will be set.</p>
|
||||
<p>I made a few more tweaks to <span class="code">isalive()</span> so
|
||||
that it will operate more consistently on different platforms. Solaris
|
||||
is the most difficult to support.</p>
|
||||
<p> </p>
|
||||
<p>You can now put <span class="code">TIMEOUT</span> in a list of
|
||||
expected patterns. This is just like putting <span class="code">EOF</span>
|
||||
in the pattern list. Expecting for a <span class="code">TIMEOUT</span>
|
||||
may not be used as often as <span class="code">EOF</span>, but this
|
||||
makes Pexpect more consitent.</p>
|
||||
<p>Thanks to a suggestion and sample code from Chad J. Schroeder I
|
||||
added the ability for Pexpect to operate on a file descriptor that is
|
||||
already open. This means that Pexpect can be used to control streams
|
||||
such as those from serial port devices. Now you just pass the integer
|
||||
file descriptor as the "command" when contsructing a spawn open. For
|
||||
example on a Linux box with a modem on ttyS1:</p>
|
||||
<blockquote>
|
||||
<pre class="code">fd = os.open("/dev/ttyS1", os.O_RDWR|os.O_NONBLOCK|os.O_NOCTTY)<br>m = pexpect.spawn(fd) # Note integer fd is used instead of usual string.<br>m.send("+++") # Escape sequence<br>m.send("ATZ0\r") # Reset modem to profile 0<br>rval = m.expect(["OK", "ERROR"])</pre>
|
||||
</blockquote>
|
||||
<h3>Pexpect now tests itself on Compile Farm!</h3>
|
||||
<p>I wrote a nice script that uses ssh to connect to each machine on
|
||||
Source Forge's Compile Farm and then run the testall.py script for each
|
||||
platform. The result of the test is then recorded for each platform.
|
||||
Now it's easy to run regression tests across multiple platforms.</p>
|
||||
<h3>Pexpect is a file-like object</h3>
|
||||
<p>The spawn object now provides a <i>file-like interface</i>. It
|
||||
supports most of the methods and attributes defined for Python File
|
||||
Objects. </p>
|
||||
<p>I changed write and writelines() so that they no longer return a
|
||||
value. Use send() if you need that functionality. I did this to make
|
||||
the Spawn object more closely match a file-like object.</p>
|
||||
<p>read() was renamed to read_nonblocking(). I added a new read()
|
||||
method that matches file-like object interface. In general, you should
|
||||
not notice the difference except that read() no longer allows you to
|
||||
directly set the timeout value. I hope this will not effect any
|
||||
existing code. Switching to read_nonblocking() should fix existing code.</p>
|
||||
<p>I changed the name of <span class="code">set_echo()</span> to <span
|
||||
class="code">setecho()</span>.</p>
|
||||
<p>I changed the name of <span class="code">send_eof()</span> to <span
|
||||
class="code">sendeof()</span>.</p>
|
||||
<p>I modified <span class="code">kill()</span> so that it checks to
|
||||
make sure the pid isalive().</p>
|
||||
<p>I modified <span class="code">spawn()</span> (really called from <span
|
||||
class="code">__spawn()</span>)so that it does not raise an expection
|
||||
if <span class="code">setwinsize()</span> 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.</p>
|
||||
<p>Added a method <span class="code">close()</span> that does what you
|
||||
think. It closes the file descriptor of the child application. It makes
|
||||
no attempt to actually kill the child or wait for its status. </p>
|
||||
<p>Add variables <span class="code">__version__</span> and <span
|
||||
class="code">__revision__</span> (from cvs) to the pexpect modules.
|
||||
This is mainly helpful to me so that I can make sure that I'm testing
|
||||
with the right version instead of one already installed.</p>
|
||||
<h3>Logging changes</h3>
|
||||
<blockquote>
|
||||
<p><span class="code">log_open()</span> and <span class="code">log_close()</span>
|
||||
have been removed. Now use <span class="code">setlog()</span>. The <span
|
||||
class="code">setlog()</span> method takes a file object. This is far
|
||||
more flexible than the previous log method. Each time data is written
|
||||
to the file object it will be flushed. To turn logging off simply call <span
|
||||
class="code">setlog()</span> with None.</p>
|
||||
</blockquote>
|
||||
<h2>isalive changes</h2>
|
||||
<blockquote>
|
||||
<p>I renamed the <span class="code">isAlive()</span> method to <span
|
||||
class="code">isalive()</span> to match the more typical naming style
|
||||
in Python. Also the technique used to detect child process status has
|
||||
been drastically modified. Previously I did some funky stuff with
|
||||
signals which caused indigestion in other Python modules on some
|
||||
platforms. It's was a big headache. It still is, but I think it works
|
||||
better now.</p>
|
||||
</blockquote>
|
||||
<h3>attribute name changes</h3>
|
||||
<blockquote>
|
||||
<p>The names of some attributes have been changed. This effects the
|
||||
names of the attributes that are set after called the <span
|
||||
class="code">expect()</span> method.</p>
|
||||
<table class="pymenu" border="0" cellpadding="5">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="pymenu">NEW NAME</th>
|
||||
<th class="pymenu">OLD NAME</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="code">before</span><br>
|
||||
<i>Everything before the match.</i></td>
|
||||
<td><span class="code">before</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="code">after</span><br>
|
||||
<i>Everything after and including the first character of the
|
||||
match</i></td>
|
||||
<td><span class="code">matched</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="code">match</span><br>
|
||||
<i>This is the re MatchObject from the match.<br>
|
||||
You can get groups() from this.<br>
|
||||
See '<span class="code">uptime.py</span>' in the examples tar ball.</i></td>
|
||||
<td><i>New -- Did not exist</i></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</blockquote>
|
||||
<h3>EOF changes</h3>
|
||||
<blockquote>
|
||||
<p>The <span class="code">expect_eof()</span> method is gone. You
|
||||
can now simply use the <span class="code">expect()</span> method to
|
||||
look for EOF.</p>
|
||||
<p>Was:</p>
|
||||
<blockquote>
|
||||
<p><span class="code">p.expect_eof ()</span></p>
|
||||
</blockquote>
|
||||
<p>Now:</p>
|
||||
<blockquote>
|
||||
<p><span class="code">p.expect (pexpect.EOF)</span></p>
|
||||
</blockquote>
|
||||
</blockquote>
|
||||
<hr noshade="noshade" size="1">
|
||||
<h1><a name="testing"></a>TESTING</h1>
|
||||
<p>The following platforms have been tested:</p>
|
||||
<!--
|
||||
<table class="pymenu" border="0" cellpadding="5">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="pymenu">PLATFORM</th>
|
||||
<th class="pymenu">RESULTS</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Linux 2.4.9-ac10-rmk2-np1-cerf2<br>
|
||||
armv4l</td>
|
||||
<td><b><i>all tests passed</i></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Linux 2.4.18 #2<br>
|
||||
sparc64</td>
|
||||
<td><b><i>all tests passed</i></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MacOS X Darwin Kernel Version 5.5<br>
|
||||
powerpc</td>
|
||||
<td>
|
||||
<p>failed more than one test.</p>
|
||||
<p>Generally Pexpect works on OS X, but the nature of the quirks
|
||||
cause a many of the tests to fail. See <a href="#bugs">bugs</a>
|
||||
(Incomplete Child Output). The problem is more than minor, but Pexpect
|
||||
is still more than useful for most tasks. The problem is an edge case.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Linux 2.2.20<br>
|
||||
alpha<br>
|
||||
</td>
|
||||
<td><b><i>all tests passed</i></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Linux 2.4.18-5smp<br>
|
||||
i686</td>
|
||||
<td><b><i>all tests passed</i></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>OpenBSD 2.9 GENERIC#653<br>
|
||||
i386</td>
|
||||
<td><b><i>all tests passed</i></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Solaris</td>
|
||||
<td>
|
||||
<p>failed <span class="code">test_destructor</span></p>
|
||||
<p>Otherwise, this is working pretty well. The destructor problem
|
||||
is minor. For some reason, the <i>second</i> time a pty file
|
||||
descriptor is created and deleted it never gets returned for use. It
|
||||
does not effect the first time or the third time or any time after
|
||||
that. It's only the second time. This is weird... This could be a file
|
||||
descriptor leak, or it could be some peculiarity of how Solaris
|
||||
recycles them. I thought it was a UNIX requirement for the OS to give
|
||||
you the lowest available filedescriptor number. In any case, this
|
||||
should not be a problem unless you create hundreds of pexpect
|
||||
instances... It may also be a pty module bug. </p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Windows XP Cygwin</td>
|
||||
<td>failed <span class="code">test_destructor</span>. That it
|
||||
works at all is amazing to me. Cygwin rules!</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
-->
|
||||
<h1> </h1>
|
||||
<h1><a name="todo">TO DO</a></h1>
|
||||
<p>Add an option to add a delay after each expect() or before each
|
||||
read()/readline() call to automatically avoid the <a href="#echo_bug">echo
|
||||
bug</a>.</p>
|
||||
<p> </p>
|
||||
</div>
|
||||
<hr noshade="noshade" size="1">
|
||||
<table border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td> <a href="http://www.noah.org/email/"><img src="email.png"
|
||||
alt="Click to send email." border="0" height="16" width="100"></a> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="Menu"><b>INDEX</b><br>
|
||||
<hr noshade="noshade" size="1"> <a href="#license"
|
||||
title="Python Software Foundation License">License</a><br>
|
||||
<a href="#download" title="Download and setup instructions">Download</a><br>
|
||||
<a href="#doc" title="Documentation and overview">Documentation</a><br>
|
||||
<a href="#status" title="Project Status">Project Status</a><br>
|
||||
<a href="#requirements" title="System requirements to use Pexpect">Requirements</a><br>
|
||||
<a href="#overview" title="Overview of what Pexpect does">Overview</a><br>
|
||||
<a href="#faq" title="FAQ">FAQ</a><br>
|
||||
<a href="#bugs" title="Bugs and work-arounds">Known Bugs</a><br>
|
||||
<a href="#changes" title="What's new with Pexpect">Recent Changes</a><br>
|
||||
<a href="#testing" title="Test results on various platforms">Testing</a><br>
|
||||
<a href="#todo" title="What to do next">To do</a><br>
|
||||
<a href="http://pexpect.svn.sourceforge.net/viewvc/pexpect/trunk/pexpect/" title="browse SVN">Browse SVN</a><br>
|
||||
<br>
|
||||
<a href="http://sourceforge.net/projects/pexpect/"
|
||||
title="The Pexpect project page on SourceForge.net"> <img
|
||||
src="http://sourceforge.net/sflogo.php?group_id=59762&type=5"
|
||||
alt="The Pexpect project page on SourceForge.net" border="0"
|
||||
height="31" width="105"> </a> </div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,868 +0,0 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Pexpect - a Pure Python Expect-like module</title>
|
||||
<link rel="stylesheet" href="clean.css" type="text/css">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
|
||||
<meta name="Author" content="Noah Spurrier">
|
||||
<meta name="Keywords"
|
||||
content="pexpect, Noah Spurrier, pypect, Python, Libes, TCL, Expect, pipe, popen, pyExpect, expectpy, expect-like, expect-alike, expect like">
|
||||
<meta name="Description"
|
||||
content="Pexpect is a pure Python Expect-like module. Pexpect makes Python a better tool for controlling other applications.">
|
||||
</head>
|
||||
<body bgcolor="#ffffff" text="#000000">
|
||||
<div id="Header">
|
||||
<h1>Pexpect version VERSION<br>
|
||||
a Pure Python Expect-like module
|
||||
</h1>
|
||||
</div>
|
||||
<div id="Content">
|
||||
<p>Pexpect makes Python a better tool for controlling other
|
||||
applications.</p>
|
||||
<p>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.</p>
|
||||
<p>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. Unlike
|
||||
other Expect-like modules for Python, Pexpect does not require TCL or
|
||||
Expect nor does it require C extensions to be compiled. It should work
|
||||
on any platform that supports the standard Python pty module. The
|
||||
Pexpect interface was designed to be easy to use.</p>
|
||||
<table border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="right" valign="top">Send questions to:</td>
|
||||
<td align="left"><a href="http://www.noah.org/email/"><img
|
||||
src="email.png" alt="Click to send email." border="0" height="16"
|
||||
width="100"></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr noshade="noshade" size="1">
|
||||
<h1><a name="license"></a>License: MIT style</h1>
|
||||
<p>
|
||||
Free, open source, and all that good stuff.<br>
|
||||
<br>
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:<br>
|
||||
<br>
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.<br>
|
||||
<br>
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.<br>
|
||||
<br>
|
||||
Pexpect Copyright (c) 2008 Noah Spurrier<br>
|
||||
http://pexpect.sourceforge.net/
|
||||
</p>
|
||||
|
||||
<hr noshade="noshade" size="1">
|
||||
<h1><a name="download"></a><a
|
||||
href="http://sourceforge.net/project/showfiles.php?group_id=59762">Download</a></h1>
|
||||
<p>Download the <a
|
||||
href="http://sourceforge.net/project/showfiles.php?group_id=59762">
|
||||
current version here</a> from the SourceForge site. Grab the current Pexpect tarball.
|
||||
</p>
|
||||
<h2>Installing Pexpect</h2>
|
||||
<p>The Pexpect tarball is a standard Python Distutil distribution.</p>
|
||||
<ol>
|
||||
<li>download <span class="code">pexpect-VERSION.tar.gz</span></li>
|
||||
<li><span class="code">tar zxf pexpect-VERSION.tar.gz</span></li>
|
||||
<li><span class="code">cd pexpect-VERSION</span></li>
|
||||
<li><span class="code">python setup.py install</span> <i>do this as root</i></li>
|
||||
</ol>
|
||||
<h2>Examples</h2>
|
||||
<p>
|
||||
Under the <span class="code">pexpect-VERSION</span> directory you should find
|
||||
the <span class="code">examples</span> directory.
|
||||
This is the best way to learn to use Pexpect.
|
||||
See the descriptions of <a href="examples.html">Pexpect Examples</a>.
|
||||
</p>
|
||||
<h2><a name="doc"></a>API Documentation</h2>
|
||||
<p>
|
||||
<blockquote>
|
||||
<a href="pexpect.html">pexpect</a> This is the main module that you want.<br>
|
||||
<a href="pxssh.html">pxssh</a> Pexpect SSH is an extension of 'pexpect.spawn' that specializes in SSH.<br>
|
||||
</blockquote>
|
||||
the following are experimental extensions to Pexpect<br>
|
||||
<blockquote>
|
||||
<a href="fdpexpect.html">fdpexpect</a> fdpexpect extension of 'pexpect.spawn' that uses an open file descriptor.<br>
|
||||
<a href="screen.html">SCREEN</a> This represents a virtual 'screen'.<br>
|
||||
<a href="ANSI.html">ANSI</a> This parses ANSI/VT-100 terminal escape codes.<br>
|
||||
<a href="FSM.html">FSM</a> This is a finite state machine used by ANSI.<br>
|
||||
</blockquote>
|
||||
</p>
|
||||
<hr noshade="noshade" size="1">
|
||||
<h1><a name="status"></a>Project Status</h1>
|
||||
<p>Automated pyunit tests reach over 80%
|
||||
code coverage on pexpect.py. I regularly test on Linux and BSD
|
||||
platforms. I try to test on Solaris and Irix.
|
||||
</p>
|
||||
<hr noshade="noshade" size="1">
|
||||
<h1><a name="requirements"></a>Requirements for use of Pexpect</h1>
|
||||
<h2>Python</h2>
|
||||
<blockquote>
|
||||
<p>Pexpect was written and tested with Python 2.4. It should work on
|
||||
earlier versions that have the <span class="code">pty</span> module. I
|
||||
sometimes even manually test it with Python 1.5.2, but I can't easily
|
||||
run the PyUnit test framework against Python 1.5.2, so I have less
|
||||
confidence in Pexpect on Python 1.5.2.</p>
|
||||
</blockquote>
|
||||
<h2>pty module</h2>
|
||||
<blockquote>
|
||||
<p>Any POSIX system (UNIX) with a working <span class="code">pty</span>
|
||||
module should be able to run Pexpect. The <span class="code">pty</span>
|
||||
module is part of the Standard Python Library, so if you are running on
|
||||
a POSIX system you should have it. The <span class="code">pty</span>
|
||||
module does not run the same on all platforms. It should be solid on Linux
|
||||
and BSD systems. I have taken effort to try to smooth the wrinkles out of the different platforms. To learn more
|
||||
about the wrinkles see <a href="#bugs">Bugs</a> and <a href="#testing">Testing</a>.</p>
|
||||
</blockquote>
|
||||
<p>Pexpect does not currently work on the standard Windows Python (see
|
||||
the pty requirement); however, it seems to work fine using <a
|
||||
href="http://www.cygwin.com/">Cygwin</a>. It is possible to build
|
||||
something like a pty for Windows, but it would have to use a different
|
||||
technique that I am still investigating. I know it's possible because
|
||||
Libes' Expect was ported to Windows. <i>If you have any ideas or
|
||||
skills to contribute in this area then I would really appreciate some
|
||||
tips on how to approach this problem.</i> </p>
|
||||
<hr noshade="noshade" size="1">
|
||||
<h1><a name="overview"></a>Overview</h1>
|
||||
<p>Pexpect can be used for automating interactive applications such as
|
||||
ssh, ftp, mencoder, passwd, etc. The Pexpect interface was designed to be
|
||||
easy to use. Here is an example of Pexpect in action:</p>
|
||||
<blockquote>
|
||||
<pre class="code"># This connects to the openbsd ftp site and<br># downloads the recursive directory listing.<br>import pexpect<br>child = pexpect.spawn ('ftp ftp.openbsd.org')<br>child.expect ('Name .*: ')<br>child.sendline ('anonymous')<br>child.expect ('Password:')<br>child.sendline ('noah@example.com')<br>child.expect ('ftp> ')<br>child.sendline ('cd pub')<br>child.expect('ftp> ')<br>child.sendline ('get ls-lR.gz')<br>child.expect('ftp> ')<br>child.sendline ('bye')<br></pre>
|
||||
</blockquote>
|
||||
<p> Obviously you could write an ftp client using Python's own <span
|
||||
class="code">ftplib</span> module, but this is just a demonstration.
|
||||
You can use this technique with any application. This is especially
|
||||
handy if you are writing automated test tools.</p>
|
||||
|
||||
<p>There are two important methods in Pexpect -- <span class="code"><b>expect()</b></span>
|
||||
and <span class="code"><b>send()</b></span> (or <span class="code">sendline()</span>
|
||||
which is like <span class="code">send()</span> with a linefeed).
|
||||
The <span class="code">expect()</span> method waits for the child application
|
||||
to return a given string. The string you specify is a regular expression, so
|
||||
you can match complicated patterns. The <span class="code"><b>send()</b></span> method
|
||||
writes a string to the child application. From the child's point of
|
||||
view it looks just like someone typed the text from a terminal. After
|
||||
each call to <span class="code"><b>expect()</b></span> the <span
|
||||
class="code"><b>before</b></span> and <span class="code"><b>after</b></span>
|
||||
properties will be set to the text printed by child application. The <span
|
||||
class="code"><b>before</b></span> property will contain all text up to
|
||||
the expected string pattern. The <span class="code"><b>after</b></span> string
|
||||
will contain the text that was matched by the expected pattern.
|
||||
The <span class="code">match</span> property is set to the <span class="code">re MatchObject</span>.
|
||||
</p>
|
||||
|
||||
<p>An example of Pexpect in action may make things more clear. This example uses
|
||||
<span class="code">ftp</span> to login to the OpenBSD site; list files
|
||||
in a directory; and then pass interactive control of the ftp session to
|
||||
the human user.</p>
|
||||
<blockquote>
|
||||
<pre class="code">import pexpect<br>child = pexpect.spawn ('ftp ftp.openbsd.org')<br>child.expect ('Name .*: ')<br>child.sendline ('anonymous')<br>child.expect ('Password:')<br>child.sendline ('noah@example.com')<br>child.expect ('ftp> ')<br>child.sendline ('ls /pub/OpenBSD/')<br>child.expect ('ftp> ')<br>print child.before # Print the result of the ls command.<br>child.interact() # Give control of the child to the user.<br></pre>
|
||||
</blockquote>
|
||||
<h2>Special EOF and TIMEOUT patterns</h2>
|
||||
<p>
|
||||
There are two special patterns to match the End Of File or a Timeout condition.
|
||||
You you can pass these patterns to <span class="code">expect()</span>.
|
||||
These patterns are not regular expressions. Use them like predefined constants.
|
||||
</p>
|
||||
<p>If the child has died and you have read all the child's output then ordinarily
|
||||
<span class="code">expect()</span> will raise an <span class="code">EOF</span>
|
||||
exception. You can read everything up to the EOF without generating an
|
||||
exception by using the EOF pattern <span class="code">expect(pexpect.EOF)</span>.
|
||||
In this case everything the child has output will be available in the <span
|
||||
class="code">before</span> property.</p>
|
||||
<p>The pattern given to <span class="code">expect()</span> may be a
|
||||
regular expression or it may also be a <b>list</b> of regular expressions.
|
||||
This allows you to match multiple optional responses. The <span class="code">expect()</span>
|
||||
method returns the index of the pattern that was matched. For example,
|
||||
say you wanted to login to a server. After entering a password you
|
||||
could get various responses from the server -- your password could be
|
||||
rejected; or you could be allowed in and asked for your terminal type;
|
||||
or you could be let right in and given a command prompt. The following
|
||||
code fragment gives an example of this:</p>
|
||||
<blockquote>
|
||||
<pre class="code">child.expect('password:')<br>child.sendline (my_secret_password)<br># We expect any of these three patterns...<br>i = child.expect (['Permission denied', 'Terminal type', '[#\$] '])<br>if i==0:<br> print 'Permission denied on host. Can't login'<br> child.kill(0)<br>elif i==2:<br> print 'Login OK... need to send terminal type.'<br> child.sendline('vt100')<br> child.expect ('[#\$] ')<br>elif i==3:<br> print 'Login OK.'<br> print 'Shell command prompt', child.after</pre>
|
||||
</blockquote>
|
||||
<p>If nothing matches an expected pattern then expect will eventually
|
||||
raise a TIMEOUT exception. The default time is 30 seconds, but you can
|
||||
change this by passing a timeout argument to expect():</p>
|
||||
<blockquote>
|
||||
<pre class="code"># Wait no more than 2 minutes (120 seconds) for password prompt.<br>child.expect('password:', timeout=120)</pre>
|
||||
</blockquote>
|
||||
<h2>Find the end of line -- CR/LF conventions<br>
|
||||
Matching at the end of a line can be tricky<br>
|
||||
$ regex pattern is useless.<br>
|
||||
</h2>
|
||||
<p>Pexpect matches regular expressions a little differently than what
|
||||
you might be used to.
|
||||
</p>
|
||||
<p><i><b>The $ pattern for end of line match is useless</b></i>.
|
||||
The $ matches the end of string, but Pexpect reads from the child
|
||||
one character at a time, so each character looks like the end of a line.
|
||||
Pexpect can't do a look-ahead into the child's output stream.
|
||||
In general you would have this situation when using regular expressions
|
||||
with any stream.<br>
|
||||
<i>Note, pexpect does have an internal buffer, so reads are faster
|
||||
than one character at a time, but from the user's perspective the regex
|
||||
patterns test happens one character at a time.</i></p>
|
||||
<p>The best way to match the end of a line is to look for the
|
||||
newline: "\r\n" (CR/LF). Yes, that does appear to be DOS-style.
|
||||
It may surprise some UNIX people to learn that terminal TTY device drivers
|
||||
(dumb, vt100, ANSI, xterm, etc.) all use the CR/LF combination to signify
|
||||
the end of line. Pexpect uses a Pseudo-TTY device to talk to the child application, so
|
||||
when the child app prints "\n" you actually see "\r\n".
|
||||
</p>
|
||||
<p><b>UNIX uses just linefeeds to end lines of text, but not when it
|
||||
comes to TTY devices!</b> TTY devices are more like the Windows world.
|
||||
Each line of text end with a CR/LF combination. When you intercept data
|
||||
from a UNIX command from a TTY device you will find that the TTY device
|
||||
outputs a CR/LF combination. A UNIX command may only write a linefeed
|
||||
(\n), but the TTY device driver converts it to CR/LF. This means that
|
||||
your terminal will see lines end with CR/LF (hex <span class="code">0D 0A</span>).
|
||||
Since Pexpect emulates a terminal, to match ends of lines you have to
|
||||
expect the CR/LF combination.</p>
|
||||
<blockquote>
|
||||
<p class="code">child.expect ('\r\n')</p>
|
||||
</blockquote>
|
||||
<p>If you just need to skip past a new line then <span class="code">expect
|
||||
('\n')</span> by itself will work, but if you are expecting a specific
|
||||
pattern before the end of line then you need to explicitly look for the
|
||||
\r. For example the following expects a word at the end of a line:</p>
|
||||
<blockquote>
|
||||
<p class="code">child.expect ('\w+\r\n')</p>
|
||||
</blockquote>
|
||||
<p>But the following would both fail:</p>
|
||||
<blockquote>
|
||||
<p class="code">child.expect ('\w+\n')</p>
|
||||
</blockquote>
|
||||
<p>And as explained before, trying to use '$' to match the end of line
|
||||
would not work either:</p>
|
||||
<blockquote>
|
||||
<p class="code">child.expect ('\w+$')</p>
|
||||
</blockquote>
|
||||
<p>So if you need to explicitly look for the END OF LINE, you want to
|
||||
look for the CR/LF combination -- not just the LF and not the $ pattern.</p>
|
||||
<p>This problem is not limited to Pexpect. This problem happens any
|
||||
time you try to perform a regular expression match on a stream. Regular
|
||||
expressions need to look ahead. With a stream it is hard to look ahead
|
||||
because the process generating the stream may not be finished. There is no
|
||||
way to know if the process has paused momentarily or is finished and
|
||||
waiting for you. <font color="#cc0000">Pexpect must implicitly always
|
||||
do a NON greedy match (minimal) at the end of a input {### already said
|
||||
this}.</font> </p>
|
||||
<p>Pexpect compiles all regular expressions with the DOTALL flag. With
|
||||
the DOTALL flag a "." will match a newline. See the Python <a
|
||||
href="http://www.python.org/doc/current/lib/node115.html#l2h-733">documentation</a></p>
|
||||
<h2>Beware of + and * at the end of input.</h2>
|
||||
<p>Remember that any time you try to match a pattern that needs
|
||||
look-ahead that you will always get a minimal match (non greedy). For
|
||||
example, the following will always return just one character:</p>
|
||||
<blockquote>
|
||||
<p class="code">child.expect ('.+')</p>
|
||||
</blockquote>
|
||||
<p>This example will match successfully, but will always return no
|
||||
characters:</p>
|
||||
<blockquote>
|
||||
<p class="code">child.expect ('.*')</p>
|
||||
</blockquote>
|
||||
<p>Generally any star * expression will match as little as possible</p>
|
||||
<p>One thing you can do is to try to force a non-ambiguous character at
|
||||
the end of your <span class="code">\d+</span> pattern. Expect that
|
||||
character to delimit the string. For example, you might try making the
|
||||
end of your pattrn be <span class="code">\D+</span> instead of <span
|
||||
class="code">\D*</span>. That means number digits alone would not
|
||||
satisfy the (<span class="code">\d+</span>) pattern. You would need
|
||||
some number(s) and at least one <span class="code">\D</span> at the
|
||||
end. </p>
|
||||
<h2>Matching groups</h2>
|
||||
<p>You can group regular expression using parenthesis. After a match,
|
||||
the <span class="code">match</span> parameter of the spawn object will
|
||||
contain the Python Match object. </p>
|
||||
<h2>Examples</h2>
|
||||
<p>Using "match" and groups...</p>
|
||||
<h2>Debugging</h2>
|
||||
<p>If you get the string value of a pexpect.spawn object you will get
|
||||
lots of useful debugging information. For debugging it's very useful to
|
||||
use the following pattern:</p>
|
||||
<p>try:<br>
|
||||
i = child.expect ([pattern1, pattern2, pattern3,
|
||||
etc])<br>
|
||||
except:<br>
|
||||
print "Exception was thrown"<br>
|
||||
print "debug information:"<br>
|
||||
print str(child)<br>
|
||||
</p>
|
||||
<p>It is also useful to log the child's input and out to a file or the
|
||||
screen. The following will turn on logging and send output to stdout
|
||||
(the screen).<br>
|
||||
</p>
|
||||
<p> child = pexpect.spawn (foo)<br>
|
||||
child.logfile = sys.stdout<br>
|
||||
<br>
|
||||
</p>
|
||||
<hr noshade="noshade" size="1">
|
||||
<h1>Exceptions</h1>
|
||||
<p><b>EOF</b></p>
|
||||
<p>Note that two flavors of EOF Exception may be thrown. They are
|
||||
virtually identical except for the message string. For practical
|
||||
purposes you should have no need to distinguish between them, but they
|
||||
do give a little extra information about what type of platform you are
|
||||
running. The two messages are:</p>
|
||||
<blockquote>
|
||||
<p class="code">End Of File (EOF) in read(). Exception style platform.</p>
|
||||
<p class="code">End Of File (EOF) in read(). Empty string style
|
||||
platform.</p>
|
||||
</blockquote>
|
||||
<p>Some UNIX platforms will throw an exception when you try to read
|
||||
from a file descriptor in the EOF state. Other UNIX platforms instead
|
||||
quietly return an empty string to indicate that the EOF state has been
|
||||
reached.</p>
|
||||
<p><b>Expecting EOF</b></p>
|
||||
<p>If you wish to read up to the end of the child's output without
|
||||
generating an <span class="code">EOF</span> exception then use the <span
|
||||
class="code">expect(pexpect.EOF)</span> method.</p>
|
||||
<p><b>TIMEOUT</b></p>
|
||||
<p>The <span class="code">expect()</span> and <span class="code">read()</span>
|
||||
methods will also timeout if the child does not generate any output for
|
||||
a given amount of time. If this happens they will raise a <span
|
||||
class="code">TIMEOUT</span> exception. You can have these method
|
||||
ignore a timeout and block indefinitely by passing None for the timeout
|
||||
parameter.</p>
|
||||
<blockquote>
|
||||
<p class="code">child.expect(pexpect.EOF, timeout=None)</p>
|
||||
</blockquote>
|
||||
<hr noshade="noshade" size="1">
|
||||
<h1><a name="faq"></a>FAQ</h1>
|
||||
<p><b>Q: Why don't shell pipe and redirect (| and >) work when I
|
||||
spawn a command?</b></p>
|
||||
<p>
|
||||
|
||||
A: Remember that Pexpect does NOT interpret shell meta characters such as
|
||||
redirect, pipe, or wild cards (>, |, or *). That's done by a shell not the
|
||||
command you are spawning. 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:
|
||||
|
||||
<pre>
|
||||
child = pexpect.spawn('/bin/sh -c "ls -l | grep LOG > log_list.txt"')
|
||||
child.expect(pexpect.EOF)
|
||||
</pre>
|
||||
|
||||
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:
|
||||
|
||||
<pre>
|
||||
shell_cmd = 'ls -l | grep LOG > log_list.txt'
|
||||
child = pexpect.spawn ('/bin/sh', ['-c', shell_cmd])
|
||||
child.expect (pexpect.EOF)
|
||||
</pre>
|
||||
|
||||
</p>
|
||||
<p><b>Q: Isn't there already a Python Expect?</b></p>
|
||||
<p>A: Yes, there are several of them. They usually require you to
|
||||
compile C. I wanted something that was pure Python and preferably a
|
||||
single module that was simple to install. I also wanted something that
|
||||
was easy to use. This pure Python expect only recently became possible
|
||||
with the introduction of the pty module in the standard Python library.
|
||||
Previously C extensions were required.</p>
|
||||
|
||||
<p><strong>Q: The before and after properties sound weird.</strong></p>
|
||||
<p>Originally I was going to model Pexpect more after Expect, but then
|
||||
I found that I could never remember how to get the context of the stuff
|
||||
I was trying to parse. I hate having to read my own documentation. I
|
||||
decided that it was easier for me to remember what before and after
|
||||
was. It just so happens that this is how the -B and -A options in grep
|
||||
works, so that made it even easier for me to remember. Whatever makes
|
||||
my life easier is what's best.</p>
|
||||
|
||||
<p><b>Q: Why not just use Expect?</b></p>
|
||||
<p>A: I love it. It's great. I has bailed me out of some real jams, but
|
||||
I wanted something that would do 90% of what I need from Expect; be 10%
|
||||
of the size; and allow me to write my code in Python instead of TCL.
|
||||
Pexpect is not nearly as big as Expect, but Pexpect does everything I
|
||||
have ever used Expect for.
|
||||
<!-- :-P If I liked TCL then you wouldn't be reading this. My appologies to Don Libes -- Expect is cool, TK is cool, but TCL is only slightly better than Perl in my book. Hopefully after Expyct is done I will not need to use Expect anymore -- except for that lovely autoexpect tool. Damn, I wish I had that! --> </p>
|
||||
|
||||
<p><b>Q: Why not just use a pipe (popen())?</b></p>
|
||||
<p>A: A pipe works fine for getting the output to non-interactive
|
||||
programs. If you just want to get the output from <span class="code">ls</span>,
|
||||
<span class="code">uname</span>, or <span class="code">ping</span>
|
||||
then this works. Pipes do not work very well for interactive programs
|
||||
and pipes will almost certainly fail for most applications that ask for
|
||||
passwords such as telnet, ftp, or ssh.</p>
|
||||
<p>There are two reasons for this. </p>
|
||||
<p>First an application may bypass stdout and print directly to its
|
||||
controlling TTY. Something like SSH will do this when it asks you for a
|
||||
password. This is why you cannot redirect the password prompt because
|
||||
it does not go through stdout or stderr.</p>
|
||||
<p>The second reason is because most applications are built using the C
|
||||
Standard IO Library (anything that uses <span class="code">#include
|
||||
<stdio.h></span>). One of the features of the stdio library is
|
||||
that it buffers all input and output. Normally output is <b><i>line
|
||||
buffered</i></b> when a program is printing to a TTY (your terminal
|
||||
screen). Every time the program prints a line-feed the currently
|
||||
buffered data will get printed to your screen. The problem comes when
|
||||
you connect a pipe. The stdio library is smart and can tell that it is
|
||||
printing to a pipe instead of a TTY. In that case it switches from line
|
||||
buffer mode to <i><b>block buffered</b></i>. In this mode the
|
||||
currently buffered data is flushed when the buffer is full. This causes
|
||||
most interactive programs to deadlock. Block buffering is more
|
||||
efficient when writing to disks and pipes. Take the situation where a
|
||||
program prints a message "Enter your user name:\n" and then waits for
|
||||
you type type something. In block buffered mode, the stdio library will
|
||||
not put the message into the pipe even though a linefeed is printed.
|
||||
The result is that you never receive the message, yet the child
|
||||
application will sit and wait for you to type a response. Don't confuse
|
||||
the stdio lib's buffer with the pipe's buffer. The pipe buffer is
|
||||
another area that can cause problems. You could flush the input side of
|
||||
a pipe, whereas you have no control over the stdio library buffer. </p>
|
||||
<p>More information: the Standard IO library has three states for a
|
||||
FILE *. These are: _IOFBF for block buffered; _IOLBF for line buffered;
|
||||
and _IONBF for unbuffered. The STDIO lib will use block buffering when
|
||||
talking to a block file descriptor such as a pipe. This is usually not
|
||||
helpful for interactive programs. Short of recompiling your program to
|
||||
include fflush() everywhere or recompiling a custom stdio library there
|
||||
is not much a controlling application can do about this if talking over
|
||||
a pipe.</p>
|
||||
<p> The program may have put data in its output that remains unflushed
|
||||
because the output buffer is not full; then the program will go and
|
||||
deadlock while waiting for input -- because you never send it any
|
||||
because you are still waiting for its output (still stuck in the
|
||||
STDIO's output buffer).</p>
|
||||
<p>The answer is to use a pseudo-tty. A TTY device will force <i><b>line</b></i>
|
||||
buffering (as opposed to block buffering). Line buffering means that
|
||||
you will get each line when the child program sends a line feed. This
|
||||
corresponds to the way most interactive programs operate -- send a line
|
||||
of output then wait for a line of input.</p>
|
||||
<p>I put "answer" in quotes because it's ugly solution and because
|
||||
there is no POSIX standard for pseudo-TTY devices (even though they
|
||||
have a TTY standard...). What would make more sense to me would be to
|
||||
have some way to set a mode on a file descriptor so that it will tell
|
||||
the STDIO to be line-buffered. I have investigated, and I don't think
|
||||
there is a way to set the buffered state of a child process. The STDIO
|
||||
Library does not maintain any external state in the kernel or whatnot,
|
||||
so I don't think there is any way for you to alter it. I'm not quite
|
||||
sure how this line-buffered/block-buffered state change happens
|
||||
internally in the STDIO library. I think the STDIO lib looks at the
|
||||
file descriptor and decides to change behavior based on whether it's a
|
||||
TTY or a block file (see isatty()).</p>
|
||||
<p>I hope that this qualifies as helpful.</p>
|
||||
|
||||
<h1>Don't use a pipe to control another application...</h1>
|
||||
<p>Pexpect may seem similar to <span class="code">os.popen()</span> or
|
||||
<span class="code">commands</span> module. The main difference is that
|
||||
Pexpect (like Expect) uses a pseudo-TTY to talk to the child
|
||||
application. Most applications do no work well through the system()
|
||||
call or through pipes. And probably all applications that ask a user to
|
||||
type in a password will fail. These applications bypass the stdin and
|
||||
read directly from the TTY device. Many applications do not explicitly
|
||||
flush their output buffers. This causes deadlocks if you try to control
|
||||
an interactive application using a pipe. What happens is that most UNIX
|
||||
applications use the stdio (#include <stdio.h>) for input and
|
||||
output. The stdio library behaves differently depending on where the
|
||||
output is going. There is no way to control this behavior from the
|
||||
client end.<br>
|
||||
</p>
|
||||
|
||||
<p><b>Q: Can I do screen scraping with this thing?</b></p>
|
||||
<p>A: That depends. If your application just does line-oriented output
|
||||
then this is easy. If it does screen-oriented output then it may work,
|
||||
but it could be hard. For example, trying to scrape data from the 'top'
|
||||
command would be hard. The top command repaints the text window. </p>
|
||||
<p>I am working on an ANSI / VT100 terminal emulator that will have
|
||||
methods to get characters from an arbitrary X,Y coordinate of the
|
||||
virtual screen. It works and you can play with it, but I have no
|
||||
working examples at this time.</p>
|
||||
<hr noshade="noshade" size="1">
|
||||
<h1><a name="bugs"></a>Bugs</h1>
|
||||
<h2>Threads</h2>
|
||||
<p>On Linux (RH 8) you cannot spawn a child from a different thread and
|
||||
pass the handle back to a worker thread. The child is successfully
|
||||
spawned but you can't interact with it. The only way to make it work is
|
||||
to spawn and interact with the child all in the same thread. [Adam
|
||||
Kerrison] </p>
|
||||
<h2><a name="echo_bug"></a>Timing issue with send() and sendline()</h2>
|
||||
<p>This problem has been addressed and should not effect most users.</p>
|
||||
<p>It is sometimes possible to read an echo of the string sent with <span
|
||||
class="code">send()</span> and <span class="code">sendline()</span>.
|
||||
If you call <span class="code">sendline()</span> and then immediately
|
||||
call <span class="code">readline()</span> you may get part of your
|
||||
output echoed back. You may read back what you just wrote even if the
|
||||
child application does not explicitly echo it. Timing is critical. This
|
||||
could be a security issue when talking to an application that asks for
|
||||
a password; otherwise, this does not seem like a big deal. <i>But why
|
||||
do TTYs do this</i>?</p>
|
||||
<p>People usually report this when they are trying to control SSH or
|
||||
some other login. For example, if your code looks something like this: </p>
|
||||
<pre class="code">child.expect ('[pP]assword:')<br>child.sendline (my_password)</pre>
|
||||
<p><br>
|
||||
<blockquote>
|
||||
1. SSH prints "password:" prompt to the user.<br>
|
||||
2. SSH turns off echo on the TTY device.<br>
|
||||
3. SSH waits for user to enter a password.<br>
|
||||
</blockquote>
|
||||
When scripting with Pexpect what can happen is that Pexpect will response to the "password:" prompt
|
||||
before SSH has had time to turn off TTY echo. In other words, Pexpect sends the password between
|
||||
steps 1. and 2., so the password gets echoed back to the TTY. I would call this an SSH bug.
|
||||
</p>
|
||||
<p>
|
||||
Pexpect now automatically adds a short delay before sending data to a child process.
|
||||
This more closely mimics what happens in the usual human-to-app interaction.
|
||||
The delay can be tuned with the 'delaybeforesend' attribute of the spawn class.
|
||||
In general, this fixes the problem for everyone and so this should not be an issue
|
||||
for most users. For some applications you might with to turn it off.
|
||||
child = pexpect.spawn ("ssh user@example.com")
|
||||
child.delaybeforesend = 0
|
||||
</p>
|
||||
<p><br>
|
||||
</p>
|
||||
<p>Try changing it to look like the following. I know that this fix
|
||||
does not look correct, but it works. I have not figured out exactly
|
||||
what is happening. You would think that the sleep should be after the
|
||||
sendline(). The fact that the sleep helps when it's between the
|
||||
expect() and the sendline() must be a clue.</p>
|
||||
<pre class="code">child.expect ('[pP]assword:')<br>child.sendline (my_password)</pre>
|
||||
<h2>Timing issue with isalive()</h2>
|
||||
<p>Reading the state of isalive() immediately after a child exits may
|
||||
sometimes return 1. This is a race condition. The child has closed its
|
||||
file descriptor, but has not yet fully exited before Pexpect's
|
||||
isalive() executes. Addings a slight delay before the isalive() will
|
||||
help. In the following example <span class="code">isalive()</span>
|
||||
sometimes returns 1:</p>
|
||||
<blockquote>
|
||||
<pre class="code">child = pexpect.spawn('ls')<br>child.expect(pexpect.EOF)<br>print child.isalive()</pre>
|
||||
</blockquote>
|
||||
<p>But if there is any delay before the call to <span class="code">isalive()</span>
|
||||
then it will always return 0 as expected.</p>
|
||||
<blockquote>
|
||||
<pre class="code">child = pexpect.spawn('ls')<br>child.expect(pexpect.EOF)<br>time.sleep(0.1)<br>print child.isalive()</pre>
|
||||
</blockquote>
|
||||
|
||||
<h2>Truncated output just before child exits</h2>
|
||||
<p><i>So far I have seen this only on older versions of <b>Apple's MacOS X</b>.</i>
|
||||
If the child application quits it may not flush its output buffer. This
|
||||
means that your Pexpect application will receive an EOF even though it
|
||||
should have received a little more data before the child died. This is
|
||||
not generally a problem when talking to interactive child applications.
|
||||
One example where it is a problem is when trying to read output from a
|
||||
program like '<span class="code">ls</span>'. You may receive most of
|
||||
the directory listing, but the last few lines will get lost before you
|
||||
receive an EOF. The reason for this is that '<span class="code">ls</span>'
|
||||
runs; completes its task; and then exits. The buffer is not flushed
|
||||
before exit so the last few lines are lost. The following example
|
||||
demonstrates the problem:</p>
|
||||
<p> </p>
|
||||
<blockquote>
|
||||
<pre class="code">child = pexpect.spawn ('ls -l')<br>child.expect (pexpect.EOF)<br>print child.before <br> </pre>
|
||||
</blockquote>
|
||||
<p></p>
|
||||
|
||||
<h2>Controlling SSH on Solaris</h2>
|
||||
<p>Pexpect does not yet work perfectly on Solaris.
|
||||
One common problem is that SSH sometimes will not allow TTY password
|
||||
authentication. For example, you may expect SSH to ask you for a
|
||||
password using code like this:
|
||||
</p>
|
||||
<pre class="code">child = pexpect.spawn ('ssh user@example.com')<br>child.expect ('assword')<br>child.sendline ('mypassword')<br></pre>
|
||||
You may see the following error come back from a spawned
|
||||
child SSH:
|
||||
<p></p>
|
||||
<blockquote>Permission denied (publickey,keyboard-interactive). </blockquote>
|
||||
<p>
|
||||
This means that SSH thinks it can't access the TTY to ask you for your
|
||||
password.
|
||||
The only solution I have found is to use public key authentication with
|
||||
SSH.
|
||||
This bypasses the need for a password. I'm not happy with this
|
||||
solution.
|
||||
The problem is due to poor support for Solaris Pseudo TTYs in the
|
||||
Python
|
||||
Standard Library. </p>
|
||||
<hr noshade="noshade" size="1">
|
||||
<h1><a name="changes"></a>CHANGES</h1>
|
||||
<h2>Current Release</h2>
|
||||
<p>Fixed OSError exception when a pexpect object is cleaned up.
|
||||
Previously you might have seen this exception:</p>
|
||||
<blockquote>
|
||||
<pre class="code">Exception exceptions.OSError: (10, 'No child processes') <br>in <bound method spawn.__del__ of<br><pexpect.spawn instance at 0xd248c>> ignored</pre>
|
||||
</blockquote>
|
||||
<p>You should not see that anymore. Thanks to Michael Surette.</p>
|
||||
<p>Added support for buffering reads. This greatly improves speed when
|
||||
trying to match long output from a child process. When you create an
|
||||
instance of the spawn object you can then set a buffer size. For now
|
||||
you MUST do the following to turn on buffering -- it may be on by
|
||||
default in future version.</p>
|
||||
<blockquote>
|
||||
<pre class="code">child = pexpect.spawn ('my_command')<br>child.maxread=1000 # Sets buffer to 1000 characters.</pre>
|
||||
</blockquote>
|
||||
<div>
|
||||
<p>I made a subtle change to the way TIMEOUT and EOF exceptions behave.
|
||||
Previously you could either expect these states in which case pexpect
|
||||
will not raise an exception, or you could just let pexpect raise an
|
||||
exception when these states were encountered. If you expected the
|
||||
states then the 'before' property was set to everything before the
|
||||
state was encountered, but if you let pexpect raise the exception then
|
||||
'before' was not set. Now the 'before' property will get set either way
|
||||
you choose to handle these states.</p>
|
||||
<h2><i>Older changes...</i></h2>
|
||||
<p>The spawn object now provides iterators for a <i>file-like interface</i>.
|
||||
This makes Pexpect a more complete file-like object. You can now write
|
||||
code like this:</p>
|
||||
<blockquote>
|
||||
<pre class="code">child = pexpect.spawn ('ls -l')<br>for line in child:<br> print line<br></pre>
|
||||
</blockquote>
|
||||
<p>I added the attribute <span class="code">exitstatus</span>. This
|
||||
will give the exit code returned by the child process. This will be set
|
||||
to <span class="code">None</span> while the child is still alive. When
|
||||
<span class="code">isalive()</span> returns 0 then <span class="code">exitstatus</span>
|
||||
will be set.</p>
|
||||
<p>I made a few more tweaks to <span class="code">isalive()</span> so
|
||||
that it will operate more consistently on different platforms. Solaris
|
||||
is the most difficult to support.</p>
|
||||
<p> </p>
|
||||
<p>You can now put <span class="code">TIMEOUT</span> in a list of
|
||||
expected patterns. This is just like putting <span class="code">EOF</span>
|
||||
in the pattern list. Expecting for a <span class="code">TIMEOUT</span>
|
||||
may not be used as often as <span class="code">EOF</span>, but this
|
||||
makes Pexpect more consitent.</p>
|
||||
<p>Thanks to a suggestion and sample code from Chad J. Schroeder I
|
||||
added the ability for Pexpect to operate on a file descriptor that is
|
||||
already open. This means that Pexpect can be used to control streams
|
||||
such as those from serial port devices. Now you just pass the integer
|
||||
file descriptor as the "command" when contsructing a spawn open. For
|
||||
example on a Linux box with a modem on ttyS1:</p>
|
||||
<blockquote>
|
||||
<pre class="code">fd = os.open("/dev/ttyS1", os.O_RDWR|os.O_NONBLOCK|os.O_NOCTTY)<br>m = pexpect.spawn(fd) # Note integer fd is used instead of usual string.<br>m.send("+++") # Escape sequence<br>m.send("ATZ0\r") # Reset modem to profile 0<br>rval = m.expect(["OK", "ERROR"])</pre>
|
||||
</blockquote>
|
||||
<h3>Pexpect now tests itself on Compile Farm!</h3>
|
||||
<p>I wrote a nice script that uses ssh to connect to each machine on
|
||||
Source Forge's Compile Farm and then run the testall.py script for each
|
||||
platform. The result of the test is then recorded for each platform.
|
||||
Now it's easy to run regression tests across multiple platforms.</p>
|
||||
<h3>Pexpect is a file-like object</h3>
|
||||
<p>The spawn object now provides a <i>file-like interface</i>. It
|
||||
supports most of the methods and attributes defined for Python File
|
||||
Objects. </p>
|
||||
<p>I changed write and writelines() so that they no longer return a
|
||||
value. Use send() if you need that functionality. I did this to make
|
||||
the Spawn object more closely match a file-like object.</p>
|
||||
<p>read() was renamed to read_nonblocking(). I added a new read()
|
||||
method that matches file-like object interface. In general, you should
|
||||
not notice the difference except that read() no longer allows you to
|
||||
directly set the timeout value. I hope this will not effect any
|
||||
existing code. Switching to read_nonblocking() should fix existing code.</p>
|
||||
<p>I changed the name of <span class="code">set_echo()</span> to <span
|
||||
class="code">setecho()</span>.</p>
|
||||
<p>I changed the name of <span class="code">send_eof()</span> to <span
|
||||
class="code">sendeof()</span>.</p>
|
||||
<p>I modified <span class="code">kill()</span> so that it checks to
|
||||
make sure the pid isalive().</p>
|
||||
<p>I modified <span class="code">spawn()</span> (really called from <span
|
||||
class="code">__spawn()</span>)so that it does not raise an expection
|
||||
if <span class="code">setwinsize()</span> 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.</p>
|
||||
<p>Added a method <span class="code">close()</span> that does what you
|
||||
think. It closes the file descriptor of the child application. It makes
|
||||
no attempt to actually kill the child or wait for its status. </p>
|
||||
<p>Add variables <span class="code">__version__</span> and <span
|
||||
class="code">__revision__</span> (from cvs) to the pexpect modules.
|
||||
This is mainly helpful to me so that I can make sure that I'm testing
|
||||
with the right version instead of one already installed.</p>
|
||||
<h3>Logging changes</h3>
|
||||
<blockquote>
|
||||
<p><span class="code">log_open()</span> and <span class="code">log_close()</span>
|
||||
have been removed. Now use <span class="code">setlog()</span>. The <span
|
||||
class="code">setlog()</span> method takes a file object. This is far
|
||||
more flexible than the previous log method. Each time data is written
|
||||
to the file object it will be flushed. To turn logging off simply call <span
|
||||
class="code">setlog()</span> with None.</p>
|
||||
</blockquote>
|
||||
<h2>isalive changes</h2>
|
||||
<blockquote>
|
||||
<p>I renamed the <span class="code">isAlive()</span> method to <span
|
||||
class="code">isalive()</span> to match the more typical naming style
|
||||
in Python. Also the technique used to detect child process status has
|
||||
been drastically modified. Previously I did some funky stuff with
|
||||
signals which caused indigestion in other Python modules on some
|
||||
platforms. It's was a big headache. It still is, but I think it works
|
||||
better now.</p>
|
||||
</blockquote>
|
||||
<h3>attribute name changes</h3>
|
||||
<blockquote>
|
||||
<p>The names of some attributes have been changed. This effects the
|
||||
names of the attributes that are set after called the <span
|
||||
class="code">expect()</span> method.</p>
|
||||
<table class="pymenu" border="0" cellpadding="5">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="pymenu">NEW NAME</th>
|
||||
<th class="pymenu">OLD NAME</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="code">before</span><br>
|
||||
<i>Everything before the match.</i></td>
|
||||
<td><span class="code">before</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="code">after</span><br>
|
||||
<i>Everything after and including the first character of the
|
||||
match</i></td>
|
||||
<td><span class="code">matched</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="code">match</span><br>
|
||||
<i>This is the re MatchObject from the match.<br>
|
||||
You can get groups() from this.<br>
|
||||
See '<span class="code">uptime.py</span>' in the examples tar ball.</i></td>
|
||||
<td><i>New -- Did not exist</i></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</blockquote>
|
||||
<h3>EOF changes</h3>
|
||||
<blockquote>
|
||||
<p>The <span class="code">expect_eof()</span> method is gone. You
|
||||
can now simply use the <span class="code">expect()</span> method to
|
||||
look for EOF.</p>
|
||||
<p>Was:</p>
|
||||
<blockquote>
|
||||
<p><span class="code">p.expect_eof ()</span></p>
|
||||
</blockquote>
|
||||
<p>Now:</p>
|
||||
<blockquote>
|
||||
<p><span class="code">p.expect (pexpect.EOF)</span></p>
|
||||
</blockquote>
|
||||
</blockquote>
|
||||
<hr noshade="noshade" size="1">
|
||||
<h1><a name="testing"></a>TESTING</h1>
|
||||
<p>The following platforms have been tested:</p>
|
||||
<!--
|
||||
<table class="pymenu" border="0" cellpadding="5">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="pymenu">PLATFORM</th>
|
||||
<th class="pymenu">RESULTS</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Linux 2.4.9-ac10-rmk2-np1-cerf2<br>
|
||||
armv4l</td>
|
||||
<td><b><i>all tests passed</i></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Linux 2.4.18 #2<br>
|
||||
sparc64</td>
|
||||
<td><b><i>all tests passed</i></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MacOS X Darwin Kernel Version 5.5<br>
|
||||
powerpc</td>
|
||||
<td>
|
||||
<p>failed more than one test.</p>
|
||||
<p>Generally Pexpect works on OS X, but the nature of the quirks
|
||||
cause a many of the tests to fail. See <a href="#bugs">bugs</a>
|
||||
(Incomplete Child Output). The problem is more than minor, but Pexpect
|
||||
is still more than useful for most tasks. The problem is an edge case.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Linux 2.2.20<br>
|
||||
alpha<br>
|
||||
</td>
|
||||
<td><b><i>all tests passed</i></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Linux 2.4.18-5smp<br>
|
||||
i686</td>
|
||||
<td><b><i>all tests passed</i></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>OpenBSD 2.9 GENERIC#653<br>
|
||||
i386</td>
|
||||
<td><b><i>all tests passed</i></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Solaris</td>
|
||||
<td>
|
||||
<p>failed <span class="code">test_destructor</span></p>
|
||||
<p>Otherwise, this is working pretty well. The destructor problem
|
||||
is minor. For some reason, the <i>second</i> time a pty file
|
||||
descriptor is created and deleted it never gets returned for use. It
|
||||
does not effect the first time or the third time or any time after
|
||||
that. It's only the second time. This is weird... This could be a file
|
||||
descriptor leak, or it could be some peculiarity of how Solaris
|
||||
recycles them. I thought it was a UNIX requirement for the OS to give
|
||||
you the lowest available filedescriptor number. In any case, this
|
||||
should not be a problem unless you create hundreds of pexpect
|
||||
instances... It may also be a pty module bug. </p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Windows XP Cygwin</td>
|
||||
<td>failed <span class="code">test_destructor</span>. That it
|
||||
works at all is amazing to me. Cygwin rules!</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
-->
|
||||
<h1> </h1>
|
||||
<h1><a name="todo">TO DO</a></h1>
|
||||
<p>Add an option to add a delay after each expect() or before each
|
||||
read()/readline() call to automatically avoid the <a href="#echo_bug">echo
|
||||
bug</a>.</p>
|
||||
<p> </p>
|
||||
</div>
|
||||
<hr noshade="noshade" size="1">
|
||||
<table border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td> <a href="http://www.noah.org/email/"><img src="email.png"
|
||||
alt="Click to send email." border="0" height="16" width="100"></a> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div id="Menu"><b>INDEX</b><br>
|
||||
<hr noshade="noshade" size="1"> <a href="#license"
|
||||
title="Python Software Foundation License">License</a><br>
|
||||
<a href="#download" title="Download and setup instructions">Download</a><br>
|
||||
<a href="#doc" title="Documentation and overview">Documentation</a><br>
|
||||
<a href="#status" title="Project Status">Project Status</a><br>
|
||||
<a href="#requirements" title="System requirements to use Pexpect">Requirements</a><br>
|
||||
<a href="#overview" title="Overview of what Pexpect does">Overview</a><br>
|
||||
<a href="#faq" title="FAQ">FAQ</a><br>
|
||||
<a href="#bugs" title="Bugs and work-arounds">Known Bugs</a><br>
|
||||
<a href="#changes" title="What's new with Pexpect">Recent Changes</a><br>
|
||||
<a href="#testing" title="Test results on various platforms">Testing</a><br>
|
||||
<a href="#todo" title="What to do next">To do</a><br>
|
||||
<a href="http://pexpect.svn.sourceforge.net/viewvc/pexpect/trunk/pexpect/" title="browse SVN">Browse SVN</a><br>
|
||||
<br>
|
||||
<a href="http://sourceforge.net/projects/pexpect/"
|
||||
title="The Pexpect project page on SourceForge.net"> <img
|
||||
src="http://sourceforge.net/sflogo.php?group_id=59762&type=5"
|
||||
alt="The Pexpect project page on SourceForge.net" border="0"
|
||||
height="31" width="105"> </a> </div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,85 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""This runs Apache Status on the remote host and returns the number of requests per second.
|
||||
|
||||
./astat.py [-s server_hostname] [-u username] [-p password]
|
||||
-s : hostname of the remote server to login to.
|
||||
-u : username to user for login.
|
||||
-p : Password to user for login.
|
||||
|
||||
Example:
|
||||
This will print information about the given host:
|
||||
./astat.py -s www.example.com -u mylogin -p mypassword
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import re
|
||||
import getopt
|
||||
import getpass
|
||||
import traceback
|
||||
import pexpect
|
||||
import pxssh
|
||||
|
||||
|
||||
def exit_with_usage():
|
||||
|
||||
print globals()['__doc__']
|
||||
os._exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
######################################################################
|
||||
# Parse the options, arguments, get ready, etc.
|
||||
######################################################################
|
||||
try:
|
||||
optlist, args = getopt.getopt(
|
||||
sys.argv[
|
||||
1:], 'h?s:u:p:', [
|
||||
'help', 'h', '?'])
|
||||
except Exception as e:
|
||||
print str(e)
|
||||
exit_with_usage()
|
||||
options = dict(optlist)
|
||||
if len(args) > 1:
|
||||
exit_with_usage()
|
||||
|
||||
if [elem for elem in options if elem in [
|
||||
'-h', '--h', '-?', '--?', '--help']]:
|
||||
print "Help:"
|
||||
exit_with_usage()
|
||||
|
||||
if '-s' in options:
|
||||
hostname = options['-s']
|
||||
else:
|
||||
hostname = raw_input('hostname: ')
|
||||
if '-u' in options:
|
||||
username = options['-u']
|
||||
else:
|
||||
username = raw_input('username: ')
|
||||
if '-p' in options:
|
||||
password = options['-p']
|
||||
else:
|
||||
password = getpass.getpass('password: ')
|
||||
|
||||
#
|
||||
# Login via SSH
|
||||
#
|
||||
p = pxssh.pxssh()
|
||||
p.login(hostname, username, password)
|
||||
p.sendline('apachectl status')
|
||||
p.expect('([0-9]+\.[0-9]+)\s*requests/sec')
|
||||
requests_per_second = p.match.groups()[0]
|
||||
p.logout()
|
||||
print requests_per_second
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception as e:
|
||||
print str(e)
|
||||
traceback.print_exc()
|
||||
os._exit(1)
|
|
@ -1,40 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""This is a very simple client for the backdoor daemon. This is intended more
|
||||
for testing rather than normal use. See bd_serv.py """
|
||||
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
import select
|
||||
|
||||
|
||||
def recv_wrapper(s):
|
||||
r, w, e = select.select([s.fileno()], [], [], 2)
|
||||
if not r:
|
||||
return ''
|
||||
#cols = int(s.recv(4))
|
||||
#rows = int(s.recv(4))
|
||||
cols = 80
|
||||
rows = 24
|
||||
packet_size = cols * rows * 2 # double it for good measure
|
||||
return s.recv(packet_size)
|
||||
|
||||
# HOST = '' #'localhost' # The remote host
|
||||
# PORT = 1664 # The same port as used by the server
|
||||
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
s.connect(sys.argv[1]) # (HOST, PORT))
|
||||
time.sleep(1)
|
||||
# s.setblocking(0)
|
||||
#s.send('COMMAND' + '\x01' + sys.argv[1])
|
||||
s.send(':sendline ' + sys.argv[2])
|
||||
print recv_wrapper(s)
|
||||
s.close()
|
||||
sys.exit()
|
||||
# while True:
|
||||
# data = recv_wrapper(s)
|
||||
# if data == '':
|
||||
# break
|
||||
# sys.stdout.write (data)
|
||||
# sys.stdout.flush()
|
||||
# s.close()
|
|
@ -1,339 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""Back door shell server
|
||||
|
||||
This exposes an shell terminal on a socket.
|
||||
|
||||
--hostname : sets the remote host name to open an ssh connection to.
|
||||
--username : sets the user name to login with
|
||||
--password : (optional) sets the password to login with
|
||||
--port : set the local port for the server to listen on
|
||||
--watch : show the virtual screen after each client request
|
||||
"""
|
||||
|
||||
# Having the password on the command line is not a good idea, but
|
||||
# then this entire project is probably not the most security concious thing
|
||||
# I've ever built. This should be considered an experimental tool -- at best.
|
||||
import pxssh
|
||||
import pexpect
|
||||
import ANSI
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
import getopt
|
||||
import getpass
|
||||
import traceback
|
||||
import threading
|
||||
import socket
|
||||
|
||||
|
||||
def exit_with_usage(exit_code=1):
|
||||
|
||||
print globals()['__doc__']
|
||||
os._exit(exit_code)
|
||||
|
||||
|
||||
class roller (threading.Thread):
|
||||
|
||||
"""This runs a function in a loop in a thread."""
|
||||
|
||||
def __init__(self, interval, function, args=[], kwargs={}):
|
||||
"""The interval parameter defines time between each call to the function.
|
||||
"""
|
||||
|
||||
threading.Thread.__init__(self)
|
||||
self.interval = interval
|
||||
self.function = function
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.finished = threading.Event()
|
||||
|
||||
def cancel(self):
|
||||
"""Stop the roller."""
|
||||
|
||||
self.finished.set()
|
||||
|
||||
def run(self):
|
||||
|
||||
while not self.finished.isSet():
|
||||
# self.finished.wait(self.interval)
|
||||
self.function(*self.args, **self.kwargs)
|
||||
|
||||
|
||||
def endless_poll(child, prompt, screen, refresh_timeout=0.1):
|
||||
"""This keeps the screen updated with the output of the child. This runs in
|
||||
a separate thread. See roller(). """
|
||||
|
||||
#child.logfile_read = screen
|
||||
try:
|
||||
s = child.read_nonblocking(4000, 0.1)
|
||||
screen.write(s)
|
||||
except:
|
||||
pass
|
||||
# while True:
|
||||
# #child.prompt (timeout=refresh_timeout)
|
||||
# try:
|
||||
# #child.read_nonblocking(1,timeout=refresh_timeout)
|
||||
# child.read_nonblocking(4000, 0.1)
|
||||
# except:
|
||||
# pass
|
||||
|
||||
|
||||
def daemonize(stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
|
||||
'''This forks the current process into a daemon. Almost none of this is
|
||||
necessary (or advisable) if your daemon is being started by inetd. In that
|
||||
case, stdin, stdout and stderr are all set up for you to refer to the
|
||||
network connection, and the fork()s and session manipulation should not be
|
||||
done (to avoid confusing inetd). Only the chdir() and umask() steps remain
|
||||
as useful.
|
||||
|
||||
References:
|
||||
UNIX Programming FAQ
|
||||
1.7 How do I get my program to act like a daemon?
|
||||
http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
|
||||
|
||||
Advanced Programming in the Unix Environment
|
||||
W. Richard Stevens, 1992, Addison-Wesley, ISBN 0-201-56317-7.
|
||||
|
||||
The stdin, stdout, and stderr arguments are file names that will be opened
|
||||
and be used to replace the standard file descriptors in sys.stdin,
|
||||
sys.stdout, and sys.stderr. These arguments are optional and default to
|
||||
/dev/null. Note that stderr is opened unbuffered, so if it shares a file
|
||||
with stdout then interleaved output may not appear in the order that you
|
||||
expect. '''
|
||||
|
||||
# Do first fork.
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
sys.exit(0) # Exit first parent.
|
||||
except OSError as e:
|
||||
sys.stderr.write("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror))
|
||||
sys.exit(1)
|
||||
|
||||
# Decouple from parent environment.
|
||||
os.chdir("/")
|
||||
os.umask(0)
|
||||
os.setsid()
|
||||
|
||||
# Do second fork.
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
sys.exit(0) # Exit second parent.
|
||||
except OSError as e:
|
||||
sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
|
||||
sys.exit(1)
|
||||
|
||||
# Now I am a daemon!
|
||||
|
||||
# Redirect standard file descriptors.
|
||||
si = open(stdin, 'r')
|
||||
so = open(stdout, 'a+')
|
||||
se = open(stderr, 'a+', 0)
|
||||
os.dup2(si.fileno(), sys.stdin.fileno())
|
||||
os.dup2(so.fileno(), sys.stdout.fileno())
|
||||
os.dup2(se.fileno(), sys.stderr.fileno())
|
||||
|
||||
# I now return as the daemon
|
||||
return 0
|
||||
|
||||
|
||||
def add_cursor_blink(response, row, col):
|
||||
|
||||
i = (row - 1) * 80 + col
|
||||
return response[:i] + \
|
||||
'<img src="http://www.noah.org/cursor.gif">' + response[i:]
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
try:
|
||||
optlist, args = getopt.getopt(
|
||||
sys.argv[
|
||||
1:], 'h?d', [
|
||||
'help', 'h', '?', 'hostname=', 'username=', 'password=', 'port=', 'watch'])
|
||||
except Exception as e:
|
||||
print str(e)
|
||||
exit_with_usage()
|
||||
|
||||
command_line_options = dict(optlist)
|
||||
options = dict(optlist)
|
||||
# There are a million ways to cry for help. These are but a few of them.
|
||||
if [elem for elem in command_line_options if elem in [
|
||||
'-h', '--h', '-?', '--?', '--help']]:
|
||||
exit_with_usage(0)
|
||||
|
||||
hostname = "127.0.0.1"
|
||||
port = 1664
|
||||
username = os.getenv('USER')
|
||||
password = ""
|
||||
daemon_mode = False
|
||||
if '-d' in options:
|
||||
daemon_mode = True
|
||||
if '--watch' in options:
|
||||
watch_mode = True
|
||||
else:
|
||||
watch_mode = False
|
||||
if '--hostname' in options:
|
||||
hostname = options['--hostname']
|
||||
if '--port' in options:
|
||||
port = int(options['--port'])
|
||||
if '--username' in options:
|
||||
username = options['--username']
|
||||
print "Login for %s@%s:%s" % (username, hostname, port)
|
||||
if '--password' in options:
|
||||
password = options['--password']
|
||||
else:
|
||||
password = getpass.getpass('password: ')
|
||||
|
||||
if daemon_mode:
|
||||
print "daemonizing server"
|
||||
daemonize()
|
||||
# daemonize('/dev/null','/tmp/daemon.log','/tmp/daemon.log')
|
||||
|
||||
sys.stdout.write('server started with pid %d\n' % os.getpid())
|
||||
|
||||
virtual_screen = ANSI.ANSI(24, 80)
|
||||
child = pxssh.pxssh()
|
||||
child.login(hostname, username, password)
|
||||
print 'created shell. command line prompt is', child.PROMPT
|
||||
#child.sendline ('stty -echo')
|
||||
# child.setecho(False)
|
||||
virtual_screen.write(child.before)
|
||||
virtual_screen.write(child.after)
|
||||
|
||||
if os.path.exists("/tmp/mysock"):
|
||||
os.remove("/tmp/mysock")
|
||||
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
localhost = '127.0.0.1'
|
||||
s.bind('/tmp/mysock')
|
||||
os.chmod('/tmp/mysock', 0o777)
|
||||
print 'Listen'
|
||||
s.listen(1)
|
||||
print 'Accept'
|
||||
#s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
#localhost = '127.0.0.1'
|
||||
#s.bind((localhost, port))
|
||||
# print 'Listen'
|
||||
# s.listen(1)
|
||||
|
||||
r = roller(0.01, endless_poll, (child, child.PROMPT, virtual_screen))
|
||||
r.start()
|
||||
print "screen poll updater started in background thread"
|
||||
sys.stdout.flush()
|
||||
|
||||
try:
|
||||
while True:
|
||||
conn, addr = s.accept()
|
||||
print 'Connected by', addr
|
||||
data = conn.recv(1024)
|
||||
if data[0] != ':':
|
||||
cmd = ':sendline'
|
||||
arg = data.strip()
|
||||
else:
|
||||
request = data.split(' ', 1)
|
||||
if len(request) > 1:
|
||||
cmd = request[0].strip()
|
||||
arg = request[1].strip()
|
||||
else:
|
||||
cmd = request[0].strip()
|
||||
if cmd == ':exit':
|
||||
r.cancel()
|
||||
break
|
||||
elif cmd == ':sendline':
|
||||
child.sendline(arg)
|
||||
# child.prompt(timeout=2)
|
||||
time.sleep(0.2)
|
||||
shell_window = str(virtual_screen)
|
||||
elif cmd == ':send' or cmd == ':xsend':
|
||||
if cmd == ':xsend':
|
||||
arg = arg.decode("hex")
|
||||
child.send(arg)
|
||||
time.sleep(0.2)
|
||||
shell_window = str(virtual_screen)
|
||||
elif cmd == ':cursor':
|
||||
shell_window = '%x%x' % (
|
||||
virtual_screen.cur_r, virtual_screen.cur_c)
|
||||
elif cmd == ':refresh':
|
||||
shell_window = str(virtual_screen)
|
||||
|
||||
response = []
|
||||
response.append(shell_window)
|
||||
#response = add_cursor_blink (response, row, col)
|
||||
sent = conn.send('\n'.join(response))
|
||||
if watch_mode:
|
||||
print '\n'.join(response)
|
||||
if sent < len(response):
|
||||
print "Sent is too short. Some data was cut off."
|
||||
conn.close()
|
||||
finally:
|
||||
r.cancel()
|
||||
print "cleaning up socket"
|
||||
s.close()
|
||||
if os.path.exists("/tmp/mysock"):
|
||||
os.remove("/tmp/mysock")
|
||||
print "done!"
|
||||
|
||||
|
||||
def pretty_box(rows, cols, s):
|
||||
"""This puts an ASCII text box around the given string, s.
|
||||
"""
|
||||
|
||||
top_bot = '+' + '-' * cols + '+\n'
|
||||
return top_bot + \
|
||||
'\n'.join(['|' + line + '|' for line in s.split('\n')]) + '\n' + top_bot
|
||||
|
||||
|
||||
def error_response(msg):
|
||||
|
||||
response = []
|
||||
response.append ("""All commands start with :
|
||||
:{REQUEST} {ARGUMENT}
|
||||
{REQUEST} may be one of the following:
|
||||
:sendline: Run the ARGUMENT followed by a line feed.
|
||||
:send : send the characters in the ARGUMENT without a line feed.
|
||||
:refresh : Use to catch up the screen with the shell if state gets out of sync.
|
||||
Example:
|
||||
:sendline ls -l
|
||||
You may also leave off :command and it will be assumed.
|
||||
Example:
|
||||
ls -l
|
||||
is equivalent to:
|
||||
:sendline ls -l
|
||||
""")
|
||||
response.append(msg)
|
||||
return '\n'.join(response)
|
||||
|
||||
|
||||
def parse_host_connect_string(hcs):
|
||||
"""This parses a host connection string in the form
|
||||
username:password@hostname:port. All fields are options expcet hostname. A
|
||||
dictionary is returned with all four keys. Keys that were not included are
|
||||
set to empty strings ''. Note that if your password has the '@' character
|
||||
then you must backslash escape it. """
|
||||
|
||||
if '@' in hcs:
|
||||
p = re.compile(
|
||||
r'(?P<username>[^@:]*)(:?)(?P<password>.*)(?!\\)@(?P<hostname>[^:]*):?(?P<port>[0-9]*)')
|
||||
else:
|
||||
p = re.compile(
|
||||
r'(?P<username>)(?P<password>)(?P<hostname>[^:]*):?(?P<port>[0-9]*)')
|
||||
m = p.search(hcs)
|
||||
d = m.groupdict()
|
||||
d['password'] = d['password'].replace('\\@', '@')
|
||||
return d
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
try:
|
||||
start_time = time.time()
|
||||
print time.asctime()
|
||||
main()
|
||||
print time.asctime()
|
||||
print "TOTAL TIME IN MINUTES:",
|
||||
print (time.time() - start_time) / 60.0
|
||||
except Exception as e:
|
||||
print str(e)
|
||||
tb_dump = traceback.format_exc()
|
||||
print str(tb_dump)
|
|
@ -1,132 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
'''This demonstrates controlling a screen oriented application (curses).
|
||||
It starts two instances of gnuchess and then pits them against each other.
|
||||
'''
|
||||
|
||||
import pexpect
|
||||
import string
|
||||
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)'
|
||||
|
||||
|
||||
class Chess:
|
||||
|
||||
def __init__(self, engine="/usr/local/bin/gnuchess -a -h 1"):
|
||||
self.child = pexpect.spawn(engine)
|
||||
self.term = ANSI.ANSI()
|
||||
|
||||
self.child.expect('Chess')
|
||||
if self.child.after != 'Chess':
|
||||
raise IOError, 'incompatible chess program'
|
||||
self.term.process_list(self.before)
|
||||
self.term.process_list(self.after)
|
||||
self.last_computer_move = ''
|
||||
|
||||
def read_until_cursor(self, r, c)
|
||||
while 1:
|
||||
self.child.read(1, 60)
|
||||
self.term.process(c)
|
||||
if self.term.cur_r == r and self.term.cur_c == c:
|
||||
return 1
|
||||
|
||||
def do_first_move(self, move):
|
||||
self.child.expect('Your move is')
|
||||
self.child.sendline(move)
|
||||
self.term.process_list(self.before)
|
||||
self.term.process_list(self.after)
|
||||
return move
|
||||
|
||||
def do_move(self, move):
|
||||
read_until_cursor(19, 60)
|
||||
#self.child.expect ('\[19;60H')
|
||||
self.child.sendline(move)
|
||||
print 'do_move' move
|
||||
return move
|
||||
|
||||
def get_first_computer_move(self):
|
||||
self.child.expect('My move is')
|
||||
self.child.expect(REGEX_MOVE)
|
||||
# print '', self.child.after
|
||||
return self.child.after
|
||||
|
||||
def get_computer_move(self):
|
||||
print 'Here'
|
||||
i = self.child.expect(['\[17;59H', '\[17;58H'])
|
||||
print i
|
||||
if i == 0:
|
||||
self.child.expect(REGEX_MOVE)
|
||||
if len(self.child.after) < 4:
|
||||
self.child.after = self.child.after + \
|
||||
self.last_computer_move[3]
|
||||
if i == 1:
|
||||
self.child.expect(REGEX_MOVE_PART)
|
||||
self.child.after = self.last_computer_move[0] + self.child.after
|
||||
print '', self.child.after
|
||||
self.last_computer_move = self.child.after
|
||||
return self.child.after
|
||||
|
||||
def switch(self):
|
||||
self.child.sendline('switch')
|
||||
|
||||
def set_depth(self, depth):
|
||||
self.child.sendline('depth')
|
||||
self.child.expect('depth=')
|
||||
self.child.sendline('%d' % depth)
|
||||
|
||||
def quit(self):
|
||||
self.child.sendline('quit')
|
||||
import sys
|
||||
import os
|
||||
print 'Starting...'
|
||||
white = Chess()
|
||||
white.child.echo = 1
|
||||
white.child.expect('Your move is')
|
||||
white.set_depth(2)
|
||||
white.switch()
|
||||
|
||||
move_white = white.get_first_computer_move()
|
||||
print 'first move white:', move_white
|
||||
|
||||
white.do_move('e7e5')
|
||||
move_white = white.get_computer_move()
|
||||
print 'move white:', move_white
|
||||
white.do_move('f8c5')
|
||||
move_white = white.get_computer_move()
|
||||
print 'move white:', move_white
|
||||
white.do_move('b8a6')
|
||||
move_white = white.get_computer_move()
|
||||
print 'move white:', move_white
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
black = Chess()
|
||||
white = Chess()
|
||||
white.child.expect('Your move is')
|
||||
white.switch()
|
||||
|
||||
move_white = white.get_first_computer_move()
|
||||
print 'first move white:', move_white
|
||||
|
||||
black.do_first_move(move_white)
|
||||
move_black = black.get_first_computer_move()
|
||||
print 'first move black:', move_black
|
||||
|
||||
white.do_move(move_black)
|
||||
|
||||
done = 0
|
||||
while not done:
|
||||
move_white = white.get_computer_move()
|
||||
print 'move white:', move_white
|
||||
|
||||
black.do_move(move_white)
|
||||
move_black = black.get_computer_move()
|
||||
print 'move black:', move_black
|
||||
|
||||
white.do_move(move_black)
|
||||
print 'tail of loop'
|
||||
|
||||
g.quit()
|
|
@ -1,135 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
'''This demonstrates controlling a screen oriented application (curses).
|
||||
It starts two instances of gnuchess and then pits them against each other.
|
||||
'''
|
||||
|
||||
import pexpect
|
||||
import string
|
||||
import ANSI
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
|
||||
class Chess:
|
||||
|
||||
def __init__(self, engine="/usr/local/bin/gnuchess -a -h 1"):
|
||||
self.child = pexpect.spawn(engine)
|
||||
self.term = ANSI.ANSI()
|
||||
|
||||
#self.child.expect ('Chess')
|
||||
# if self.child.after != 'Chess':
|
||||
# raise IOError, 'incompatible chess program'
|
||||
#self.term.process_list (self.child.before)
|
||||
#self.term.process_list (self.child.after)
|
||||
|
||||
self.last_computer_move = ''
|
||||
|
||||
def read_until_cursor(self, r, c, e=0):
|
||||
'''Eventually something like this should move into the screen class or
|
||||
a subclass. Maybe a combination of pexpect and screen...
|
||||
'''
|
||||
fout = open('log', 'a')
|
||||
while self.term.cur_r != r or self.term.cur_c != c:
|
||||
try:
|
||||
k = self.child.read(1, 10)
|
||||
except Exception as e:
|
||||
print 'EXCEPTION, (r,c):(%d,%d)\n' % (self.term.cur_r, self.term.cur_c)
|
||||
sys.stdout.flush()
|
||||
self.term.process(k)
|
||||
fout.write('(r,c):(%d,%d)\n' % (self.term.cur_r, self.term.cur_c))
|
||||
fout.flush()
|
||||
if e:
|
||||
sys.stdout.write(k)
|
||||
sys.stdout.flush()
|
||||
if self.term.cur_r == r and self.term.cur_c == c:
|
||||
fout.close()
|
||||
return 1
|
||||
print 'DIDNT EVEN HIT.'
|
||||
fout.close()
|
||||
return 1
|
||||
|
||||
def expect_region(self):
|
||||
'''This is another method that would be moved into the
|
||||
screen class.
|
||||
'''
|
||||
pass
|
||||
|
||||
def do_scan(self):
|
||||
fout = open('log', 'a')
|
||||
while True:
|
||||
c = self.child.read(1, 10)
|
||||
self.term.process(c)
|
||||
fout.write('(r,c):(%d,%d)\n' % (self.term.cur_r, self.term.cur_c))
|
||||
fout.flush()
|
||||
sys.stdout.write(c)
|
||||
sys.stdout.flush()
|
||||
|
||||
def do_move(self, move, e=0):
|
||||
time.sleep(1)
|
||||
self.read_until_cursor(19, 60, e)
|
||||
self.child.sendline(move)
|
||||
|
||||
def wait(self, color):
|
||||
while True:
|
||||
r = self.term.get_region(14, 50, 14, 60)[0]
|
||||
r = r.strip()
|
||||
if r == color:
|
||||
return
|
||||
time.sleep(1)
|
||||
|
||||
def parse_computer_move(self, s):
|
||||
i = s.find('is: ')
|
||||
cm = s[i + 3:i + 9]
|
||||
return cm
|
||||
|
||||
def get_computer_move(self, e=0):
|
||||
time.sleep(1)
|
||||
self.read_until_cursor(19, 60, e)
|
||||
time.sleep(1)
|
||||
r = self.term.get_region(17, 50, 17, 62)[0]
|
||||
cm = self.parse_computer_move(r)
|
||||
return cm
|
||||
|
||||
def switch(self):
|
||||
print 'switching'
|
||||
self.child.sendline('switch')
|
||||
|
||||
def set_depth(self, depth):
|
||||
self.child.sendline('depth')
|
||||
self.child.expect('depth=')
|
||||
self.child.sendline('%d' % depth)
|
||||
|
||||
def quit(self):
|
||||
self.child.sendline('quit')
|
||||
|
||||
|
||||
def LOG(s):
|
||||
print s
|
||||
sys.stdout.flush()
|
||||
fout = open('moves.log', 'a')
|
||||
fout.write(s + '\n')
|
||||
fout.close()
|
||||
|
||||
print 'Starting...'
|
||||
|
||||
black = Chess()
|
||||
white = Chess()
|
||||
white.read_until_cursor(19, 60, 1)
|
||||
white.switch()
|
||||
|
||||
done = 0
|
||||
while not done:
|
||||
white.wait('Black')
|
||||
move_white = white.get_computer_move(1)
|
||||
LOG('move white:' + move_white)
|
||||
|
||||
black.do_move(move_white)
|
||||
black.wait('White')
|
||||
move_black = black.get_computer_move()
|
||||
LOG('move black:' + move_black)
|
||||
|
||||
white.do_move(move_black, 1)
|
||||
|
||||
g.quit()
|
|
@ -1,139 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
'''This demonstrates controlling a screen oriented application (curses).
|
||||
It starts two instances of gnuchess and then pits them against each other.
|
||||
'''
|
||||
|
||||
import pexpect
|
||||
import string
|
||||
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)'
|
||||
|
||||
|
||||
class Chess:
|
||||
|
||||
def __init__(self, engine="/usr/local/bin/gnuchess -a -h 1"):
|
||||
self.child = pexpect.spawn(engine)
|
||||
self.term = ANSI.ANSI()
|
||||
|
||||
# self.child.expect ('Chess')
|
||||
# if self.child.after != 'Chess':
|
||||
# raise IOError, 'incompatible chess program'
|
||||
# self.term.process_list (self.before)
|
||||
# self.term.process_list (self.after)
|
||||
self.last_computer_move = ''
|
||||
|
||||
def read_until_cursor(self, r, c):
|
||||
fout = open('log', 'a')
|
||||
while True:
|
||||
k = self.child.read(1, 10)
|
||||
self.term.process(k)
|
||||
fout.write('(r,c):(%d,%d)\n' % (self.term.cur_r, self.term.cur_c))
|
||||
fout.flush()
|
||||
if self.term.cur_r == r and self.term.cur_c == c:
|
||||
fout.close()
|
||||
return 1
|
||||
sys.stdout.write(k)
|
||||
sys.stdout.flush()
|
||||
|
||||
def do_scan(self):
|
||||
fout = open('log', 'a')
|
||||
while True:
|
||||
c = self.child.read(1, 10)
|
||||
self.term.process(c)
|
||||
fout.write('(r,c):(%d,%d)\n' % (self.term.cur_r, self.term.cur_c))
|
||||
fout.flush()
|
||||
sys.stdout.write(c)
|
||||
sys.stdout.flush()
|
||||
|
||||
def do_move(self, move):
|
||||
self.read_until_cursor(19, 60)
|
||||
self.child.sendline(move)
|
||||
return move
|
||||
|
||||
def get_computer_move(self):
|
||||
print 'Here'
|
||||
i = self.child.expect(['\[17;59H', '\[17;58H'])
|
||||
print i
|
||||
if i == 0:
|
||||
self.child.expect(REGEX_MOVE)
|
||||
if len(self.child.after) < 4:
|
||||
self.child.after = self.child.after + \
|
||||
self.last_computer_move[3]
|
||||
if i == 1:
|
||||
self.child.expect(REGEX_MOVE_PART)
|
||||
self.child.after = self.last_computer_move[0] + self.child.after
|
||||
print '', self.child.after
|
||||
self.last_computer_move = self.child.after
|
||||
return self.child.after
|
||||
|
||||
def switch(self):
|
||||
self.child.sendline('switch')
|
||||
|
||||
def set_depth(self, depth):
|
||||
self.child.sendline('depth')
|
||||
self.child.expect('depth=')
|
||||
self.child.sendline('%d' % depth)
|
||||
|
||||
def quit(self):
|
||||
self.child.sendline('quit')
|
||||
import sys
|
||||
import os
|
||||
print 'Starting...'
|
||||
white = Chess()
|
||||
white.do_move('b2b4')
|
||||
white.read_until_cursor(19, 60)
|
||||
c1 = white.term.get_abs(17, 58)
|
||||
c2 = white.term.get_abs(17, 59)
|
||||
c3 = white.term.get_abs(17, 60)
|
||||
c4 = white.term.get_abs(17, 61)
|
||||
fout = open('log', 'a')
|
||||
fout.write('Computer:%s%s%s%s\n' % (c1, c2, c3, c4))
|
||||
fout.close()
|
||||
white.do_move('c2c4')
|
||||
white.read_until_cursor(19, 60)
|
||||
c1 = white.term.get_abs(17, 58)
|
||||
c2 = white.term.get_abs(17, 59)
|
||||
c3 = white.term.get_abs(17, 60)
|
||||
c4 = white.term.get_abs(17, 61)
|
||||
fout = open('log', 'a')
|
||||
fout.write('Computer:%s%s%s%s\n' % (c1, c2, c3, c4))
|
||||
fout.close()
|
||||
white.do_scan()
|
||||
|
||||
#white.do_move ('b8a6')
|
||||
#move_white = white.get_computer_move()
|
||||
# print 'move white:', move_white
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
black = Chess()
|
||||
white = Chess()
|
||||
white.child.expect('Your move is')
|
||||
white.switch()
|
||||
|
||||
move_white = white.get_first_computer_move()
|
||||
print 'first move white:', move_white
|
||||
|
||||
black.do_first_move(move_white)
|
||||
move_black = black.get_first_computer_move()
|
||||
print 'first move black:', move_black
|
||||
|
||||
white.do_move(move_black)
|
||||
|
||||
done = 0
|
||||
while not done:
|
||||
move_white = white.get_computer_move()
|
||||
print 'move white:', move_white
|
||||
|
||||
black.do_move(move_white)
|
||||
move_black = black.get_computer_move()
|
||||
print 'move black:', move_black
|
||||
|
||||
white.do_move(move_black)
|
||||
print 'tail of loop'
|
||||
|
||||
g.quit()
|
|
@ -1,33 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""This collects filesystem capacity info using the 'df' command. Tuples of
|
||||
filesystem name and percentage are stored in a list. A simple report is
|
||||
printed. Filesystems over 95% capacity are highlighted. Note that this does not
|
||||
parse filesystem names after the first space, so names with spaces in them will
|
||||
be truncated. This will produce ambiguous results for automount filesystems on
|
||||
Apple OSX. """
|
||||
|
||||
import pexpect
|
||||
|
||||
child = pexpect.spawn('df')
|
||||
|
||||
# parse 'df' output into a list.
|
||||
pattern = "\n(\S+).*?([0-9]+)%"
|
||||
filesystem_list = []
|
||||
for dummy in range(0, 1000):
|
||||
i = child.expect([pattern, pexpect.EOF])
|
||||
if i == 0:
|
||||
filesystem_list.append(child.match.groups())
|
||||
else:
|
||||
break
|
||||
|
||||
# Print report
|
||||
print
|
||||
for m in filesystem_list:
|
||||
s = "Filesystem %s is at %s%%" % (m[0], m[1])
|
||||
# highlight filesystems over 95% capacity
|
||||
if int(m[1]) > 95:
|
||||
s = '! ' + s
|
||||
else:
|
||||
s = ' ' + s
|
||||
print s
|
|
@ -1,98 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""This is for cleaning up binary files improperly added to CVS. This script
|
||||
scans the given path to find binary files; checks with CVS to see if the sticky
|
||||
options are set to -kb; finally if sticky options are not -kb then uses 'cvs
|
||||
admin' to set the -kb option.
|
||||
|
||||
This script ignores CVS directories, symbolic links, and files not known under
|
||||
CVS control (cvs status is 'Unknown').
|
||||
|
||||
Run this on a CHECKED OUT module sandbox, not on the repository itself. After
|
||||
if fixes the sticky options on any files you should manually do a 'cvs commit'
|
||||
to accept the changes. Then be sure to have all users do a 'cvs up -A' to
|
||||
update the Sticky Option status.
|
||||
|
||||
Noah Spurrier
|
||||
20030426
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import pexpect
|
||||
|
||||
VERBOSE = 1
|
||||
|
||||
|
||||
def is_binary(filename):
|
||||
"""Assume that any file with a character where the 8th bit is set is
|
||||
binary. """
|
||||
|
||||
fin = open(filename, 'rb')
|
||||
wholething = fin.read()
|
||||
fin.close()
|
||||
for c in wholething:
|
||||
if ord(c) & 0x80:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def is_kb_sticky(filename):
|
||||
"""This checks if 'cvs status' reports '-kb' for Sticky options. If the
|
||||
Sticky Option status is '-ks' then this returns 1. If the status is
|
||||
'Unknown' then it returns 1. Otherwise 0 is returned. """
|
||||
|
||||
try:
|
||||
s = pexpect.spawn('cvs status %s' % filename)
|
||||
i = s.expect(['Sticky Options:\s*(.*)\r\n', 'Status: Unknown'])
|
||||
if i == 1 and VERBOSE:
|
||||
print 'File not part of CVS repository:', filename
|
||||
return 1 # Pretend it's OK.
|
||||
if s.match.group(1) == '-kb':
|
||||
return 1
|
||||
s = None
|
||||
except:
|
||||
print 'Something went wrong trying to run external cvs command.'
|
||||
print ' cvs status %s' % filename
|
||||
print 'The cvs command returned:'
|
||||
print s.before
|
||||
return 0
|
||||
|
||||
|
||||
def cvs_admin_kb(filename):
|
||||
"""This uses 'cvs admin' to set the '-kb' sticky option. """
|
||||
|
||||
s = pexpect.run('cvs admin -kb %s' % filename)
|
||||
# There is a timing issue. If I run 'cvs admin' too quickly
|
||||
# cvs sometimes has trouble obtaining the directory lock.
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def walk_and_clean_cvs_binaries(arg, dirname, names):
|
||||
"""This contains the logic for processing files. This is the os.path.walk
|
||||
callback. This skips dirnames that end in CVS. """
|
||||
|
||||
if len(dirname) > 3 and dirname[-3:] == 'CVS':
|
||||
return
|
||||
for n in names:
|
||||
fullpath = os.path.join(dirname, n)
|
||||
if os.path.isdir(fullpath) or os.path.islink(fullpath):
|
||||
continue
|
||||
if is_binary(fullpath):
|
||||
if not is_kb_sticky(fullpath):
|
||||
if VERBOSE:
|
||||
print fullpath
|
||||
cvs_admin_kb(fullpath)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
root = '.'
|
||||
else:
|
||||
root = sys.argv[1]
|
||||
os.path.walk(root, walk_and_clean_cvs_binaries, None)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,47 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""This demonstrates an FTP "bookmark". This connects to an ftp site; does a
|
||||
few ftp stuff; and then gives the user interactive control over the session. In
|
||||
this case the "bookmark" is to a directory on the OpenBSD ftp server. It puts
|
||||
you in the i386 packages directory. You can easily modify this for other sites.
|
||||
"""
|
||||
|
||||
import pexpect
|
||||
import sys
|
||||
|
||||
child = pexpect.spawn('ftp ftp.openbsd.org')
|
||||
child.expect('(?i)name .*: ')
|
||||
child.sendline('anonymous')
|
||||
child.expect('(?i)password')
|
||||
child.sendline('pexpect@sourceforge.net')
|
||||
child.expect('ftp> ')
|
||||
child.sendline('cd /pub/OpenBSD/3.7/packages/i386')
|
||||
child.expect('ftp> ')
|
||||
child.sendline('bin')
|
||||
child.expect('ftp> ')
|
||||
child.sendline('prompt')
|
||||
child.expect('ftp> ')
|
||||
child.sendline('pwd')
|
||||
child.expect('ftp> ')
|
||||
print("Escape character is '^]'.\n")
|
||||
sys.stdout.write(child.after)
|
||||
sys.stdout.flush()
|
||||
child.interact() # Escape character defaults to ^]
|
||||
# At this point this script blocks until the user presses the escape character
|
||||
# or until the child exits. The human user and the child should be talking
|
||||
# to each other now.
|
||||
|
||||
# At this point the script is running again.
|
||||
print 'Left interactve mode.'
|
||||
|
||||
# The rest is not strictly necessary. This just demonstrates a few functions.
|
||||
# This makes sure the child is dead; although it would be killed when
|
||||
# Python exits.
|
||||
if child.isalive():
|
||||
child.sendline('bye') # Try to ask ftp child to exit.
|
||||
child.close()
|
||||
# Print the final state of the child. Normally isalive() should be FALSE.
|
||||
if child.isalive():
|
||||
print 'Child did not exit gracefully.'
|
||||
else:
|
||||
print 'Child exited gracefully.'
|
|
@ -1,222 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
""" This runs a sequence of commands on a remote host using SSH. It runs a
|
||||
simple system checks such as uptime and free to monitor the state of the remote
|
||||
host.
|
||||
|
||||
./monitor.py [-s server_hostname] [-u username] [-p password]
|
||||
-s : hostname of the remote server to login to.
|
||||
-u : username to user for login.
|
||||
-p : Password to user for login.
|
||||
|
||||
Example:
|
||||
This will print information about the given host:
|
||||
./monitor.py -s www.example.com -u mylogin -p mypassword
|
||||
|
||||
It works like this:
|
||||
Login via SSH (This is the hardest part).
|
||||
Run and parse 'uptime'.
|
||||
Run 'iostat'.
|
||||
Run 'vmstat'.
|
||||
Run 'netstat'
|
||||
Run 'free'.
|
||||
Exit the remote host.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import re
|
||||
import getopt
|
||||
import getpass
|
||||
import traceback
|
||||
import pexpect
|
||||
|
||||
#
|
||||
# Some constants.
|
||||
#
|
||||
# This is way too simple for industrial use -- we will change is ASAP.
|
||||
COMMAND_PROMPT = '[#$] '
|
||||
TERMINAL_PROMPT = '(?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'
|
||||
|
||||
|
||||
def exit_with_usage():
|
||||
|
||||
print globals()['__doc__']
|
||||
os._exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
global COMMAND_PROMPT, TERMINAL_PROMPT, TERMINAL_TYPE, SSH_NEWKEY
|
||||
######################################################################
|
||||
# Parse the options, arguments, get ready, etc.
|
||||
######################################################################
|
||||
try:
|
||||
optlist, args = getopt.getopt(
|
||||
sys.argv[
|
||||
1:], 'h?s:u:p:', [
|
||||
'help', 'h', '?'])
|
||||
except Exception as e:
|
||||
print str(e)
|
||||
exit_with_usage()
|
||||
options = dict(optlist)
|
||||
if len(args) > 1:
|
||||
exit_with_usage()
|
||||
|
||||
if [elem for elem in options if elem in [
|
||||
'-h', '--h', '-?', '--?', '--help']]:
|
||||
print "Help:"
|
||||
exit_with_usage()
|
||||
|
||||
if '-s' in options:
|
||||
host = options['-s']
|
||||
else:
|
||||
host = raw_input('hostname: ')
|
||||
if '-u' in options:
|
||||
user = options['-u']
|
||||
else:
|
||||
user = raw_input('username: ')
|
||||
if '-p' in options:
|
||||
password = options['-p']
|
||||
else:
|
||||
password = getpass.getpass('password: ')
|
||||
|
||||
#
|
||||
# Login via SSH
|
||||
#
|
||||
child = pexpect.spawn('ssh -l %s %s' % (user, host))
|
||||
i = child.expect([pexpect.TIMEOUT, SSH_NEWKEY,
|
||||
COMMAND_PROMPT, '(?i)password'])
|
||||
if i == 0: # Timeout
|
||||
print 'ERROR! could not login with SSH. Here is what SSH said:'
|
||||
print child.before, child.after
|
||||
print str(child)
|
||||
sys.exit(1)
|
||||
if i == 1: # In this case SSH does not have the public key cached.
|
||||
child.sendline('yes')
|
||||
child.expect('(?i)password')
|
||||
if i == 2:
|
||||
# This may happen if a public key was setup to automatically login.
|
||||
# But beware, the COMMAND_PROMPT at this point is very trivial and
|
||||
# could be fooled by some output in the MOTD or login message.
|
||||
pass
|
||||
if i == 3:
|
||||
child.sendline(password)
|
||||
# Now we are either at the command prompt or
|
||||
# the login process is asking for our terminal type.
|
||||
i = child.expect([COMMAND_PROMPT, TERMINAL_PROMPT])
|
||||
if i == 1:
|
||||
child.sendline(TERMINAL_TYPE)
|
||||
child.expect(COMMAND_PROMPT)
|
||||
#
|
||||
# Set command prompt to something more unique.
|
||||
#
|
||||
COMMAND_PROMPT = "\[PEXPECT\]\$ "
|
||||
child.sendline("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]\$ '")
|
||||
i = child.expect([pexpect.TIMEOUT, COMMAND_PROMPT], timeout=10)
|
||||
if i == 0:
|
||||
print "Failed to set command prompt using sh or csh style."
|
||||
print "Response was:"
|
||||
print child.before
|
||||
sys.exit(1)
|
||||
|
||||
# Now we should be at the command prompt and ready to run some commands.
|
||||
print '---------------------------------------'
|
||||
print 'Report of commands run on remote host.'
|
||||
print '---------------------------------------'
|
||||
|
||||
# Run uname.
|
||||
child.sendline('uname -a')
|
||||
child.expect(COMMAND_PROMPT)
|
||||
print child.before
|
||||
if 'linux' in child.before.lower():
|
||||
LINUX_MODE = 1
|
||||
else:
|
||||
LINUX_MODE = 0
|
||||
|
||||
# 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])')
|
||||
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)
|
||||
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)
|
||||
mins = str(int(child.match.group(1)))
|
||||
print
|
||||
print 'Uptime: %s days, %s users, %s (1 min), %s (5 min), %s (15 min)' % (
|
||||
duration, users, av1, av5, av15)
|
||||
child.expect(COMMAND_PROMPT)
|
||||
|
||||
# Run iostat.
|
||||
child.sendline('iostat')
|
||||
child.expect(COMMAND_PROMPT)
|
||||
print child.before
|
||||
|
||||
# Run vmstat.
|
||||
child.sendline('vmstat')
|
||||
child.expect(COMMAND_PROMPT)
|
||||
print child.before
|
||||
|
||||
# Run free.
|
||||
if LINUX_MODE:
|
||||
child.sendline('free') # Linux systems only.
|
||||
child.expect(COMMAND_PROMPT)
|
||||
print child.before
|
||||
|
||||
# Run df.
|
||||
child.sendline('df')
|
||||
child.expect(COMMAND_PROMPT)
|
||||
print child.before
|
||||
|
||||
# Run lsof.
|
||||
child.sendline('lsof')
|
||||
child.expect(COMMAND_PROMPT)
|
||||
print child.before
|
||||
|
||||
# # Run netstat
|
||||
# child.sendline ('netstat')
|
||||
# child.expect (COMMAND_PROMPT)
|
||||
# print child.before
|
||||
|
||||
# # Run MySQL show status.
|
||||
# child.sendline ('mysql -p -e "SHOW STATUS;"')
|
||||
# child.expect (PASSWORD_PROMPT_MYSQL)
|
||||
# child.sendline (password_mysql)
|
||||
# child.expect (COMMAND_PROMPT)
|
||||
# print
|
||||
# print child.before
|
||||
|
||||
# Now exit the remote host.
|
||||
child.sendline('exit')
|
||||
index = child.expect([pexpect.EOF, "(?i)there are stopped jobs"])
|
||||
if index == 1:
|
||||
child.sendline("exit")
|
||||
child.expect(EOF)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
try:
|
||||
main()
|
||||
except Exception as e:
|
||||
print str(e)
|
||||
traceback.print_exc()
|
||||
os._exit(1)
|
|
@ -1,95 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""Change passwords on the named machines. passmass host1 host2 host3 . . .
|
||||
Note that login shell prompt on remote machine must end in # or $. """
|
||||
|
||||
import pexpect
|
||||
import sys
|
||||
import getpass
|
||||
|
||||
USAGE = '''passmass host1 host2 host3 . . .'''
|
||||
COMMAND_PROMPT = '[$#] '
|
||||
TERMINAL_PROMPT = r'Terminal type\?'
|
||||
TERMINAL_TYPE = 'vt100'
|
||||
SSH_NEWKEY = r'Are you sure you want to continue connecting \(yes/no\)\?'
|
||||
|
||||
|
||||
def login(host, user, password):
|
||||
|
||||
child = pexpect.spawn('ssh -l %s %s' % (user, host))
|
||||
fout = file("LOG.TXT", "wb")
|
||||
child.setlog(fout)
|
||||
|
||||
i = child.expect([pexpect.TIMEOUT, SSH_NEWKEY, '[Pp]assword: '])
|
||||
if i == 0: # Timeout
|
||||
print 'ERROR!'
|
||||
print 'SSH could not login. Here is what SSH said:'
|
||||
print child.before, child.after
|
||||
sys.exit(1)
|
||||
if i == 1: # SSH does not have the public key. Just accept it.
|
||||
child.sendline('yes')
|
||||
child.expect('[Pp]assword: ')
|
||||
child.sendline(password)
|
||||
# Now we are either at the command prompt or
|
||||
# the login process is asking for our terminal type.
|
||||
i = child.expect(['Permission denied', TERMINAL_PROMPT, COMMAND_PROMPT])
|
||||
if i == 0:
|
||||
print 'Permission denied on host:', host
|
||||
sys.exit(1)
|
||||
if i == 1:
|
||||
child.sendline(TERMINAL_TYPE)
|
||||
child.expect(COMMAND_PROMPT)
|
||||
return child
|
||||
|
||||
# (current) UNIX password:
|
||||
|
||||
|
||||
def change_password(child, user, oldpassword, newpassword):
|
||||
|
||||
child.sendline('passwd')
|
||||
i = child.expect(
|
||||
['[Oo]ld [Pp]assword', '.current.*password', '[Nn]ew [Pp]assword'])
|
||||
# Root does not require old password, so it gets to bypass the next step.
|
||||
if i == 0 or i == 1:
|
||||
child.sendline(oldpassword)
|
||||
child.expect('[Nn]ew [Pp]assword')
|
||||
child.sendline(newpassword)
|
||||
i = child.expect(['[Nn]ew [Pp]assword', '[Rr]etype', '[Rr]e-enter'])
|
||||
if i == 0:
|
||||
print 'Host did not like new password. Here is what it said...'
|
||||
print child.before
|
||||
child.send(chr(3)) # Ctrl-C
|
||||
child.sendline('') # This should tell remote passwd command to quit.
|
||||
return
|
||||
child.sendline(newpassword)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
if len(sys.argv) <= 1:
|
||||
print USAGE
|
||||
return 1
|
||||
|
||||
user = raw_input('Username: ')
|
||||
password = getpass.getpass('Current Password: ')
|
||||
newpassword = getpass.getpass('New Password: ')
|
||||
newpasswordconfirm = getpass.getpass('Confirm New Password: ')
|
||||
if newpassword != newpasswordconfirm:
|
||||
print 'New Passwords do not match.'
|
||||
return 1
|
||||
|
||||
for host in sys.argv[1:]:
|
||||
child = login(host, user, password)
|
||||
if child is None:
|
||||
print 'Could not login to host:', host
|
||||
continue
|
||||
print 'Changing password on host:', host
|
||||
change_password(child, user, password, newpassword)
|
||||
child.expect(COMMAND_PROMPT)
|
||||
child.sendline('exit')
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
except pexpect.ExceptionPexpect as e:
|
||||
print str(e)
|
|
@ -1,21 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""This starts the python interpreter; captures the startup message; then gives
|
||||
the user interactive control over the session. Why? For fun... """
|
||||
|
||||
# Don't do this unless you like being John Malkovich
|
||||
# c = pexpect.spawn ('/usr/bin/env python ./python.py')
|
||||
|
||||
import pexpect
|
||||
c = pexpect.spawn('/usr/bin/env python')
|
||||
c.expect('>>>')
|
||||
print 'And now for something completely different...'
|
||||
f = lambda s: s and f(s[1:]) + s[0] # Makes a function to reverse a string.
|
||||
print f(c.before)
|
||||
print 'Yes, it\'s python, but it\'s backwards.'
|
||||
print
|
||||
print 'Escape character is \'^]\'.'
|
||||
print c.after,
|
||||
c.interact()
|
||||
c.kill(1)
|
||||
print 'is alive:', c.isalive()
|
File diff suppressed because it is too large
Load Diff
|
@ -1,115 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""This spawns a sub-shell (bash) and gives the user interactive control. The
|
||||
entire shell session is logged to a file called script.log. This behaves much
|
||||
like the classic BSD command 'script'.
|
||||
|
||||
./script.py [-a] [-c command] {logfilename}
|
||||
|
||||
logfilename : This is the name of the log file. Default is script.log.
|
||||
-a : Append to log file. Default is to overwrite log file.
|
||||
-c : spawn command. Default is to spawn the sh shell.
|
||||
|
||||
Example:
|
||||
|
||||
This will start a bash shell and append to the log named my_session.log:
|
||||
|
||||
./script.py -a -c bash my_session.log
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import getopt
|
||||
import signal
|
||||
import fcntl
|
||||
import termios
|
||||
import struct
|
||||
import traceback
|
||||
import pexpect
|
||||
|
||||
global_pexpect_instance = None # Used by signal handler
|
||||
|
||||
|
||||
def exit_with_usage():
|
||||
|
||||
print globals()['__doc__']
|
||||
os._exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
######################################################################
|
||||
# Parse the options, arguments, get ready, etc.
|
||||
######################################################################
|
||||
try:
|
||||
optlist, args = getopt.getopt(
|
||||
sys.argv[
|
||||
1:], 'h?ac:', [
|
||||
'help', 'h', '?'])
|
||||
except Exception as e:
|
||||
print str(e)
|
||||
exit_with_usage()
|
||||
options = dict(optlist)
|
||||
if len(args) > 1:
|
||||
exit_with_usage()
|
||||
|
||||
if [elem for elem in options if elem in [
|
||||
'-h', '--h', '-?', '--?', '--help']]:
|
||||
print "Help:"
|
||||
exit_with_usage()
|
||||
|
||||
if len(args) == 1:
|
||||
script_filename = args[0]
|
||||
else:
|
||||
script_filename = "script.log"
|
||||
if '-a' in options:
|
||||
fout = file(script_filename, "ab")
|
||||
else:
|
||||
fout = file(script_filename, "wb")
|
||||
if '-c' in options:
|
||||
command = options['-c']
|
||||
else:
|
||||
command = "sh"
|
||||
|
||||
# Begin log with date/time in the form CCCCyymm.hhmmss
|
||||
fout.write('# %4d%02d%02d.%02d%02d%02d \n' % time.localtime()[:-3])
|
||||
|
||||
######################################################################
|
||||
# Start the interactive session
|
||||
######################################################################
|
||||
p = pexpect.spawn(command)
|
||||
p.logfile = fout
|
||||
global global_pexpect_instance
|
||||
global_pexpect_instance = p
|
||||
signal.signal(signal.SIGWINCH, sigwinch_passthrough)
|
||||
|
||||
print "Script recording started. Type ^] (ASCII 29) to escape from the script shell."
|
||||
p.interact(chr(29))
|
||||
fout.close()
|
||||
return 0
|
||||
|
||||
|
||||
def sigwinch_passthrough(sig, data):
|
||||
|
||||
# Check for buggy platforms (see pexpect.setwinsize()).
|
||||
if 'TIOCGWINSZ' in dir(termios):
|
||||
TIOCGWINSZ = termios.TIOCGWINSZ
|
||||
else:
|
||||
TIOCGWINSZ = 1074295912 # assume
|
||||
s = struct.pack("HHHH", 0, 0, 0, 0)
|
||||
a = struct.unpack('HHHH', fcntl.ioctl(sys.stdout.fileno(), TIOCGWINSZ, s))
|
||||
global global_pexpect_instance
|
||||
global_pexpect_instance.setwinsize(a[0], a[1])
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except SystemExit as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
print "ERROR"
|
||||
print str(e)
|
||||
traceback.print_exc()
|
||||
os._exit(1)
|
|
@ -1,93 +0,0 @@
|
|||
#
|
||||
# Eric S. Raymond
|
||||
#
|
||||
# Greatly modified by Nigel W. Moriarty
|
||||
# April 2003
|
||||
#
|
||||
from pexpect import *
|
||||
import os
|
||||
import sys
|
||||
import getpass
|
||||
import time
|
||||
|
||||
|
||||
class ssh_session:
|
||||
|
||||
"Session with extra state including the password to be used."
|
||||
|
||||
def __init__(self, user, host, password=None, verbose=0):
|
||||
|
||||
self.user = user
|
||||
self.host = host
|
||||
self.verbose = verbose
|
||||
self.password = password
|
||||
self.keys = [
|
||||
'authenticity',
|
||||
'assword:',
|
||||
'@@@@@@@@@@@@',
|
||||
'Command not found.',
|
||||
EOF,
|
||||
]
|
||||
|
||||
self.f = open('ssh.out', 'w')
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
outl = 'class :' + self.__class__.__name__
|
||||
for attr in self.__dict__:
|
||||
if attr == 'password':
|
||||
outl += '\n\t' + attr + ' : ' + '*' * len(self.password)
|
||||
else:
|
||||
outl += '\n\t' + attr + ' : ' + str(getattr(self, attr))
|
||||
return outl
|
||||
|
||||
def __exec(self, command):
|
||||
"Execute a command on the remote host. Return the output."
|
||||
child = spawn(command,
|
||||
# timeout=10,
|
||||
)
|
||||
if self.verbose:
|
||||
sys.stderr.write("-> " + command + "\n")
|
||||
seen = child.expect(self.keys)
|
||||
self.f.write(str(child.before) + str(child.after) + '\n')
|
||||
if seen == 0:
|
||||
child.sendline('yes')
|
||||
seen = child.expect(self.keys)
|
||||
if seen == 1:
|
||||
if not self.password:
|
||||
self.password = getpass.getpass('Remote password: ')
|
||||
child.sendline(self.password)
|
||||
child.readline()
|
||||
time.sleep(5)
|
||||
# Added to allow the background running of remote process
|
||||
if not child.isalive():
|
||||
seen = child.expect(self.keys)
|
||||
if seen == 2:
|
||||
lines = child.readlines()
|
||||
self.f.write(lines)
|
||||
if self.verbose:
|
||||
sys.stderr.write("<- " + child.before + "|\n")
|
||||
try:
|
||||
self.f.write(str(child.before) + str(child.after) + '\n')
|
||||
except:
|
||||
pass
|
||||
self.f.close()
|
||||
return child.before
|
||||
|
||||
def ssh(self, command):
|
||||
|
||||
return self.__exec("ssh -l %s %s \"%s\""
|
||||
% (self.user, self.host, command))
|
||||
|
||||
def scp(self, src, dst):
|
||||
|
||||
return self.__exec("scp %s %s@%s:%s"
|
||||
% (src, session.user, session.host, dst))
|
||||
|
||||
def exists(self, file):
|
||||
"Retrieve file permissions of specified remote file."
|
||||
seen = self.ssh("/bin/ls -ld %s" % file)
|
||||
if string.find(seen, "No such file") > -1:
|
||||
return None # File doesn't exist
|
||||
else:
|
||||
return seen.split()[0] # Return permission field of listing.
|
|
@ -1,76 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""This starts an SSH tunnel to a given host. If the SSH process ever dies then
|
||||
this script will detect that and restart it. I use this under Cygwin to keep
|
||||
open encrypted tunnels to port 25 (SMTP), port 143 (IMAP4), and port 110
|
||||
(POP3). I set my mail client to talk to localhost and I keep this script
|
||||
running in the background.
|
||||
|
||||
Note that this is a rather stupid script at the moment because it just looks to
|
||||
see if any ssh process is running. It should really make sure that our specific
|
||||
ssh process is running. The problem is that ssh is missing a very useful
|
||||
feature. It has no way to report the process id of the background daemon that
|
||||
it creates with the -f command. This would be a really useful script if I could
|
||||
figure a way around this problem. """
|
||||
|
||||
import pexpect
|
||||
import getpass
|
||||
import time
|
||||
|
||||
# SMTP:25 IMAP4:143 POP3:110
|
||||
tunnel_command = 'ssh -C -N -f -L 25:127.0.0.1:25 -L 143:127.0.0.1:143 -L 110:127.0.0.1:110 %(user)@%(host)'
|
||||
host = raw_input('Hostname: ')
|
||||
user = raw_input('Username: ')
|
||||
X = getpass.getpass('Password: ')
|
||||
|
||||
|
||||
def get_process_info():
|
||||
|
||||
# This seems to work on both Linux and BSD, but should otherwise be
|
||||
# considered highly UNportable.
|
||||
|
||||
ps = pexpect.run('ps ax -O ppid')
|
||||
pass
|
||||
|
||||
|
||||
def start_tunnel():
|
||||
try:
|
||||
ssh_tunnel = pexpect.spawn(tunnel_command % globals())
|
||||
ssh_tunnel.expect('password:')
|
||||
time.sleep(0.1)
|
||||
ssh_tunnel.sendline(X)
|
||||
time.sleep(60) # Cygwin is slow to update process status.
|
||||
ssh_tunnel.expect(pexpect.EOF)
|
||||
|
||||
except Exception, e:
|
||||
print str(e)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
while True:
|
||||
ps = pexpect.spawn('ps')
|
||||
time.sleep(1)
|
||||
index = ps.expect(['/usr/bin/ssh', pexpect.EOF, pexpect.TIMEOUT])
|
||||
if index == 2:
|
||||
print 'TIMEOUT in ps command...'
|
||||
print str(ps)
|
||||
time.sleep(13)
|
||||
if index == 1:
|
||||
print time.asctime(),
|
||||
print 'restarting tunnel'
|
||||
start_tunnel()
|
||||
time.sleep(11)
|
||||
print 'tunnel OK'
|
||||
else:
|
||||
# print 'tunnel OK'
|
||||
time.sleep(7)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
# This was for older SSH versions that didn't have -f option
|
||||
#tunnel_command = 'ssh -C -n -L 25:%(host)s:25 -L 110:%(host)s:110 %(user)s@%(host)s -f nothing.sh'
|
||||
# nothing_script = """#!/bin/sh
|
||||
# while true; do sleep 53; done
|
||||
#"""
|
|
@ -1,57 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""This runs 'ls -l' on a remote host using SSH. At the prompts enter hostname,
|
||||
user, and password.
|
||||
|
||||
$Id: sshls.py 489 2007-11-28 23:40:34Z noah $
|
||||
"""
|
||||
|
||||
import pexpect
|
||||
import getpass
|
||||
import os
|
||||
|
||||
|
||||
def ssh_command(user, host, password, command):
|
||||
"""This runs a command on the remote host. This could also be done with the
|
||||
pxssh class, but this demonstrates what that class does at a simpler level.
|
||||
This returns a pexpect.spawn object. This handles the case when you try to
|
||||
connect to a new host and ssh asks you if you want to accept the public key
|
||||
fingerprint and continue connecting. """
|
||||
|
||||
ssh_newkey = 'Are you sure you want to continue connecting'
|
||||
child = pexpect.spawn('ssh -l %s %s %s' % (user, host, command))
|
||||
i = child.expect([pexpect.TIMEOUT, ssh_newkey, 'password: '])
|
||||
if i == 0: # Timeout
|
||||
print 'ERROR!'
|
||||
print 'SSH could not login. Here is what SSH said:'
|
||||
print child.before, child.after
|
||||
return None
|
||||
if i == 1: # SSH does not have the public key. Just accept it.
|
||||
child.sendline('yes')
|
||||
child.expect('password: ')
|
||||
i = child.expect([pexpect.TIMEOUT, 'password: '])
|
||||
if i == 0: # Timeout
|
||||
print 'ERROR!'
|
||||
print 'SSH could not login. Here is what SSH said:'
|
||||
print child.before, child.after
|
||||
return None
|
||||
child.sendline(password)
|
||||
return child
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
host = raw_input('Hostname: ')
|
||||
user = raw_input('User: ')
|
||||
password = getpass.getpass('Password: ')
|
||||
child = ssh_command(user, host, password, '/bin/ls -l')
|
||||
child.expect(pexpect.EOF)
|
||||
print child.before
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
except Exception as e:
|
||||
print str(e)
|
||||
traceback.print_exc()
|
||||
os._exit(1)
|
|
@ -1,58 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""This displays uptime information using uptime. This is redundant,
|
||||
but it demonstrates expecting for a regular expression that uses subgroups.
|
||||
|
||||
$Id: uptime.py 489 2007-11-28 23:40:34Z noah $
|
||||
"""
|
||||
|
||||
import pexpect
|
||||
import re
|
||||
|
||||
# There are many different styles of uptime results. I try to parse them all. Yeee!
|
||||
# Examples from different machines:
|
||||
# [x86] Linux 2.4 (Redhat 7.3)
|
||||
# 2:06pm up 63 days, 18 min, 3 users, load average: 0.32, 0.08, 0.02
|
||||
# [x86] Linux 2.4.18-14 (Redhat 8.0)
|
||||
# 3:07pm up 29 min, 1 user, load average: 2.44, 2.51, 1.57
|
||||
# [PPC - G4] MacOS X 10.1 SERVER Edition
|
||||
# 2:11PM up 3 days, 13:50, 3 users, load averages: 0.01, 0.00, 0.00
|
||||
# [powerpc] Darwin v1-58.corefa.com 8.2.0 Darwin Kernel Version 8.2.0
|
||||
# 10:35 up 18:06, 4 users, load averages: 0.52 0.47 0.36
|
||||
# [Sparc - R220] Sun Solaris (8)
|
||||
# 2:13pm up 22 min(s), 1 user, load average: 0.02, 0.01, 0.01
|
||||
# [x86] Linux 2.4.18-14 (Redhat 8)
|
||||
# 11:36pm up 4 days, 17:58, 1 user, load average: 0.03, 0.01, 0.00
|
||||
# AIX jwdir 2 5 0001DBFA4C00
|
||||
# 09:43AM up 23:27, 1 user, load average: 0.49, 0.32, 0.23
|
||||
# OpenBSD box3 2.9 GENERIC#653 i386
|
||||
# 6:08PM up 4 days, 22:26, 1 user, load averages: 0.13, 0.09, 0.08
|
||||
|
||||
# This parses uptime output into the major groups using regex group matching.
|
||||
p = pexpect.spawn('uptime')
|
||||
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])')
|
||||
duration, users, av1, av5, av15 = p.match.groups()
|
||||
|
||||
# The duration is a little harder to parse because of all the different
|
||||
# styles of uptime. I'm sure there is a way to do this all at once with
|
||||
# one single regex, but I bet it would be hard to read and maintain.
|
||||
# If anyone wants to send me a version using a single regex I'd be happy
|
||||
# to see it.
|
||||
days = '0'
|
||||
hours = '0'
|
||||
mins = '0'
|
||||
if 'day' in duration:
|
||||
p.match = re.search('([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)
|
||||
mins = str(int(p.match.group(1)))
|
||||
|
||||
# Print the parsed fields in CSV format.
|
||||
print 'days, hours, minutes, users, cpu avg 1 min, cpu avg 5 min, cpu avg 15 min'
|
||||
print '%s, %s, %s, %s, %s, %s, %s' % (days, hours, mins, users, av1, av5, av15)
|
|
@ -1,98 +0,0 @@
|
|||
"""This is like pexpect, but will work on any file descriptor that you pass it.
|
||||
So you are reponsible for opening and close the file descriptor.
|
||||
|
||||
$Id: fdpexpect.py 505 2007-12-26 21:33:50Z noah $
|
||||
"""
|
||||
|
||||
from pexpect import *
|
||||
import os
|
||||
|
||||
__all__ = ['fdspawn']
|
||||
|
||||
|
||||
class fdspawn (spawn):
|
||||
|
||||
"""This is like pexpect.spawn but allows you to supply your own open file
|
||||
descriptor. For example, you could use it to read through a file looking
|
||||
for patterns, or to control a modem or serial device. """
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
fd,
|
||||
args=[],
|
||||
timeout=30,
|
||||
maxread=2000,
|
||||
searchwindowsize=None,
|
||||
logfile=None):
|
||||
"""This takes a file descriptor (an int) or an object that support the
|
||||
fileno() method (returning an int). All Python file-like objects
|
||||
support fileno(). """
|
||||
|
||||
# TODO: Add better handling of trying to use fdspawn in place of spawn
|
||||
# TODO: (overload to allow fdspawn to also handle commands as spawn
|
||||
# does.
|
||||
|
||||
if not isinstance(fd, type(0)) and hasattr(fd, 'fileno'):
|
||||
fd = fd.fileno()
|
||||
|
||||
if not isinstance(fd, type(0)):
|
||||
raise ExceptionPexpect(
|
||||
'The fd argument is not an int. If this is a command string then maybe you want to use pexpect.spawn.')
|
||||
|
||||
try: # make sure fd is a valid file descriptor
|
||||
os.fstat(fd)
|
||||
except OSError:
|
||||
raise ExceptionPexpect(
|
||||
'The fd argument is not a valid file descriptor.')
|
||||
|
||||
self.args = None
|
||||
self.command = None
|
||||
spawn.__init__(
|
||||
self,
|
||||
None,
|
||||
args,
|
||||
timeout,
|
||||
maxread,
|
||||
searchwindowsize,
|
||||
logfile)
|
||||
self.child_fd = fd
|
||||
self.own_fd = False
|
||||
self.closed = False
|
||||
self.name = '<file descriptor %d>' % fd
|
||||
|
||||
def __del__(self):
|
||||
|
||||
return
|
||||
|
||||
def close(self):
|
||||
|
||||
if self.child_fd == -1:
|
||||
return
|
||||
if self.own_fd:
|
||||
self.close(self)
|
||||
else:
|
||||
self.flush()
|
||||
os.close(self.child_fd)
|
||||
self.child_fd = -1
|
||||
self.closed = True
|
||||
|
||||
def isalive(self):
|
||||
"""This checks if the file descriptor is still valid. If os.fstat()
|
||||
does not raise an exception then we assume it is alive. """
|
||||
|
||||
if self.child_fd == -1:
|
||||
return False
|
||||
try:
|
||||
os.fstat(self.child_fd)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def terminate(self, force=False):
|
||||
|
||||
raise ExceptionPexpect(
|
||||
'This method is not valid for file descriptors.')
|
||||
|
||||
def kill(self, sig):
|
||||
|
||||
return
|
File diff suppressed because it is too large
Load Diff
|
@ -1,367 +0,0 @@
|
|||
"""This class extends pexpect.spawn to specialize setting up SSH connections.
|
||||
This adds methods for login, logout, and expecting the shell prompt.
|
||||
|
||||
$Id: pxssh.py 513 2008-02-09 18:26:13Z noah $
|
||||
"""
|
||||
|
||||
from pexpect import *
|
||||
import pexpect
|
||||
import time
|
||||
|
||||
__all__ = ['ExceptionPxssh', 'pxssh']
|
||||
|
||||
# Exception classes used by this module.
|
||||
|
||||
|
||||
class ExceptionPxssh(ExceptionPexpect):
|
||||
"""Raised for pxssh exceptions.
|
||||
"""
|
||||
|
||||
|
||||
class pxssh (spawn):
|
||||
|
||||
"""This class extends pexpect.spawn to specialize setting up SSH
|
||||
connections. This adds methods for login, logout, and expecting the shell
|
||||
prompt. It does various tricky things to handle many situations in the SSH
|
||||
login process. For example, if the session is your first login, then pxssh
|
||||
automatically accepts the remote certificate; or if you have public key
|
||||
authentication setup then pxssh won't wait for the password prompt.
|
||||
|
||||
pxssh uses the shell prompt to synchronize output from the remote host. In
|
||||
order to make this more robust it sets the shell prompt to something more
|
||||
unique than just $ or #. This should work on most Borne/Bash or Csh style
|
||||
shells.
|
||||
|
||||
Example that runs a few commands on a remote server and prints the result::
|
||||
|
||||
import pxssh
|
||||
import getpass
|
||||
try:
|
||||
s = pxssh.pxssh()
|
||||
hostname = raw_input('hostname: ')
|
||||
username = raw_input('username: ')
|
||||
password = getpass.getpass('password: ')
|
||||
s.login (hostname, username, password)
|
||||
s.sendline ('uptime') # run a command
|
||||
s.prompt() # match the prompt
|
||||
print s.before # print everything before the prompt.
|
||||
s.sendline ('ls -l')
|
||||
s.prompt()
|
||||
print s.before
|
||||
s.sendline ('df')
|
||||
s.prompt()
|
||||
print s.before
|
||||
s.logout()
|
||||
except pxssh.ExceptionPxssh, e:
|
||||
print "pxssh failed on login."
|
||||
print str(e)
|
||||
|
||||
Note that if you have ssh-agent running while doing development with pxssh
|
||||
then this can lead to a lot of confusion. Many X display managers (xdm,
|
||||
gdm, kdm, etc.) will automatically start a GUI agent. You may see a GUI
|
||||
dialog box popup asking for a password during development. You should turn
|
||||
off any key agents during testing. The 'force_password' attribute will turn
|
||||
off public key authentication. This will only work if the remote SSH server
|
||||
is configured to allow password logins. Example of using 'force_password'
|
||||
attribute::
|
||||
|
||||
s = pxssh.pxssh()
|
||||
s.force_password = True
|
||||
hostname = raw_input('hostname: ')
|
||||
username = raw_input('username: ')
|
||||
password = getpass.getpass('password: ')
|
||||
s.login (hostname, username, password)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
timeout=30,
|
||||
maxread=2000,
|
||||
searchwindowsize=None,
|
||||
logfile=None,
|
||||
cwd=None,
|
||||
env=None):
|
||||
spawn.__init__(
|
||||
self,
|
||||
None,
|
||||
timeout=timeout,
|
||||
maxread=maxread,
|
||||
searchwindowsize=searchwindowsize,
|
||||
logfile=logfile,
|
||||
cwd=cwd,
|
||||
env=env)
|
||||
|
||||
self.name = '<pxssh>'
|
||||
|
||||
# SUBTLE HACK ALERT! Note that the command to set the prompt uses a
|
||||
# slightly different string than the regular expression to match it. This
|
||||
# is because when you set the prompt the command will echo back, but we
|
||||
# don't want to match the echoed command. So if we make the set command
|
||||
# slightly different than the regex we eliminate the problem. To make the
|
||||
# set command different we add a backslash in front of $. The $ doesn't
|
||||
# need to be escaped, but it doesn't hurt and serves to make the set
|
||||
# prompt command different than the regex.
|
||||
|
||||
# used to match the command-line prompt
|
||||
self.UNIQUE_PROMPT = "\[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.SSH_OPTS = "-o'RSAAuthentication=no' -o 'PubkeyAuthentication=no'"
|
||||
# Disabling X11 forwarding gets rid of the annoying SSH_ASKPASS from
|
||||
# displaying a GUI password dialog. I have not figured out how to
|
||||
# disable only SSH_ASKPASS without also disabling X11 forwarding.
|
||||
# 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.auto_prompt_reset = True
|
||||
|
||||
def levenshtein_distance(self, a, b):
|
||||
"""This calculates the Levenshtein distance between a and b.
|
||||
"""
|
||||
|
||||
n, m = len(a), len(b)
|
||||
if n > m:
|
||||
a, b = b, a
|
||||
n, m = m, n
|
||||
current = range(n + 1)
|
||||
for i in range(1, m + 1):
|
||||
previous, current = current, [i] + [0] * n
|
||||
for j in range(1, n + 1):
|
||||
add, delete = previous[j] + 1, current[j - 1] + 1
|
||||
change = previous[j - 1]
|
||||
if a[j - 1] != b[i - 1]:
|
||||
change = change + 1
|
||||
current[j] = min(add, delete, change)
|
||||
return current[n]
|
||||
|
||||
def sync_original_prompt(self):
|
||||
"""This attempts to find the prompt. Basically, press enter and record
|
||||
the response; press enter again and record the response; if the two
|
||||
responses are similar then assume we are at the original prompt. This
|
||||
is a slow function. It can take over 10 seconds. """
|
||||
|
||||
# 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.
|
||||
# If latency is worse than these values then this will fail.
|
||||
|
||||
try:
|
||||
# GAS: Clear out the cache before getting the prompt
|
||||
self.read_nonblocking(size=10000, timeout=1)
|
||||
except TIMEOUT:
|
||||
pass
|
||||
time.sleep(0.1)
|
||||
self.sendline()
|
||||
time.sleep(0.5)
|
||||
x = self.read_nonblocking(size=1000, timeout=1)
|
||||
time.sleep(0.1)
|
||||
self.sendline()
|
||||
time.sleep(0.5)
|
||||
a = self.read_nonblocking(size=1000, timeout=1)
|
||||
time.sleep(0.1)
|
||||
self.sendline()
|
||||
time.sleep(0.5)
|
||||
b = self.read_nonblocking(size=1000, timeout=1)
|
||||
ld = self.levenshtein_distance(a, b)
|
||||
len_a = len(a)
|
||||
if len_a == 0:
|
||||
return False
|
||||
if float(ld) / len_a < 0.4:
|
||||
return True
|
||||
return False
|
||||
|
||||
# TODO: This is getting messy and I'm pretty sure this isn't perfect.
|
||||
# TODO: I need to draw a flow chart for this.
|
||||
def login(
|
||||
self,
|
||||
server,
|
||||
username,
|
||||
password='',
|
||||
terminal_type='ansi',
|
||||
original_prompt=r"[#$]",
|
||||
login_timeout=10,
|
||||
port=None,
|
||||
auto_prompt_reset=True):
|
||||
"""This logs the user into the given server. It uses the
|
||||
'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
|
||||
called "~/.hushlogin" on the remote server. If a prompt cannot be found
|
||||
then this will not necessarily cause the login to fail. In the case of
|
||||
a timeout when looking for the prompt we assume that the original
|
||||
prompt was so weird that we could not match it, so we use a few tricks
|
||||
to guess when we have reached the prompt. Then we hope for the best and
|
||||
blindly try to reset the prompt to something more unique. If that fails
|
||||
then login() raises an ExceptionPxssh exception.
|
||||
|
||||
In some situations it is not possible or desirable to reset the
|
||||
original prompt. In this case, set 'auto_prompt_reset' to False to
|
||||
inhibit setting the prompt to the UNIQUE_PROMPT. Remember that pxssh
|
||||
uses a unique prompt in the prompt() method. If the original prompt is
|
||||
not reset then this will disable the prompt() method unless you
|
||||
manually set the PROMPT attribute. """
|
||||
|
||||
ssh_options = '-q'
|
||||
if self.force_password:
|
||||
ssh_options = ssh_options + ' ' + self.SSH_OPTS
|
||||
if port is not None:
|
||||
ssh_options = ssh_options + ' -p %s' % (str(port))
|
||||
cmd = "ssh %s -l %s %s" % (ssh_options, username, server)
|
||||
|
||||
# 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"],
|
||||
timeout=login_timeout)
|
||||
|
||||
# First phase
|
||||
if i == 0:
|
||||
# New certificate -- always accept it.
|
||||
# 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])
|
||||
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])
|
||||
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])
|
||||
|
||||
# Second phase
|
||||
if i == 0:
|
||||
# This is weird. This should not happen twice in a row.
|
||||
self.close()
|
||||
raise ExceptionPxssh(
|
||||
'Weird error. Got "are you sure" prompt twice.')
|
||||
elif i == 1: # can occur if you have a public key pair set to authenticate.
|
||||
# TODO: May NOT be OK if expect() got tricked and matched a false
|
||||
# prompt.
|
||||
pass
|
||||
elif i == 2: # password prompt again
|
||||
# For incorrect passwords, some ssh servers will
|
||||
# ask for the password again, others return 'denied' right away.
|
||||
# If we get the password prompt again then this means
|
||||
# we didn't get the password right the first time.
|
||||
self.close()
|
||||
raise ExceptionPxssh('password refused')
|
||||
elif i == 3: # permission denied -- password was bad.
|
||||
self.close()
|
||||
raise ExceptionPxssh('permission denied')
|
||||
elif i == 4: # terminal type again? WTF?
|
||||
self.close()
|
||||
raise ExceptionPxssh(
|
||||
'Weird error. Got "terminal type" prompt twice.')
|
||||
elif i == 5: # Timeout
|
||||
# This is tricky... I presume that we are at the command-line prompt.
|
||||
# It may be that the shell prompt was so weird that we couldn't match
|
||||
# it. Or it may be that we couldn't log in for some other reason. I
|
||||
# can't be sure, but it's safe to guess that we did login because if
|
||||
# I presume wrong and we are not logged in then this should be caught
|
||||
# later when I try to set the shell prompt.
|
||||
pass
|
||||
elif i == 6: # Connection closed by remote host
|
||||
self.close()
|
||||
raise ExceptionPxssh('connection closed')
|
||||
else: # Unexpected
|
||||
self.close()
|
||||
raise ExceptionPxssh('unexpected login response')
|
||||
if not self.sync_original_prompt():
|
||||
self.close()
|
||||
raise ExceptionPxssh('could not synchronize with original prompt')
|
||||
# We appear to be in.
|
||||
# set shell prompt to something unique.
|
||||
if auto_prompt_reset:
|
||||
if not self.set_unique_prompt():
|
||||
self.close()
|
||||
raise ExceptionPxssh(
|
||||
'could not set shell prompt\n' + self.before)
|
||||
return True
|
||||
|
||||
def logout(self):
|
||||
"""This sends exit to the remote shell. If there are stopped jobs then
|
||||
this automatically sends exit twice. """
|
||||
|
||||
self.sendline("exit")
|
||||
index = self.expect([EOF, "(?i)there are stopped jobs"])
|
||||
if index == 1:
|
||||
self.sendline("exit")
|
||||
self.expect(EOF)
|
||||
self.close()
|
||||
|
||||
def prompt(self, timeout=20):
|
||||
"""This matches the shell prompt. This is little more than a short-cut
|
||||
to the expect() method. This returns True if the shell prompt was
|
||||
matched. This returns False if there was a timeout. Note that if you
|
||||
called login() with auto_prompt_reset set to False then you should have
|
||||
manually set the PROMPT attribute to a regex pattern for matching the
|
||||
prompt. """
|
||||
|
||||
i = self.expect([self.PROMPT, TIMEOUT], timeout=timeout)
|
||||
if i == 1:
|
||||
return False
|
||||
return True
|
||||
|
||||
def set_unique_prompt(self):
|
||||
"""This sets the remote prompt to something more unique than # or $.
|
||||
This makes it easier for the prompt() method to match the shell prompt
|
||||
unambiguously. This method is called automatically by the login()
|
||||
method, but you may want to call it manually if you somehow reset the
|
||||
shell prompt. For example, if you 'su' to a different user then you
|
||||
will need to manually reset the prompt. This sends shell commands to
|
||||
the remote host to set the prompt, so this assumes the remote host is
|
||||
ready to receive commands.
|
||||
|
||||
Alternatively, you may use your own prompt pattern. Just set the PROMPT
|
||||
attribute to a regular expression that matches it. In this case you
|
||||
should call login() with auto_prompt_reset=False; then set the PROMPT
|
||||
attribute. After that the prompt() method will try to match your prompt
|
||||
pattern."""
|
||||
|
||||
self.sendline("unset PROMPT_COMMAND")
|
||||
self.sendline(self.PROMPT_SET_SH) # sh-style
|
||||
i = self.expect([TIMEOUT, self.PROMPT], timeout=10)
|
||||
if i == 0: # csh-style
|
||||
self.sendline(self.PROMPT_SET_CSH)
|
||||
i = self.expect([TIMEOUT, self.PROMPT], timeout=10)
|
||||
if i == 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
# vi:ts=4:sw=4:expandtab:ft=python:
|
|
@ -1,349 +0,0 @@
|
|||
"""This implements a virtual screen. This is used to support ANSI terminal
|
||||
emulation. The screen representation and state is implemented in this class.
|
||||
Most of the methods are inspired by ANSI screen control codes. The ANSI class
|
||||
extends this class to add parsing of ANSI escape codes.
|
||||
|
||||
$Id: screen.py 486 2007-07-13 01:04:16Z noah $
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
||||
NUL = 0 # Fill character; ignored on input.
|
||||
ENQ = 5 # Transmit answerback message.
|
||||
BEL = 7 # Ring the bell.
|
||||
BS = 8 # Move cursor left.
|
||||
HT = 9 # Move cursor to next tab stop.
|
||||
LF = 10 # Line feed.
|
||||
VT = 11 # Same as LF.
|
||||
FF = 12 # Same as LF.
|
||||
CR = 13 # Move cursor to left margin or newline.
|
||||
SO = 14 # Invoke G1 character set.
|
||||
SI = 15 # Invoke G0 character set.
|
||||
XON = 17 # Resume transmission.
|
||||
XOFF = 19 # Halt transmission.
|
||||
CAN = 24 # Cancel escape sequence.
|
||||
SUB = 26 # Same as CAN.
|
||||
ESC = 27 # Introduce a control sequence.
|
||||
DEL = 127 # Fill character; ignored on input.
|
||||
SPACE = chr(32) # Space or blank character.
|
||||
|
||||
|
||||
def constrain(n, min, max):
|
||||
"""This returns a number, n constrained to the min and max bounds. """
|
||||
|
||||
if n < min:
|
||||
return min
|
||||
if n > max:
|
||||
return max
|
||||
return n
|
||||
|
||||
|
||||
class screen:
|
||||
|
||||
"""This object maintains the state of a virtual text screen as a
|
||||
rectangluar 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). """
|
||||
|
||||
def __init__(self, r=24, c=80):
|
||||
"""This initializes a blank scree of the given dimentions."""
|
||||
|
||||
self.rows = r
|
||||
self.cols = c
|
||||
self.cur_r = 1
|
||||
self.cur_c = 1
|
||||
self.cur_saved_r = 1
|
||||
self.cur_saved_c = 1
|
||||
self.scroll_row_start = 1
|
||||
self.scroll_row_end = self.rows
|
||||
self.w = [[SPACE] * self.cols for c in range(self.rows)]
|
||||
|
||||
def __str__(self):
|
||||
"""This returns a printable representation of the screen. The end of
|
||||
each screen line is terminated by a newline. """
|
||||
|
||||
return '\n'.join([''.join(c) for c in self.w])
|
||||
|
||||
def dump(self):
|
||||
"""This returns a copy of the screen as a string. This is similar to
|
||||
__str__ except that lines are not terminated with line feeds. """
|
||||
|
||||
return ''.join([''.join(c) for c in self.w])
|
||||
|
||||
def pretty(self):
|
||||
"""This returns a copy of the screen as a string with an ASCII text box
|
||||
around the screen border. This is similar to __str__ except that it
|
||||
adds a box. """
|
||||
|
||||
top_bot = '+' + '-' * self.cols + '+\n'
|
||||
return top_bot + \
|
||||
'\n'.join(['|' + line + '|' for line in str(self).split('\n')]) + '\n' + top_bot
|
||||
|
||||
def fill(self, ch=SPACE):
|
||||
|
||||
self.fill_region(1, 1, self.rows, self.cols, ch)
|
||||
|
||||
def fill_region(self, rs, cs, re, ce, ch=SPACE):
|
||||
|
||||
rs = constrain(rs, 1, self.rows)
|
||||
re = constrain(re, 1, self.rows)
|
||||
cs = constrain(cs, 1, self.cols)
|
||||
ce = constrain(ce, 1, self.cols)
|
||||
if rs > re:
|
||||
rs, re = re, rs
|
||||
if cs > ce:
|
||||
cs, ce = ce, cs
|
||||
for r in range(rs, re + 1):
|
||||
for c in range(cs, ce + 1):
|
||||
self.put_abs(r, c, ch)
|
||||
|
||||
def cr(self):
|
||||
"""This moves the cursor to the beginning (col 1) of the current row.
|
||||
"""
|
||||
|
||||
self.cursor_home(self.cur_r, 1)
|
||||
|
||||
def lf(self):
|
||||
"""This moves the cursor down with scrolling.
|
||||
"""
|
||||
|
||||
old_r = self.cur_r
|
||||
self.cursor_down()
|
||||
if old_r == self.cur_r:
|
||||
self.scroll_up()
|
||||
self.erase_line()
|
||||
|
||||
def crlf(self):
|
||||
"""This advances the cursor with CRLF properties.
|
||||
The cursor will line wrap and the screen may scroll.
|
||||
"""
|
||||
|
||||
self.cr()
|
||||
self.lf()
|
||||
|
||||
def newline(self):
|
||||
"""This is an alias for crlf().
|
||||
"""
|
||||
|
||||
self.crlf()
|
||||
|
||||
def put_abs(self, r, c, ch):
|
||||
"""Screen array starts at 1 index."""
|
||||
|
||||
r = constrain(r, 1, self.rows)
|
||||
c = constrain(c, 1, self.cols)
|
||||
ch = str(ch)[0]
|
||||
self.w[r - 1][c - 1] = ch
|
||||
|
||||
def put(self, ch):
|
||||
"""This puts a characters at the current cursor position.
|
||||
"""
|
||||
|
||||
self.put_abs(self.cur_r, self.cur_c, ch)
|
||||
|
||||
def insert_abs(self, r, c, ch):
|
||||
"""This inserts a character at (r,c). Everything under
|
||||
and to the right is shifted right one character.
|
||||
The last character of the line is lost.
|
||||
"""
|
||||
|
||||
r = constrain(r, 1, self.rows)
|
||||
c = constrain(c, 1, self.cols)
|
||||
for ci in range(self.cols, c, -1):
|
||||
self.put_abs(r, ci, self.get_abs(r, ci - 1))
|
||||
self.put_abs(r, c, ch)
|
||||
|
||||
def insert(self, ch):
|
||||
|
||||
self.insert_abs(self.cur_r, self.cur_c, ch)
|
||||
|
||||
def get_abs(self, r, c):
|
||||
|
||||
r = constrain(r, 1, self.rows)
|
||||
c = constrain(c, 1, self.cols)
|
||||
return self.w[r - 1][c - 1]
|
||||
|
||||
def get(self):
|
||||
|
||||
self.get_abs(self.cur_r, self.cur_c)
|
||||
|
||||
def get_region(self, rs, cs, re, ce):
|
||||
"""This returns a list of lines representing the region.
|
||||
"""
|
||||
|
||||
rs = constrain(rs, 1, self.rows)
|
||||
re = constrain(re, 1, self.rows)
|
||||
cs = constrain(cs, 1, self.cols)
|
||||
ce = constrain(ce, 1, self.cols)
|
||||
if rs > re:
|
||||
rs, re = re, rs
|
||||
if cs > ce:
|
||||
cs, ce = ce, cs
|
||||
sc = []
|
||||
for r in range(rs, re + 1):
|
||||
line = ''
|
||||
for c in range(cs, ce + 1):
|
||||
ch = self.get_abs(r, c)
|
||||
line = line + ch
|
||||
sc.append(line)
|
||||
return sc
|
||||
|
||||
def cursor_constrain(self):
|
||||
"""This keeps the cursor within the screen area.
|
||||
"""
|
||||
|
||||
self.cur_r = constrain(self.cur_r, 1, self.rows)
|
||||
self.cur_c = constrain(self.cur_c, 1, self.cols)
|
||||
|
||||
def cursor_home(self, r=1, c=1): # <ESC>[{ROW};{COLUMN}H
|
||||
|
||||
self.cur_r = r
|
||||
self.cur_c = c
|
||||
self.cursor_constrain()
|
||||
|
||||
def cursor_back(self, count=1): # <ESC>[{COUNT}D (not confused with down)
|
||||
|
||||
self.cur_c = self.cur_c - count
|
||||
self.cursor_constrain()
|
||||
|
||||
def cursor_down(self, count=1): # <ESC>[{COUNT}B (not confused with back)
|
||||
|
||||
self.cur_r = self.cur_r + count
|
||||
self.cursor_constrain()
|
||||
|
||||
def cursor_forward(self, count=1): # <ESC>[{COUNT}C
|
||||
|
||||
self.cur_c = self.cur_c + count
|
||||
self.cursor_constrain()
|
||||
|
||||
def cursor_up(self, count=1): # <ESC>[{COUNT}A
|
||||
|
||||
self.cur_r = self.cur_r - count
|
||||
self.cursor_constrain()
|
||||
|
||||
def cursor_up_reverse(self): # <ESC> M (called RI -- Reverse Index)
|
||||
|
||||
old_r = self.cur_r
|
||||
self.cursor_up()
|
||||
if old_r == self.cur_r:
|
||||
self.scroll_up()
|
||||
|
||||
def cursor_force_position(self, r, c): # <ESC>[{ROW};{COLUMN}f
|
||||
"""Identical to Cursor Home."""
|
||||
|
||||
self.cursor_home(r, c)
|
||||
|
||||
def cursor_save(self): # <ESC>[s
|
||||
"""Save current cursor position."""
|
||||
|
||||
self.cursor_save_attrs()
|
||||
|
||||
def cursor_unsave(self): # <ESC>[u
|
||||
"""Restores cursor position after a Save Cursor."""
|
||||
|
||||
self.cursor_restore_attrs()
|
||||
|
||||
def cursor_save_attrs(self): # <ESC>7
|
||||
"""Save current cursor position."""
|
||||
|
||||
self.cur_saved_r = self.cur_r
|
||||
self.cur_saved_c = self.cur_c
|
||||
|
||||
def cursor_restore_attrs(self): # <ESC>8
|
||||
"""Restores cursor position after a Save Cursor."""
|
||||
|
||||
self.cursor_home(self.cur_saved_r, self.cur_saved_c)
|
||||
|
||||
def scroll_constrain(self):
|
||||
"""This keeps the scroll region within the screen region."""
|
||||
|
||||
if self.scroll_row_start <= 0:
|
||||
self.scroll_row_start = 1
|
||||
if self.scroll_row_end > self.rows:
|
||||
self.scroll_row_end = self.rows
|
||||
|
||||
def scroll_screen(self): # <ESC>[r
|
||||
"""Enable scrolling for entire display."""
|
||||
|
||||
self.scroll_row_start = 1
|
||||
self.scroll_row_end = self.rows
|
||||
|
||||
def scroll_screen_rows(self, rs, re): # <ESC>[{start};{end}r
|
||||
"""Enable scrolling from row {start} to row {end}."""
|
||||
|
||||
self.scroll_row_start = rs
|
||||
self.scroll_row_end = re
|
||||
self.scroll_constrain()
|
||||
|
||||
def scroll_down(self): # <ESC>D
|
||||
"""Scroll display down one line."""
|
||||
|
||||
# Screen is indexed from 1, but arrays are indexed from 0.
|
||||
s = self.scroll_row_start - 1
|
||||
e = self.scroll_row_end - 1
|
||||
self.w[s + 1:e + 1] = copy.deepcopy(self.w[s:e])
|
||||
|
||||
def scroll_up(self): # <ESC>M
|
||||
"""Scroll display up one line."""
|
||||
|
||||
# Screen is indexed from 1, but arrays are indexed from 0.
|
||||
s = self.scroll_row_start - 1
|
||||
e = self.scroll_row_end - 1
|
||||
self.w[s:e] = copy.deepcopy(self.w[s + 1:e + 1])
|
||||
|
||||
def erase_end_of_line(self): # <ESC>[0K -or- <ESC>[K
|
||||
"""Erases from the current cursor position to the end of the current
|
||||
line."""
|
||||
|
||||
self.fill_region(self.cur_r, self.cur_c, self.cur_r, self.cols)
|
||||
|
||||
def erase_start_of_line(self): # <ESC>[1K
|
||||
"""Erases from the current cursor position to the start of the current
|
||||
line."""
|
||||
|
||||
self.fill_region(self.cur_r, 1, self.cur_r, self.cur_c)
|
||||
|
||||
def erase_line(self): # <ESC>[2K
|
||||
"""Erases the entire current line."""
|
||||
|
||||
self.fill_region(self.cur_r, 1, self.cur_r, self.cols)
|
||||
|
||||
def erase_down(self): # <ESC>[0J -or- <ESC>[J
|
||||
"""Erases the screen from the current line down to the bottom of the
|
||||
screen."""
|
||||
|
||||
self.erase_end_of_line()
|
||||
self.fill_region(self.cur_r + 1, 1, self.rows, self.cols)
|
||||
|
||||
def erase_up(self): # <ESC>[1J
|
||||
"""Erases the screen from the current line up to the top of the
|
||||
screen."""
|
||||
|
||||
self.erase_start_of_line()
|
||||
self.fill_region(self.cur_r - 1, 1, 1, self.cols)
|
||||
|
||||
def erase_screen(self): # <ESC>[2J
|
||||
"""Erases the screen with the background color."""
|
||||
|
||||
self.fill()
|
||||
|
||||
def set_tab(self): # <ESC>H
|
||||
"""Sets a tab at the current position."""
|
||||
|
||||
pass
|
||||
|
||||
def clear_tab(self): # <ESC>[g
|
||||
"""Clears tab at the current position."""
|
||||
|
||||
pass
|
||||
|
||||
def clear_all_tabs(self): # <ESC>[3g
|
||||
"""Clears all tabs."""
|
||||
|
||||
pass
|
||||
|
||||
# Insert line Esc [ Pn L
|
||||
# Delete line Esc [ Pn M
|
||||
# Delete character Esc [ Pn P
|
||||
# Scrolling region Esc [ Pn(top);Pn(bot) r
|
|
@ -1,36 +0,0 @@
|
|||
'''
|
||||
$Revision: 485 $
|
||||
$Date: 2007-07-12 15:23:15 -0700 (Thu, 12 Jul 2007) $
|
||||
'''
|
||||
from distutils.core import setup
|
||||
setup(name='pexpect',
|
||||
version='2.4',
|
||||
py_modules=['pexpect', 'pxssh', 'fdpexpect', 'FSM', 'screen', 'ANSI'],
|
||||
description='Pexpect is a pure Python Expect. It allows easy control of other applications.',
|
||||
author='Noah Spurrier',
|
||||
author_email='noah@noah.org',
|
||||
url='http://pexpect.sourceforge.net/',
|
||||
license='MIT license',
|
||||
platforms='UNIX',
|
||||
)
|
||||
|
||||
# classifiers = [
|
||||
# 'Development Status :: 4 - Beta',
|
||||
# 'Environment :: Console',
|
||||
# 'Environment :: Console (Text Based)',
|
||||
# 'Intended Audience :: Developers',
|
||||
# 'Intended Audience :: System Administrators',
|
||||
# 'Intended Audience :: Quality Engineers',
|
||||
# 'License :: OSI Approved :: Python Software Foundation License',
|
||||
# 'Operating System :: POSIX',
|
||||
# 'Operating System :: MacOS :: MacOS X',
|
||||
# 'Programming Language :: Python',
|
||||
# 'Topic :: Software Development',
|
||||
# 'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
# 'Topic :: Software Development :: Quality Assurance',
|
||||
# 'Topic :: Software Development :: Testing',
|
||||
# 'Topic :: System, System :: Archiving :: Packaging, System :: Installation/Setup',
|
||||
# 'Topic :: System :: Shells',
|
||||
# 'Topic :: System :: Software Distribution',
|
||||
# 'Topic :: Terminals, Utilities',
|
||||
# ],
|
|
@ -0,0 +1,11 @@
|
|||
*.pyc
|
||||
doc/_build
|
||||
tests/log
|
||||
build/
|
||||
dist/
|
||||
MANIFEST
|
||||
*~
|
||||
.coverage*
|
||||
htmlcov
|
||||
*.egg-info/
|
||||
.cache/
|
|
@ -0,0 +1,31 @@
|
|||
language: python
|
||||
|
||||
python:
|
||||
- 2.7
|
||||
- 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
|
||||
- pip install coveralls pytest-cov ptyprocess
|
||||
|
||||
script:
|
||||
- ./tools/display-sighandlers.py
|
||||
- ./tools/display-terminalinfo.py
|
||||
- py.test --cov pexpect --cov-config .coveragerc
|
||||
|
||||
after_success:
|
||||
- coverage combine
|
||||
- coveralls
|
||||
|
||||
# Use new Travis stack, should be faster
|
||||
sudo: false
|
|
@ -0,0 +1,12 @@
|
|||
To run the tests, use `py.test <http://pytest.org/latest/>`_::
|
||||
|
||||
py.test tests
|
||||
|
||||
The tests are all located in the tests/ directory. To add a new unit
|
||||
test all you have to do is create the file in the tests/ directory with a
|
||||
filename in this format::
|
||||
|
||||
test_*.py
|
||||
|
||||
New test case classes may wish to inherit from ``PexpectTestCase.PexpectTestCase``
|
||||
in the tests directory, which sets up some convenient functionality.
|
|
@ -0,0 +1,20 @@
|
|||
ISC LICENSE
|
||||
|
||||
This license is approved by the OSI and FSF as GPL-compatible.
|
||||
http://opensource.org/licenses/isc-license.txt
|
||||
|
||||
Copyright (c) 2013-2014, Pexpect development team
|
||||
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.
|
||||
|
|
@ -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 *~
|
|
@ -0,0 +1,55 @@
|
|||
.. image:: https://travis-ci.org/pexpect/pexpect.svg?branch=master
|
||||
:target: https://travis-ci.org/pexpect/pexpect
|
||||
:align: right
|
||||
:alt: Build status
|
||||
|
||||
Pexpect is a Pure Python Expect-like module
|
||||
|
||||
Pexpect makes Python a better tool for controlling other applications.
|
||||
|
||||
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.
|
||||
|
||||
If you want to work with the development version of the source code then please
|
||||
read the DEVELOPERS.rst document in the root of the source code tree.
|
||||
|
||||
Free, open source, and all that good stuff.
|
||||
|
||||
You can install Pexpect using pip::
|
||||
|
||||
pip install pexpect
|
||||
|
||||
`Docs on ReadTheDocs <https://pexpect.readthedocs.io/>`_
|
||||
|
||||
PEXPECT LICENSE::
|
||||
|
||||
http://opensource.org/licenses/isc-license.txt
|
||||
|
||||
Copyright (c) 2013-2016, Pexpect development team
|
||||
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.
|
||||
|
||||
This license is approved by the OSI and FSF as GPL-compatible.
|
|
@ -0,0 +1,145 @@
|
|||
FAQ
|
||||
===
|
||||
|
||||
**Q: Where can I get help with pexpect? Is there a mailing list?**
|
||||
|
||||
A: You can use the `pexpect tag on Stackoverflow <http://stackoverflow.com/questions/tagged/pexpect>`__
|
||||
to ask questions specifically related to Pexpect. For more general Python
|
||||
support, there's the python-list_ mailing list, and the `#python`_
|
||||
IRC channel. Please refrain from using github for general
|
||||
python or systems scripting support.
|
||||
|
||||
.. _python-list: https://mail.python.org/mailman/listinfo/python-list
|
||||
.. _#python: https://www.python.org/community/irc/
|
||||
|
||||
**Q: Why don't shell pipe and redirect (| and >) work when I spawn a command?**
|
||||
|
||||
A: Remember that Pexpect does NOT interpret shell meta characters such as
|
||||
redirect, pipe, or wild cards (``>``, ``|``, or ``*``). That's done by a shell not
|
||||
the command you are spawning. 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::
|
||||
|
||||
child = pexpect.spawn('/bin/bash -c "ls -l | grep LOG > log_list.txt"')
|
||||
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::
|
||||
|
||||
shell_cmd = 'ls -l | grep LOG > log_list.txt'
|
||||
child = pexpect.spawn('/bin/bash', ['-c', shell_cmd])
|
||||
child.expect(pexpect.EOF)
|
||||
|
||||
**Q: The `before` and `after` properties sound weird.**
|
||||
|
||||
A: This is how the -B and -A options in grep works, so that made it
|
||||
easier for me to remember. Whatever makes my life easier is what's best.
|
||||
Originally I was going to model Pexpect after Expect, but then I found
|
||||
that I didn't actually like the way Expect did some things. It was more
|
||||
confusing. The `after` property can be a little confusing at first,
|
||||
because it will actually include the matched string. The `after` means
|
||||
after the point of match, not after the matched string.
|
||||
|
||||
**Q: Why not just use Expect?**
|
||||
|
||||
A: I love it. It's great. I has bailed me out of some real jams, but I
|
||||
wanted something that would do 90% of what I need from Expect; be 10% of
|
||||
the size; and allow me to write my code in Python instead of TCL.
|
||||
Pexpect is not nearly as big as Expect, but Pexpect does everything I
|
||||
have ever used Expect for.
|
||||
|
||||
.. _whynotpipe:
|
||||
|
||||
**Q: Why not just use a pipe (popen())?**
|
||||
|
||||
A: A pipe works fine for getting the output to non-interactive programs.
|
||||
If you just want to get the output from ls, uname, or ping then this
|
||||
works. Pipes do not work very well for interactive programs and pipes
|
||||
will almost certainly fail for most applications that ask for passwords
|
||||
such as telnet, ftp, or ssh.
|
||||
|
||||
There are two reasons for this.
|
||||
|
||||
* First an application may bypass stdout and print directly to its
|
||||
controlling TTY. Something like SSH will do this when it asks you for
|
||||
a password. This is why you cannot redirect the password prompt because
|
||||
it does not go through stdout or stderr.
|
||||
|
||||
* The second reason is because most applications are built using the C
|
||||
Standard IO Library (anything that uses ``#include <stdio.h>``). One
|
||||
of the features of the stdio library is that it buffers all input and
|
||||
output. Normally output is line buffered when a program is printing to
|
||||
a TTY (your terminal screen). Everytime the program prints a line-feed
|
||||
the currently buffered data will get printed to your screen. The
|
||||
problem comes when you connect a pipe. The stdio library is smart and
|
||||
can tell that it is printing to a pipe instead of a TTY. In that case
|
||||
it switches from line buffer mode to block buffered. In this mode the
|
||||
currently buffered data is flushed when the buffer is full. This
|
||||
causes most interactive programs to deadlock. Block buffering is more
|
||||
efficient when writing to disks and pipes. Take the situation where a
|
||||
program prints a message ``"Enter your user name:\n"`` and then waits
|
||||
for you type type something. In block buffered mode, the stdio library
|
||||
will not put the message into the pipe even though a linefeed is
|
||||
printed. The result is that you never receive the message, yet the
|
||||
child application will sit and wait for you to type a response. Don't
|
||||
confuse the stdio lib's buffer with the pipe's buffer. The pipe buffer
|
||||
is another area that can cause problems. You could flush the input
|
||||
side of a pipe, whereas you have no control over the stdio library buffer.
|
||||
|
||||
More information: the Standard IO library has three states for a
|
||||
``FILE *``. These are: _IOFBF for block buffered; _IOLBF for line buffered;
|
||||
and _IONBF for unbuffered. The STDIO lib will use block buffering when
|
||||
talking to a block file descriptor such as a pipe. This is usually not
|
||||
helpful for interactive programs. Short of recompiling your program to
|
||||
include fflush() everywhere or recompiling a custom stdio library there
|
||||
is not much a controlling application can do about this if talking over
|
||||
a pipe.
|
||||
|
||||
The program may have put data in its output that remains unflushed
|
||||
because the output buffer is not full; then the program will go and
|
||||
deadlock while waiting for input -- because you never send it any
|
||||
because you are still waiting for its output (still stuck in the STDIO's
|
||||
output buffer).
|
||||
|
||||
The answer is to use a pseudo-tty. A TTY device will force line
|
||||
buffering (as opposed to block buffering). Line buffering means that you
|
||||
will get each line when the child program sends a line feed. This
|
||||
corresponds to the way most interactive programs operate -- send a line
|
||||
of output then wait for a line of input.
|
||||
|
||||
I put "answer" in quotes because it's ugly solution and because there is
|
||||
no POSIX standard for pseudo-TTY devices (even though they have a TTY
|
||||
standard...). What would make more sense to me would be to have some way
|
||||
to set a mode on a file descriptor so that it will tell the STDIO to be
|
||||
line-buffered. I have investigated, and I don't think there is a way to
|
||||
set the buffered state of a child process. The STDIO Library does not
|
||||
maintain any external state in the kernel or whatnot, so I don't think
|
||||
there is any way for you to alter it. I'm not quite sure how this
|
||||
line-buffered/block-buffered state change happens internally in the
|
||||
STDIO library. I think the STDIO lib looks at the file descriptor and
|
||||
decides to change behavior based on whether it's a TTY or a block file
|
||||
(see isatty()).
|
||||
|
||||
I hope that this qualifies as helpful. Don't use a pipe to control
|
||||
another application.
|
||||
|
||||
**Q: Can I do screen scraping with this thing?**
|
||||
|
||||
A: That depends. If your application just does line-oriented output then
|
||||
this is easy. If a program emits many terminal sequences, from video
|
||||
attributes to screen addressing, such as programs using curses, then
|
||||
it may become very difficult to ascertain what text is displayed on a screen.
|
||||
|
||||
We suggest using the `pyte <https://github.com/selectel/pyte>`_ library to
|
||||
screen-scrape. The module :mod:`pexpect.ANSI` released with previous versions
|
||||
of pexpect is now marked deprecated and may be removed in the future.
|
||||
|
||||
**Q: I get strange behavior with pexect and gevent**
|
||||
|
||||
A: Pexpect uses fork(2), exec(2), select(2), waitpid(2), and implements its
|
||||
own selector in expect family of calls. pexpect has been known to misbehave
|
||||
when paired with gevent. A solution might be to isolate your pexpect
|
||||
dependent code from any frameworks that manipulate event selection behavior
|
||||
by running it in an another process entirely.
|
|
@ -0,0 +1,153 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Pexpect.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Pexpect.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/Pexpect"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Pexpect"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
|
@ -0,0 +1,20 @@
|
|||
fdpexpect - use pexpect with a file descriptor
|
||||
==============================================
|
||||
|
||||
.. automodule:: pexpect.fdpexpect
|
||||
|
||||
fdspawn class
|
||||
-------------
|
||||
|
||||
.. autoclass:: fdspawn
|
||||
:show-inheritance:
|
||||
|
||||
.. automethod:: __init__
|
||||
.. automethod:: isalive
|
||||
.. automethod:: close
|
||||
|
||||
.. method:: expect
|
||||
expect_exact
|
||||
expect_list
|
||||
|
||||
As :class:`pexpect.spawn`.
|
|
@ -0,0 +1,18 @@
|
|||
API documentation
|
||||
=================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
pexpect
|
||||
fdpexpect
|
||||
popen_spawn
|
||||
replwrap
|
||||
pxssh
|
||||
|
||||
The modules ``pexpect.screen`` and ``pexpect.ANSI`` have been deprecated in
|
||||
Pexpect version 4. They were separate from the main use cases for Pexpect, and
|
||||
there are better maintained Python terminal emulator packages, such as
|
||||
`pyte <https://pypi.python.org/pypi/pyte>`__.
|
||||
These modules are still present for now, but we don't advise using them in new
|
||||
code.
|
|
@ -0,0 +1,115 @@
|
|||
Core pexpect components
|
||||
=======================
|
||||
|
||||
.. automodule:: pexpect
|
||||
|
||||
spawn class
|
||||
-----------
|
||||
|
||||
.. autoclass:: spawn
|
||||
|
||||
.. automethod:: __init__
|
||||
.. automethod:: expect
|
||||
.. automethod:: expect_exact
|
||||
.. automethod:: expect_list
|
||||
.. automethod:: compile_pattern_list
|
||||
.. automethod:: send
|
||||
.. automethod:: sendline
|
||||
.. automethod:: write
|
||||
.. automethod:: writelines
|
||||
.. automethod:: sendcontrol
|
||||
.. automethod:: sendeof
|
||||
.. automethod:: sendintr
|
||||
.. automethod:: read
|
||||
.. automethod:: readline
|
||||
.. automethod:: read_nonblocking
|
||||
.. automethod:: eof
|
||||
.. automethod:: interact
|
||||
|
||||
.. attribute:: logfile
|
||||
logfile_read
|
||||
logfile_send
|
||||
|
||||
Set these to a Python file object (or :data:`sys.stdout`) to log all
|
||||
communication, data read from the child process, or data sent to the child
|
||||
process.
|
||||
|
||||
.. note::
|
||||
|
||||
With :class:`spawn` in bytes mode, the log files should be open for
|
||||
writing binary data. In unicode mode, they should
|
||||
be open for writing unicode text. See :ref:`unicode`.
|
||||
|
||||
Controlling the child process
|
||||
`````````````````````````````
|
||||
|
||||
.. class:: spawn
|
||||
|
||||
.. automethod:: kill
|
||||
.. automethod:: terminate
|
||||
.. automethod:: isalive
|
||||
.. automethod:: wait
|
||||
.. automethod:: close
|
||||
.. automethod:: getwinsize
|
||||
.. automethod:: setwinsize
|
||||
.. automethod:: getecho
|
||||
.. automethod:: setecho
|
||||
.. automethod:: waitnoecho
|
||||
|
||||
.. attribute:: pid
|
||||
|
||||
The process ID of the child process.
|
||||
|
||||
.. attribute:: child_fd
|
||||
|
||||
The file descriptor used to communicate with the child process.
|
||||
|
||||
.. _unicode:
|
||||
|
||||
Handling unicode
|
||||
````````````````
|
||||
|
||||
By default, :class:`spawn` is a bytes interface: its read methods return bytes,
|
||||
and its write/send and expect methods expect bytes. If you pass the *encoding*
|
||||
parameter to the constructor, it will instead act as a unicode interface:
|
||||
strings you send will be encoded using that encoding, and bytes received will
|
||||
be decoded before returning them to you. In this mode, patterns for
|
||||
:meth:`~spawn.expect` and :meth:`~spawn.expect_exact` should also be unicode.
|
||||
|
||||
.. versionchanged:: 4.0
|
||||
|
||||
:class:`spawn` provides both the bytes and unicode interfaces. In Pexpect
|
||||
3.x, the unicode interface was provided by a separate ``spawnu`` class.
|
||||
|
||||
For backwards compatibility, some Unicode is allowed in bytes mode: the
|
||||
send methods will encode arbitrary unicode as UTF-8 before sending it to the
|
||||
child process, and its expect methods can accept ascii-only unicode strings.
|
||||
|
||||
.. note::
|
||||
|
||||
Unicode handling with pexpect works the same way on Python 2 and 3, despite
|
||||
the difference in names. I.e.:
|
||||
|
||||
- Bytes mode works with ``str`` on Python 2, and :class:`bytes` on Python 3,
|
||||
- Unicode mode works with ``unicode`` on Python 2, and :class:`str` on Python 3.
|
||||
|
||||
run function
|
||||
------------
|
||||
|
||||
.. autofunction:: run
|
||||
|
||||
Exceptions
|
||||
----------
|
||||
|
||||
.. autoclass:: EOF
|
||||
|
||||
.. autoclass:: TIMEOUT
|
||||
|
||||
.. autoclass:: ExceptionPexpect
|
||||
|
||||
Utility functions
|
||||
-----------------
|
||||
|
||||
.. autofunction:: which
|
||||
|
||||
.. autofunction:: split_command_line
|
|
@ -0,0 +1,24 @@
|
|||
popen_spawn - use pexpect with a piped subprocess
|
||||
=================================================
|
||||
|
||||
.. automodule:: pexpect.popen_spawn
|
||||
|
||||
PopenSpawn class
|
||||
----------------
|
||||
|
||||
.. autoclass:: PopenSpawn
|
||||
|
||||
.. automethod:: __init__
|
||||
.. automethod:: send
|
||||
.. automethod:: sendline
|
||||
.. automethod:: write
|
||||
.. automethod:: writelines
|
||||
.. automethod:: kill
|
||||
.. automethod:: sendeof
|
||||
.. automethod:: wait
|
||||
|
||||
.. method:: expect
|
||||
expect_exact
|
||||
expect_list
|
||||
|
||||
As :class:`pexpect.spawn`.
|
|
@ -0,0 +1,34 @@
|
|||
pxssh - control an SSH session
|
||||
==============================
|
||||
|
||||
.. automodule:: pexpect.pxssh
|
||||
|
||||
.. autoclass:: ExceptionPxssh
|
||||
|
||||
pxssh class
|
||||
-----------
|
||||
|
||||
.. autoclass:: pxssh
|
||||
|
||||
.. automethod:: __init__
|
||||
|
||||
.. attribute:: PROMPT
|
||||
|
||||
The regex pattern to search for to find the prompt. If you call :meth:`login`
|
||||
with ``auto_prompt_reset=False``, you must set this attribute manually.
|
||||
|
||||
.. attribute:: force_password
|
||||
|
||||
If this is set to True, public key authentication is disabled, forcing the
|
||||
server to ask for a password. Note that the sysadmin can disable password
|
||||
logins, in which case this won't work.
|
||||
|
||||
.. attribute:: options
|
||||
|
||||
The dictionary of user specified SSH options, eg, ``options = dict(StrictHostKeyChecking="no", UserKnownHostsFile="/dev/null")``
|
||||
|
||||
.. automethod:: login
|
||||
.. automethod:: logout
|
||||
.. automethod:: prompt
|
||||
.. automethod:: sync_original_prompt
|
||||
.. automethod:: set_unique_prompt
|
|
@ -0,0 +1,26 @@
|
|||
replwrap - Control read-eval-print-loops
|
||||
========================================
|
||||
|
||||
.. automodule:: pexpect.replwrap
|
||||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
.. autoclass:: REPLWrapper
|
||||
|
||||
.. automethod:: run_command
|
||||
|
||||
.. data:: PEXPECT_PROMPT
|
||||
|
||||
A string that can be used as a prompt, and is unlikely to be found in output.
|
||||
|
||||
Using the objects above, it is easy to wrap a REPL. For instance, to use a
|
||||
Python shell::
|
||||
|
||||
py = REPLWrapper("python", ">>> ", "import sys; sys.ps1={!r}; sys.ps2={!r}")
|
||||
py.run_command("4+7")
|
||||
|
||||
Convenience functions are provided for Python and bash shells:
|
||||
|
||||
.. autofunction:: python
|
||||
|
||||
.. autofunction:: bash
|
|
@ -0,0 +1,101 @@
|
|||
Common problems
|
||||
===============
|
||||
|
||||
Threads
|
||||
-------
|
||||
|
||||
On Linux (RH 8) you cannot spawn a child from a different thread and pass the
|
||||
handle back to a worker thread. The child is successfully spawned but you can't
|
||||
interact with it. The only way to make it work is to spawn and interact with the
|
||||
child all in the same thread. [Adam Kerrison]
|
||||
|
||||
Timing issue with send() and sendline()
|
||||
---------------------------------------
|
||||
|
||||
This problem has been addressed and should not affect most users.
|
||||
|
||||
It is sometimes possible to read an echo of the string sent with
|
||||
:meth:`~pexpect.spawn.send` and :meth:`~pexpect.spawn.sendline`. If you call
|
||||
:meth:`~pexpect.spawn.send` and then immediately call :meth:`~pexpect.spawn.readline`,
|
||||
you may get part of your output echoed back. You may read back what you just
|
||||
wrote even if the child application does not explicitly echo it. Timing is
|
||||
critical. This could be a security issue when talking to an application that
|
||||
asks for a password; otherwise, this does not seem like a big deal. But why do
|
||||
TTYs do this?
|
||||
|
||||
People usually report this when they are trying to control SSH or some other
|
||||
login. For example, if your code looks something like this::
|
||||
|
||||
child.expect ('[pP]assword:')
|
||||
child.sendline (my_password)
|
||||
|
||||
|
||||
1. SSH prints "password:" prompt to the user.
|
||||
2. SSH turns off echo on the TTY device.
|
||||
3. SSH waits for user to enter a password.
|
||||
|
||||
When scripting with Pexpect what can happen is that Pexpect will respond to the
|
||||
"password:" prompt before SSH has had time to turn off TTY echo. In other words,
|
||||
Pexpect sends the password between steps 1. and 2., so the password gets echoed
|
||||
back to the TTY. I would call this an SSH bug.
|
||||
|
||||
Pexpect now automatically adds a short delay before sending data to a child
|
||||
process. This more closely mimics what happens in the usual human-to-app
|
||||
interaction. The delay can be tuned with the ``delaybeforesend`` attribute of the
|
||||
spawn class. In general, this fixes the problem for everyone and so this should
|
||||
not be an issue for most users. For some applications you might with to turn it
|
||||
off::
|
||||
|
||||
child = pexpect.spawn ("ssh user@example.com")
|
||||
child.delaybeforesend = None
|
||||
|
||||
Truncated output just before child exits
|
||||
----------------------------------------
|
||||
|
||||
So far I have seen this only on older versions of Apple's MacOS X. If the child
|
||||
application quits it may not flush its output buffer. This means that your
|
||||
Pexpect application will receive an EOF even though it should have received a
|
||||
little more data before the child died. This is not generally a problem when
|
||||
talking to interactive child applications. One example where it is a problem is
|
||||
when trying to read output from a program like *ls*. You may receive most of the
|
||||
directory listing, but the last few lines will get lost before you receive an EOF.
|
||||
The reason for this is that *ls* runs; completes its task; and then exits. The
|
||||
buffer is not flushed before exit so the last few lines are lost. The following
|
||||
example demonstrates the problem::
|
||||
|
||||
child = pexpect.spawn('ls -l')
|
||||
child.expect(pexpect.EOF)
|
||||
print child.before
|
||||
|
||||
Controlling SSH on Solaris
|
||||
--------------------------
|
||||
|
||||
Pexpect does not yet work perfectly on Solaris. One common problem is that SSH
|
||||
sometimes will not allow TTY password authentication. For example, you may
|
||||
expect SSH to ask you for a password using code like this::
|
||||
|
||||
child = pexpect.spawn('ssh user@example.com')
|
||||
child.expect('password')
|
||||
child.sendline('mypassword')
|
||||
|
||||
You may see the following error come back from a spawned child SSH::
|
||||
|
||||
Permission denied (publickey,keyboard-interactive).
|
||||
|
||||
This means that SSH thinks it can't access the TTY to ask you for your password.
|
||||
The only solution I have found is to use public key authentication with SSH.
|
||||
This bypasses the need for a password. I'm not happy with this solution. The
|
||||
problem is due to poor support for Solaris Pseudo TTYs in the Python Standard
|
||||
Library.
|
||||
|
||||
child does not receive full input, emits BEL
|
||||
--------------------------------------------
|
||||
|
||||
You may notice when running for example cat(1) or base64(1), when sending a
|
||||
very long input line, that it is not fully received, and the BEL ('\a') may
|
||||
be found in output.
|
||||
|
||||
By default the child terminal matches the parent, which is often in "canonical
|
||||
mode processing". You may wish to disable this mode. The exact limit of a line
|
||||
varies by operating system, and details of disabling canonical mode may be
|
||||
found in the docstring of :meth:`~pexpect.spawn.send`.
|
|
@ -0,0 +1,250 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Pexpect documentation build configuration file, created by
|
||||
# sphinx-quickstart on Tue Sep 17 11:05:11 2013.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath('sphinxext'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.viewcode', 'github', # for easy GitHub links
|
||||
]
|
||||
|
||||
github_project_url = "https://github.com/pexpect/pexpect"
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Pexpect'
|
||||
copyright = u'2013, Noah Spurrier and contributors'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '4.6'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = version
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Pexpectdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'Pexpect.tex', u'Pexpect Documentation',
|
||||
u'Noah Spurrier and contributors', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'pexpect', u'Pexpect Documentation',
|
||||
[u'Noah Spurrier and contributors'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'Pexpect', u'Pexpect Documentation',
|
||||
u'Noah Spurrier and contributors', 'Pexpect', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {'http://docs.python.org/3/': None}
|
|
@ -0,0 +1,63 @@
|
|||
Examples
|
||||
========
|
||||
|
||||
Under the distribution tarball directory you should find an "examples" directory.
|
||||
This is the best way to learn to use Pexpect. See the descriptions of Pexpect
|
||||
Examples.
|
||||
|
||||
`topip.py <https://github.com/pexpect/pexpect/blob/master/examples/topip.py>`_
|
||||
This runs `netstat` on a local or remote server. It calculates some simple
|
||||
statistical information on the number of external inet connections. This can
|
||||
be used to detect if one IP address is taking up an excessive number of
|
||||
connections. It can also send an email alert if a given IP address exceeds a
|
||||
threshold between runs of the script. This script can be used as a drop-in
|
||||
Munin plugin or it can be used stand-alone from cron. I used this on a busy
|
||||
web server that would sometimes get hit with denial of service attacks. This
|
||||
made it easy to see if a script was opening many multiple connections. A
|
||||
typical browser would open fewer than 10 connections at once. A script might
|
||||
open over 100 simultaneous connections.
|
||||
|
||||
`hive.py <https://github.com/pexpect/pexpect/blob/master/examples/hive.py>`_
|
||||
This script creates SSH connections to a list of hosts that you provide.
|
||||
Then you are given a command line prompt. Each shell command that you
|
||||
enter is sent to all the hosts. The response from each host is collected
|
||||
and printed. For example, you could connect to a dozen different
|
||||
machines and reboot them all at once.
|
||||
|
||||
`script.py <https://github.com/pexpect/pexpect/blob/master/examples/script.py>`_
|
||||
This implements a command similar to the classic BSD "script" command.
|
||||
This will start a subshell and log all input and output to a file.
|
||||
This demonstrates the :meth:`~pexpect.spawn.interact` method of Pexpect.
|
||||
|
||||
`ftp.py <https://github.com/pexpect/pexpect/blob/master/examples/ftp.py>`_
|
||||
This demonstrates an FTP "bookmark". This connects to an ftp site;
|
||||
does a few ftp tasks; and then gives the user interactive control over
|
||||
the session. In this case the "bookmark" is to a directory on the
|
||||
OpenBSD ftp server. It puts you in the i386 packages directory. You
|
||||
can easily modify this for other sites. This demonstrates the
|
||||
:meth:`~pexpect.spawn.interact` method of Pexpect.
|
||||
|
||||
`monitor.py <https://github.com/pexpect/pexpect/blob/master/examples/monitor.py>`_
|
||||
This runs a sequence of commands on a remote host using SSH. It runs a
|
||||
simple system checks such as uptime and free to monitor the state of
|
||||
the remote host.
|
||||
|
||||
`passmass.py <https://github.com/pexpect/pexpect/blob/master/examples/passmass.py>`_
|
||||
This will login to each given server and change the password of the
|
||||
given user. This demonstrates scripting logins and passwords.
|
||||
|
||||
`python.py <https://github.com/pexpect/pexpect/blob/master/examples/python.py>`_
|
||||
This starts the python interpreter and prints the greeting message
|
||||
backwards. It then gives the user iteractive control of Python. It's
|
||||
pretty useless!
|
||||
|
||||
`ssh_tunnel.py <https://github.com/pexpect/pexpect/blob/master/examples/ssh_tunnel.py>`_
|
||||
This starts an SSH tunnel to a remote machine. It monitors the
|
||||
connection and restarts the tunnel if it goes down.
|
||||
|
||||
`uptime.py <https://github.com/pexpect/pexpect/blob/master/examples/uptime.py>`_
|
||||
This will run the uptime command and parse the output into variables.
|
||||
This demonstrates using a single regular expression to match the
|
||||
output of a command and capturing different variable in match groups.
|
||||
The grouping regular expression handles a wide variety of different
|
||||
uptime formats.
|
|
@ -0,0 +1,338 @@
|
|||
History
|
||||
=======
|
||||
|
||||
Releases
|
||||
--------
|
||||
|
||||
Version 4.6
|
||||
```````````
|
||||
|
||||
* The :meth:`.pxssh.login` method now supports an ``ssh_config`` parameter,
|
||||
which can be used to specify a file path to an SSH config file
|
||||
(:ghpull:`490`).
|
||||
* Improved compatability for the ``crlf`` parameter of :class:`~.PopenSpawn`
|
||||
(:ghpull:`493`)
|
||||
* Fixed an issue in read timeout handling when using :class:`~.spawn` and
|
||||
:class:`~.fdspawn` with the ``use_poll`` parameter (:ghpull:`492`).
|
||||
|
||||
Version 4.5
|
||||
```````````
|
||||
|
||||
* :class:`~.spawn` and :class:`~.fdspawn` now have a ``use_poll`` parameter.
|
||||
If this is True, they will use :func:`select.poll` instead of :func:`select.select`.
|
||||
``poll()`` allows file descriptors above 1024, but it must be explicitly
|
||||
enabled due to compatibility concerns (:ghpull:`474`).
|
||||
* The :meth:`.pxssh.login` method has several new and changed options:
|
||||
|
||||
* The option ``password_regex`` allows changing
|
||||
the password prompt regex, for servers that include ``password:`` in a banner
|
||||
before reaching a prompt (:ghpull:`468`).
|
||||
* :meth:`~.pxssh.login` now allows for setting up SSH tunnels to be requested once
|
||||
logged in to the remote server. This option is ``ssh_tunnels`` (:ghpull:`473`).
|
||||
The structure should be like this::
|
||||
|
||||
{
|
||||
'local': ['2424:localhost:22'], # Local SSH tunnels
|
||||
'remote': ['2525:localhost:22'], # Remote SSH tunnels
|
||||
'dynamic': [8888], # Dynamic/SOCKS tunnels
|
||||
}
|
||||
|
||||
* The option ``spawn_local_ssh=False`` allows subsequent logins from the
|
||||
remote session and treats the session as if it was local (:ghpull:`472`).
|
||||
* Setting ``sync_original_prompt=False`` will prevent changing the prompt to
|
||||
something unique, in case the remote server is sensitive to new lines at login
|
||||
(:ghpull:`468`).
|
||||
* If ``ssh_key=True`` is passed, the SSH client forces forwarding the authentication
|
||||
agent to the remote server instead of providing a key (:ghpull:`473`).
|
||||
|
||||
Version 4.4
|
||||
```````````
|
||||
|
||||
* :class:`~.PopenSpawn` now has a ``preexec_fn`` parameter, like :class:`~.spawn`
|
||||
and :class:`subprocess.Popen`, for a function to be called in the child
|
||||
process before executing the new command. Like in ``Popen``, this works only
|
||||
in POSIX, and can cause issues if your application also uses threads
|
||||
(:ghpull:`460`).
|
||||
* Significant performance improvements when processing large amounts of data
|
||||
(:ghpull:`464`).
|
||||
* Ensure that ``spawn.closed`` gets set by :meth:`~.spawn.close`, and improve
|
||||
an example for passing ``SIGWINCH`` through to a child process (:ghpull:`466`).
|
||||
|
||||
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
|
||||
`````````````
|
||||
|
||||
* Fix to allow running ``env`` in replwrap-ed bash.
|
||||
* Raise more informative exception from pxssh if it fails to connect.
|
||||
* Change ``passmass`` example to not log passwords entered.
|
||||
|
||||
Version 4.2
|
||||
```````````
|
||||
|
||||
* Change: When an ``env`` parameter is specified to the :class:`~.spawn` or
|
||||
:class:`~.run` family of calls containing a value for ``PATH``, its value is
|
||||
used to discover the target executable from a relative path, rather than the
|
||||
current process's environment ``PATH``. This mirrors the behavior of
|
||||
:func:`subprocess.Popen` in the standard library (:ghissue:`348`).
|
||||
|
||||
* Regression: Re-introduce capability for :meth:`read_nonblocking` in class
|
||||
:class:`fdspawn` as previously supported in version 3.3 (:ghissue:`359`).
|
||||
|
||||
Version 4.0
|
||||
```````````
|
||||
|
||||
* Integration with :mod:`asyncio`: passing ``async=True`` to :meth:`~.spawn.expect`,
|
||||
:meth:`~.spawn.expect_exact` or :meth:`~.spawn.expect_list` will make them return a
|
||||
coroutine. You can get the result using ``yield from``, or wrap it in an
|
||||
:class:`asyncio.Task`. This allows the event loop to do other things while
|
||||
waiting for output that matches a pattern.
|
||||
* Experimental support for Windows (with some caveats)—see :ref:`windows`.
|
||||
* Enhancement: allow method as callbacks of argument ``events`` for
|
||||
:func:`pexpect.run` (:ghissue:`176`).
|
||||
* It is now possible to call :meth:`~.spawn.wait` multiple times, or after a process
|
||||
is already determined to be terminated without raising an exception
|
||||
(:ghpull:`211`).
|
||||
* New :class:`pexpect.spawn` keyword argument, ``dimensions=(rows, columns)``
|
||||
allows setting terminal screen dimensions before launching a program
|
||||
(:ghissue:`122`).
|
||||
* Fix regression that prevented executable, but unreadable files from
|
||||
being found when not specified by absolute path -- such as
|
||||
/usr/bin/sudo (:ghissue:`104`).
|
||||
* Fixed regression when executing pexpect with some prior releases of
|
||||
the multiprocessing module where stdin has been closed (:ghissue:`86`).
|
||||
|
||||
Backwards incompatible changes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Deprecated ``pexpect.screen`` and ``pexpect.ANSI``. Please use other packages
|
||||
such as `pyte <https://pypi.python.org/pypi/pyte>`__ to emulate a terminal.
|
||||
* Removed the independent top-level modules (``pxssh fdpexpect FSM screen ANSI``)
|
||||
which were installed alongside Pexpect. These were moved into the Pexpect
|
||||
package in 3.0, but the old names were left as aliases.
|
||||
* Child processes created by Pexpect no longer ignore SIGHUP by default: the
|
||||
``ignore_sighup`` parameter of :class:`pexpect.spawn` defaults to False. To
|
||||
get the old behaviour, pass ``ignore_sighup=True``.
|
||||
|
||||
Version 3.3
|
||||
```````````
|
||||
|
||||
* Added a mechanism to wrap REPLs, or shells, in an object which can conveniently
|
||||
be used to send commands and wait for the output (:mod:`pexpect.replwrap`).
|
||||
* Fixed issue where pexpect would attempt to execute a directory because
|
||||
it has the 'execute' bit set (:ghissue:`37`).
|
||||
* Removed the ``pexpect.psh`` module. This was never documented, and we found
|
||||
no evidence that people use it. The new :mod:`pexpect.replwrap` module
|
||||
provides a more flexible alternative.
|
||||
* Fixed ``TypeError: got <type 'str'> ('\r\n') as pattern`` in :meth:`spawnu.readline`
|
||||
method (:ghissue:`67`).
|
||||
* Fixed issue where EOF was not correctly detected in :meth:`~.interact`, causing
|
||||
a repeating loop of output on Linux, and blocking before EOF on BSD and
|
||||
Solaris (:ghissue:`49`).
|
||||
* Several Solaris (SmartOS) bugfixes, preventing :exc:`IOError` exceptions, especially
|
||||
when used with cron(1) (:ghissue:`44`).
|
||||
* Added new keyword argument ``echo=True`` for :class:`spawn`. On SVR4-like
|
||||
systems, the method :meth:`~.isatty` will always return *False*: the child pty
|
||||
does not appear as a terminal. Therefore, :meth:`~.setecho`, :meth:`~.getwinsize`,
|
||||
:meth:`~.setwinsize`, and :meth:`~.waitnoecho` are not supported on those platforms.
|
||||
|
||||
After this, we intend to start working on a bigger refactoring of the code, to
|
||||
be released as Pexpect 4. There may be more bugfix 3.x releases, however.
|
||||
|
||||
Version 3.2
|
||||
```````````
|
||||
|
||||
* Fix exception handling from :func:`select.select` on Python 2 (:ghpull:`38`).
|
||||
This was accidentally broken in the previous release when it was fixed for
|
||||
Python 3.
|
||||
* Removed a workaround for ``TIOCSWINSZ`` on very old systems, which was causing
|
||||
issues on some BSD systems (:ghpull:`40`).
|
||||
* Fixed an issue with exception handling in :mod:`~pexpect.pxssh` (:ghpull:`43`)
|
||||
|
||||
The documentation for :mod:`~pexpect.pxssh` was improved.
|
||||
|
||||
Version 3.1
|
||||
```````````
|
||||
|
||||
* Fix an issue that prevented importing pexpect on Python 3 when ``sys.stdout``
|
||||
was reassigned (:ghissue:`30`).
|
||||
* Improve prompt synchronisation in :mod:`~pexpect.pxssh` (:ghpull:`28`).
|
||||
* Fix pickling exception instances (:ghpull:`34`).
|
||||
* Fix handling exceptions from :func:`select.select` on Python 3 (:ghpull:`33`).
|
||||
|
||||
The examples have also been cleaned up somewhat - this will continue in future
|
||||
releases.
|
||||
|
||||
Version 3.0
|
||||
```````````
|
||||
|
||||
The new major version number doesn't indicate any deliberate API incompatibility.
|
||||
We have endeavoured to avoid breaking existing APIs. However, pexpect is under
|
||||
new maintenance after a long dormancy, so some caution is warranted.
|
||||
|
||||
* A new :ref:`unicode API <unicode>` was introduced.
|
||||
* Python 3 is now supported, using a single codebase.
|
||||
* Pexpect now requires at least Python 2.6 or 3.2.
|
||||
* The modules other than pexpect, such as :mod:`pexpect.fdpexpect` and
|
||||
:mod:`pexpect.pxssh`, were moved into the pexpect package. For now, wrapper
|
||||
modules are installed to the old locations for backwards compatibility (e.g.
|
||||
``import pxssh`` will still work), but these will be removed at some point in
|
||||
the future.
|
||||
* Ignoring ``SIGHUP`` is now optional - thanks to Kimmo Parviainen-Jalanko for
|
||||
the patch.
|
||||
|
||||
We also now have `docs on ReadTheDocs <https://pexpect.readthedocs.io/>`_,
|
||||
and `continuous integration on Travis CI <https://travis-ci.org/pexpect/pexpect>`_.
|
||||
|
||||
Version 2.4
|
||||
```````````
|
||||
|
||||
* Fix a bug regarding making the pty the controlling terminal when the process
|
||||
spawning it is not, actually, a terminal (such as from cron)
|
||||
|
||||
Version 2.3
|
||||
```````````
|
||||
|
||||
* Fixed OSError exception when a pexpect object is cleaned up. Previously, you
|
||||
might have seen this exception::
|
||||
|
||||
Exception exceptions.OSError: (10, 'No child processes')
|
||||
in <bound method spawn.__del__ of <pexpect.spawn instance at 0xd248c>> ignored
|
||||
|
||||
You should not see that anymore. Thanks to Michael Surette.
|
||||
* Added support for buffering reads. This greatly improves speed when trying to
|
||||
match long output from a child process. When you create an instance of the spawn
|
||||
object you can then set a buffer size. For now you MUST do the following to turn
|
||||
on buffering -- it may be on by default in future version::
|
||||
|
||||
child = pexpect.spawn ('my_command')
|
||||
child.maxread=1000 # Sets buffer to 1000 characters.
|
||||
|
||||
* I made a subtle change to the way TIMEOUT and EOF exceptions behave.
|
||||
Previously you could either expect these states in which case pexpect
|
||||
will not raise an exception, or you could just let pexpect raise an
|
||||
exception when these states were encountered. If you expected the
|
||||
states then the ``before`` property was set to everything before the
|
||||
state was encountered, but if you let pexpect raise the exception then
|
||||
``before`` was not set. Now, the ``before`` property will get set either
|
||||
way you choose to handle these states.
|
||||
* The spawn object now provides iterators for a *file-like interface*.
|
||||
This makes Pexpect a more complete file-like object. You can now write
|
||||
code like this::
|
||||
|
||||
child = pexpect.spawn ('ls -l')
|
||||
for line in child:
|
||||
print line
|
||||
|
||||
* write and writelines() no longer return a value. Use send() if you need that
|
||||
functionality. I did this to make the Spawn object more closely match a
|
||||
file-like object.
|
||||
* Added the attribute ``exitstatus``. This will give the exit code returned
|
||||
by the child process. This will be set to ``None`` while the child is still
|
||||
alive. When ``isalive()`` returns 0 then ``exitstatus`` will be set.
|
||||
* Made a few more tweaks to ``isalive()`` so that it will operate more
|
||||
consistently on different platforms. Solaris is the most difficult to support.
|
||||
* You can now put ``TIMEOUT`` in a list of expected patterns. This is just like
|
||||
putting ``EOF`` in the pattern list. Expecting for a ``TIMEOUT`` may not be
|
||||
used as often as ``EOF``, but this makes Pexpect more consistent.
|
||||
* Thanks to a suggestion and sample code from Chad J. Schroeder I added the ability
|
||||
for Pexpect to operate on a file descriptor that is already open. This means that
|
||||
Pexpect can be used to control streams such as those from serial port devices. Now,
|
||||
you just pass the integer file descriptor as the "command" when constructing a
|
||||
spawn open. For example on a Linux box with a modem on ttyS1::
|
||||
|
||||
fd = os.open("/dev/ttyS1", os.O_RDWR|os.O_NONBLOCK|os.O_NOCTTY)
|
||||
m = pexpect.spawn(fd) # Note integer fd is used instead of usual string.
|
||||
m.send("+++") # Escape sequence
|
||||
m.send("ATZ0\r") # Reset modem to profile 0
|
||||
rval = m.expect(["OK", "ERROR"])
|
||||
|
||||
* ``read()`` was renamed to ``read_nonblocking()``. Added new ``read()`` method
|
||||
that matches file-like object interface. In general, you should not notice
|
||||
the difference except that ``read()`` no longer allows you to directly set the
|
||||
timeout value. I hope this will not effect any existing code. Switching to
|
||||
``read_nonblocking()`` should fix existing code.
|
||||
* Changed the name of ``set_echo()`` to ``setecho()``.
|
||||
* 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 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.
|
||||
* Added a method ``close()`` that does what you think. It closes the file
|
||||
descriptor of the child application. It makes no attempt to actually kill the
|
||||
child or wait for its status.
|
||||
* Add variables ``__version__`` and ``__revision__`` (from cvs) to the pexpect
|
||||
modules. This is mainly helpful to me so that I can make sure that I'm testing
|
||||
with the right version instead of one already installed.
|
||||
* ``log_open()`` and ``log_close(`` have been removed. Now use ``setlog()``.
|
||||
The ``setlog()`` method takes a file object. This is far more flexible than
|
||||
the previous log method. Each time data is written to the file object it will
|
||||
be flushed. To turn logging off simply call ``setlog()`` with None.
|
||||
* renamed the ``isAlive()`` method to ``isalive()`` to match the more typical
|
||||
naming style in Python. Also the technique used to detect child process
|
||||
status has been drastically modified. Previously I did some funky stuff
|
||||
with signals which caused indigestion in other Python modules on some
|
||||
platforms. It was a big headache. It still is, but I think it works
|
||||
better now.
|
||||
* attribute ``matched`` renamed to ``after``
|
||||
* new attribute ``match``
|
||||
* The ``expect_eof()`` method is gone. You can now simply use the
|
||||
``expect()`` method to look for EOF.
|
||||
* **Pexpect works on OS X**, but the nature of the quirks cause many of the
|
||||
tests to fail. See bugs. (Incomplete Child Output). The problem is more
|
||||
than minor, but Pexpect is still more than useful for most tasks.
|
||||
* **Solaris**: For some reason, the *second* time a pty file descriptor is created and
|
||||
deleted it never gets returned for use. It does not effect the first time
|
||||
or the third time or any time after that. It's only the second time. This
|
||||
is weird... This could be a file descriptor leak, or it could be some
|
||||
peculiarity of how Solaris recycles them. I thought it was a UNIX requirement
|
||||
for the OS to give you the lowest available filedescriptor number. In any case,
|
||||
this should not be a problem unless you create hundreds of pexpect instances...
|
||||
It may also be a pty module bug.
|
||||
|
||||
|
||||
Moves and forks
|
||||
---------------
|
||||
|
||||
* Pexpect development used to be hosted on Sourceforge.
|
||||
* In 2011, Thomas Kluyver forked pexpect as 'pexpect-u', to support
|
||||
Python 3. He later decided he had taken the wrong approach with this.
|
||||
* In 2012, Noah Spurrier, the original author of Pexpect, moved the
|
||||
project to Github, but was still too busy to develop it much.
|
||||
* In 2013, Thomas Kluyver and Jeff Quast forked Pexpect again, intending
|
||||
to call the new fork Pexpected. Noah Spurrier agreed to let them use
|
||||
the name Pexpect, so Pexpect versions 3 and above are based on this
|
||||
fork, which now lives `here on Github <https://github.com/pexpect/pexpect>`_.
|
|
@ -0,0 +1,50 @@
|
|||
Pexpect version |version|
|
||||
=========================
|
||||
|
||||
.. image:: https://travis-ci.org/pexpect/pexpect.png?branch=master
|
||||
:target: https://travis-ci.org/pexpect/pexpect
|
||||
:align: right
|
||||
:alt: Build status
|
||||
|
||||
Pexpect makes Python a better tool for controlling other
|
||||
applications.
|
||||
|
||||
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. Unlike
|
||||
other Expect-like modules for Python, Pexpect does not require TCL or
|
||||
Expect nor does it require C extensions to be compiled. It should work
|
||||
on any platform that supports the standard Python pty module. The
|
||||
Pexpect interface was designed to be easy to use.
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
install
|
||||
overview
|
||||
api/index
|
||||
examples
|
||||
FAQ
|
||||
commonissues
|
||||
history
|
||||
|
||||
Pexpect is developed `on Github <http://github.com/pexpect/pexpect>`_. Please
|
||||
report `issues <https://github.com/pexpect/pexpect/issues>`_ there as well.
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
Installation
|
||||
============
|
||||
|
||||
Pexpect is on PyPI, and can be installed with standard tools::
|
||||
|
||||
pip install pexpect
|
||||
|
||||
Or::
|
||||
|
||||
easy_install pexpect
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
This version of Pexpect requires Python 3.3 or above, or Python 2.7.
|
||||
|
||||
As of version 4.0, Pexpect can be used on Windows and POSIX systems. However,
|
||||
:class:`pexpect.spawn` and :func:`pexpect.run` are only available on POSIX,
|
||||
where the :mod:`pty` module is present in the standard library. See
|
||||
:ref:`windows` for more information.
|
|
@ -0,0 +1,190 @@
|
|||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. texinfo to make Texinfo files
|
||||
echo. gettext to make PO message catalogs
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Pexpect.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Pexpect.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "texinfo" (
|
||||
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "gettext" (
|
||||
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
|
@ -0,0 +1,266 @@
|
|||
API Overview
|
||||
============
|
||||
|
||||
Pexpect can be used for automating interactive applications such as ssh, ftp,
|
||||
mencoder, passwd, etc. The Pexpect interface was designed to be easy to use.
|
||||
|
||||
Here is an example of Pexpect in action::
|
||||
|
||||
# This connects to the openbsd ftp site and
|
||||
# downloads the recursive directory listing.
|
||||
import pexpect
|
||||
child = pexpect.spawn('ftp ftp.openbsd.org')
|
||||
child.expect('Name .*: ')
|
||||
child.sendline('anonymous')
|
||||
child.expect('Password:')
|
||||
child.sendline('noah@example.com')
|
||||
child.expect('ftp> ')
|
||||
child.sendline('lcd /tmp')
|
||||
child.expect('ftp> ')
|
||||
child.sendline('cd pub/OpenBSD')
|
||||
child.expect('ftp> ')
|
||||
child.sendline('get README')
|
||||
child.expect('ftp> ')
|
||||
child.sendline('bye')
|
||||
|
||||
Obviously you could write an ftp client using Python's own :mod:`ftplib` module,
|
||||
but this is just a demonstration. You can use this technique with any application.
|
||||
This is especially handy if you are writing automated test tools.
|
||||
|
||||
There are two important methods in Pexpect -- :meth:`~pexpect.spawn.expect` and
|
||||
:meth:`~pexpect.spawn.send` (or :meth:`~pexpect.spawn.sendline` which is
|
||||
like :meth:`~pexpect.spawn.send` with a linefeed). The :meth:`~pexpect.spawn.expect`
|
||||
method waits for the child application to return a given string. The string you
|
||||
specify is a regular expression, so you can match complicated patterns. The
|
||||
:meth:`~pexpect.spawn.send` method writes a string to the child application.
|
||||
From the child's point of view it looks just like someone typed the text from a
|
||||
terminal. After each call to :meth:`~pexpect.spawn.expect` the ``before`` and ``after``
|
||||
properties will be set to the text printed by child application. The ``before``
|
||||
property will contain all text up to the expected string pattern. The ``after``
|
||||
string will contain the text that was matched by the expected pattern.
|
||||
The match property is set to the `re match object <http://docs.python.org/3/library/re#match-objects>`_.
|
||||
|
||||
An example of Pexpect in action may make things more clear. This example uses
|
||||
ftp to login to the OpenBSD site; list files in a directory; and then pass
|
||||
interactive control of the ftp session to the human user::
|
||||
|
||||
import pexpect
|
||||
child = pexpect.spawn ('ftp ftp.openbsd.org')
|
||||
child.expect ('Name .*: ')
|
||||
child.sendline ('anonymous')
|
||||
child.expect ('Password:')
|
||||
child.sendline ('noah@example.com')
|
||||
child.expect ('ftp> ')
|
||||
child.sendline ('ls /pub/OpenBSD/')
|
||||
child.expect ('ftp> ')
|
||||
print child.before # Print the result of the ls command.
|
||||
child.interact() # Give control of the child to the user.
|
||||
|
||||
Special EOF and TIMEOUT patterns
|
||||
--------------------------------
|
||||
|
||||
There are two special patterns to match the End Of File (:class:`~pexpect.EOF`)
|
||||
or a Timeout condition (:class:`~pexpect.TIMEOUT`). You can pass these
|
||||
patterns to :meth:`~pexpect.spawn.expect`. These patterns are not regular
|
||||
expressions. Use them like predefined constants.
|
||||
|
||||
If the child has died and you have read all the child's output then ordinarily
|
||||
:meth:`~pexpect.spawn.expect` will raise an :class:`~pexpect.EOF` exception.
|
||||
You can read everything up to the EOF without generating an exception by using
|
||||
the EOF pattern expect. In this case everything the child has output will be
|
||||
available in the ``before`` property.
|
||||
|
||||
The pattern given to :meth:`~pexpect.spawn.expect` may be a regular expression
|
||||
or it may also be a list of regular expressions. This allows you to match
|
||||
multiple optional responses. The :meth:`~pexpect.spawn.expect` method returns
|
||||
the index of the pattern that was matched. For example, say you wanted to login
|
||||
to a server. After entering a password you could get various responses from the
|
||||
server -- your password could be rejected; or you could be allowed in and asked
|
||||
for your terminal type; or you could be let right in and given a command prompt.
|
||||
The following code fragment gives an example of this::
|
||||
|
||||
child.expect('password:')
|
||||
child.sendline(my_secret_password)
|
||||
# We expect any of these three patterns...
|
||||
i = child.expect (['Permission denied', 'Terminal type', '[#\$] '])
|
||||
if i==0:
|
||||
print('Permission denied on host. Can\'t login')
|
||||
child.kill(0)
|
||||
elif i==1:
|
||||
print('Login OK... need to send terminal type.')
|
||||
child.sendline('vt100')
|
||||
child.expect('[#\$] ')
|
||||
elif i==2:
|
||||
print('Login OK.')
|
||||
print('Shell command prompt', child.after)
|
||||
|
||||
If nothing matches an expected pattern then :meth:`~pexpect.spawn.expect` will
|
||||
eventually raise a :class:`~pexpect.TIMEOUT` exception. The default time is 30
|
||||
seconds, but you can change this by passing a timeout argument to
|
||||
:meth:`~pexpect.spawn.expect`::
|
||||
|
||||
# Wait no more than 2 minutes (120 seconds) for password prompt.
|
||||
child.expect('password:', timeout=120)
|
||||
|
||||
Find the end of line -- CR/LF conventions
|
||||
-----------------------------------------
|
||||
|
||||
Pexpect matches regular expressions a little differently than what you might be
|
||||
used to.
|
||||
|
||||
The :regexp:`$` pattern for end of line match is useless. The :regexp:`$`
|
||||
matches the end of string, but Pexpect reads from the child one character at a
|
||||
time, so each character looks like the end of a line. Pexpect can't do a
|
||||
look-ahead into the child's output stream. In general you would have this
|
||||
situation when using regular expressions with any stream.
|
||||
|
||||
.. note::
|
||||
|
||||
Pexpect does have an internal buffer, so reads are faster than one character
|
||||
at a time, but from the user's perspective the regex patterns test happens
|
||||
one character at a time.
|
||||
|
||||
The best way to match the end of a line is to look for the newline: ``"\r\n"``
|
||||
(CR/LF). Yes, that does appear to be DOS-style. It may surprise some UNIX people
|
||||
to learn that terminal TTY device drivers (dumb, vt100, ANSI, xterm, etc.) all
|
||||
use the CR/LF combination to signify the end of line. Pexpect uses a Pseudo-TTY
|
||||
device to talk to the child application, so when the child app prints ``"\n"``
|
||||
you actually see ``"\r\n"``.
|
||||
|
||||
UNIX uses just linefeeds to end lines of text, but not when it comes to TTY
|
||||
devices! TTY devices are more like the Windows world. Each line of text ends
|
||||
with a CR/LF combination. When you intercept data from a UNIX command from a
|
||||
TTY device you will find that the TTY device outputs a CR/LF combination. A
|
||||
UNIX command may only write a linefeed (``\n``), but the TTY device driver
|
||||
converts it to CR/LF. This means that your terminal will see lines end with
|
||||
CR/LF (hex ``0D 0A``). Since Pexpect emulates a terminal, to match ends of
|
||||
lines you have to expect the CR/LF combination::
|
||||
|
||||
child.expect('\r\n')
|
||||
|
||||
If you just need to skip past a new line then ``expect('\n')`` by itself will
|
||||
work, but if you are expecting a specific pattern before the end of line then
|
||||
you need to explicitly look for the ``\r``. For example the following expects a
|
||||
word at the end of a line::
|
||||
|
||||
child.expect('\w+\r\n')
|
||||
|
||||
But the following would both fail::
|
||||
|
||||
child.expect('\w+\n')
|
||||
|
||||
And as explained before, trying to use :regexp:`$` to match the end of line
|
||||
would not work either::
|
||||
|
||||
child.expect ('\w+$')
|
||||
|
||||
So if you need to explicitly look for the END OF LINE, you want to look for the
|
||||
CR/LF combination -- not just the LF and not the $ pattern.
|
||||
|
||||
This problem is not limited to Pexpect. This problem happens any time you try
|
||||
to perform a regular expression match on a stream. Regular expressions need to
|
||||
look ahead. With a stream it is hard to look ahead because the process
|
||||
generating the stream may not be finished. There is no way to know if the
|
||||
process has paused momentarily or is finished and waiting for you. Pexpect must
|
||||
implicitly always do a NON greedy match (minimal) at the end of a input.
|
||||
|
||||
Pexpect compiles all regular expressions with the :data:`re.DOTALL` flag.
|
||||
With the :data:`~re.DOTALL` flag, a ``"."`` will match a newline.
|
||||
|
||||
Beware of + and * at the end of patterns
|
||||
----------------------------------------
|
||||
|
||||
Remember that any time you try to match a pattern that needs look-ahead that
|
||||
you will always get a minimal match (non greedy). For example, the following
|
||||
will always return just one character::
|
||||
|
||||
child.expect ('.+')
|
||||
|
||||
This example will match successfully, but will always return no characters::
|
||||
|
||||
child.expect ('.*')
|
||||
|
||||
Generally any star * expression will match as little as possible.
|
||||
|
||||
One thing you can do is to try to force a non-ambiguous character at the end of
|
||||
your :regexp:`\\d+` pattern. Expect that character to delimit the string. For
|
||||
example, you might try making the end of your pattern be :regexp:`\\D+` instead
|
||||
of :regexp:`\\D*`. Number digits alone would not satisfy the :regexp:`(\\d+)\\D+`
|
||||
pattern. You would need some numbers and at least one non-number at the end.
|
||||
|
||||
|
||||
Debugging
|
||||
---------
|
||||
|
||||
If you get the string value of a :class:`pexpect.spawn` object you will get lots
|
||||
of useful debugging information. For debugging it's very useful to use the
|
||||
following pattern::
|
||||
|
||||
try:
|
||||
i = child.expect ([pattern1, pattern2, pattern3, etc])
|
||||
except:
|
||||
print("Exception was thrown")
|
||||
print("debug information:")
|
||||
print(str(child))
|
||||
|
||||
It is also useful to log the child's input and out to a file or the screen. The
|
||||
following will turn on logging and send output to stdout (the screen)::
|
||||
|
||||
child = pexpect.spawn(foo)
|
||||
child.logfile = sys.stdout
|
||||
|
||||
Exceptions
|
||||
----------
|
||||
|
||||
:class:`~pexpect.EOF`
|
||||
|
||||
Note that two flavors of EOF Exception may be thrown. They are virtually
|
||||
identical except for the message string. For practical purposes you should have
|
||||
no need to distinguish between them, but they do give a little extra information
|
||||
about what type of platform you are running. The two messages are:
|
||||
|
||||
- "End Of File (EOF) in read(). Exception style platform."
|
||||
- "End Of File (EOF) in read(). Empty string style platform."
|
||||
|
||||
Some UNIX platforms will throw an exception when you try to read from a file
|
||||
descriptor in the EOF state. Other UNIX platforms instead quietly return an
|
||||
empty string to indicate that the EOF state has been reached.
|
||||
|
||||
If you wish to read up to the end of the child's output without generating an
|
||||
:class:`~pexpect.EOF` exception then use the ``expect(pexpect.EOF)`` method.
|
||||
|
||||
:class:`~pexpect.TIMEOUT`
|
||||
|
||||
The :meth:`~pexpect.spawn.expect` and :meth:`~pexpect.spawn.read` methods will
|
||||
also timeout if the child does not generate any output for a given amount of
|
||||
time. If this happens they will raise a :class:`~pexpect.TIMEOUT` exception.
|
||||
You can have these methods ignore timeout and block indefinitely by passing
|
||||
``None`` for the timeout parameter::
|
||||
|
||||
child.expect(pexpect.EOF, timeout=None)
|
||||
|
||||
.. _windows:
|
||||
|
||||
Pexpect on Windows
|
||||
------------------
|
||||
|
||||
.. versionadded:: 4.0
|
||||
Windows support
|
||||
|
||||
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`.
|
||||
|
||||
: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.
|
|
@ -0,0 +1 @@
|
|||
ptyprocess
|
|
@ -0,0 +1,155 @@
|
|||
"""Define text roles for GitHub
|
||||
|
||||
* ghissue - Issue
|
||||
* ghpull - Pull Request
|
||||
* ghuser - User
|
||||
|
||||
Adapted from bitbucket example here:
|
||||
https://bitbucket.org/birkenfeld/sphinx-contrib/src/tip/bitbucket/sphinxcontrib/bitbucket.py
|
||||
|
||||
Authors
|
||||
-------
|
||||
|
||||
* Doug Hellmann
|
||||
* Min RK
|
||||
"""
|
||||
#
|
||||
# Original Copyright (c) 2010 Doug Hellmann. All rights reserved.
|
||||
#
|
||||
|
||||
from docutils import nodes, utils
|
||||
from docutils.parsers.rst.roles import set_classes
|
||||
|
||||
def make_link_node(rawtext, app, type, slug, options):
|
||||
"""Create a link to a github resource.
|
||||
|
||||
:param rawtext: Text being replaced with link node.
|
||||
:param app: Sphinx application context
|
||||
:param type: Link type (issues, changeset, etc.)
|
||||
:param slug: ID of the thing to link to
|
||||
:param options: Options dictionary passed to role func.
|
||||
"""
|
||||
|
||||
try:
|
||||
base = app.config.github_project_url
|
||||
if not base:
|
||||
raise AttributeError
|
||||
if not base.endswith('/'):
|
||||
base += '/'
|
||||
except AttributeError as err:
|
||||
raise ValueError('github_project_url configuration value is not set (%s)' % str(err))
|
||||
|
||||
ref = base + type + '/' + slug + '/'
|
||||
set_classes(options)
|
||||
prefix = "#"
|
||||
if type == 'pull':
|
||||
prefix = "PR " + prefix
|
||||
node = nodes.reference(rawtext, prefix + utils.unescape(slug), refuri=ref,
|
||||
**options)
|
||||
return node
|
||||
|
||||
def ghissue_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
"""Link to a GitHub issue.
|
||||
|
||||
Returns 2 part tuple containing list of nodes to insert into the
|
||||
document and a list of system messages. Both are allowed to be
|
||||
empty.
|
||||
|
||||
:param name: The role name used in the document.
|
||||
:param rawtext: The entire markup snippet, with role.
|
||||
:param text: The text marked with the role.
|
||||
:param lineno: The line number where rawtext appears in the input.
|
||||
:param inliner: The inliner instance that called us.
|
||||
:param options: Directive options for customization.
|
||||
:param content: The directive content for customization.
|
||||
"""
|
||||
|
||||
try:
|
||||
issue_num = int(text)
|
||||
if issue_num <= 0:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
msg = inliner.reporter.error(
|
||||
'GitHub issue number must be a number greater than or equal to 1; '
|
||||
'"%s" is invalid.' % text, line=lineno)
|
||||
prb = inliner.problematic(rawtext, rawtext, msg)
|
||||
return [prb], [msg]
|
||||
app = inliner.document.settings.env.app
|
||||
#app.info('issue %r' % text)
|
||||
if 'pull' in name.lower():
|
||||
category = 'pull'
|
||||
elif 'issue' in name.lower():
|
||||
category = 'issues'
|
||||
else:
|
||||
msg = inliner.reporter.error(
|
||||
'GitHub roles include "ghpull" and "ghissue", '
|
||||
'"%s" is invalid.' % name, line=lineno)
|
||||
prb = inliner.problematic(rawtext, rawtext, msg)
|
||||
return [prb], [msg]
|
||||
node = make_link_node(rawtext, app, category, str(issue_num), options)
|
||||
return [node], []
|
||||
|
||||
def ghuser_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
"""Link to a GitHub user.
|
||||
|
||||
Returns 2 part tuple containing list of nodes to insert into the
|
||||
document and a list of system messages. Both are allowed to be
|
||||
empty.
|
||||
|
||||
:param name: The role name used in the document.
|
||||
:param rawtext: The entire markup snippet, with role.
|
||||
:param text: The text marked with the role.
|
||||
:param lineno: The line number where rawtext appears in the input.
|
||||
:param inliner: The inliner instance that called us.
|
||||
:param options: Directive options for customization.
|
||||
:param content: The directive content for customization.
|
||||
"""
|
||||
app = inliner.document.settings.env.app
|
||||
#app.info('user link %r' % text)
|
||||
ref = 'https://www.github.com/' + text
|
||||
node = nodes.reference(rawtext, text, refuri=ref, **options)
|
||||
return [node], []
|
||||
|
||||
def ghcommit_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
"""Link to a GitHub commit.
|
||||
|
||||
Returns 2 part tuple containing list of nodes to insert into the
|
||||
document and a list of system messages. Both are allowed to be
|
||||
empty.
|
||||
|
||||
:param name: The role name used in the document.
|
||||
:param rawtext: The entire markup snippet, with role.
|
||||
:param text: The text marked with the role.
|
||||
:param lineno: The line number where rawtext appears in the input.
|
||||
:param inliner: The inliner instance that called us.
|
||||
:param options: Directive options for customization.
|
||||
:param content: The directive content for customization.
|
||||
"""
|
||||
app = inliner.document.settings.env.app
|
||||
#app.info('user link %r' % text)
|
||||
try:
|
||||
base = app.config.github_project_url
|
||||
if not base:
|
||||
raise AttributeError
|
||||
if not base.endswith('/'):
|
||||
base += '/'
|
||||
except AttributeError as err:
|
||||
raise ValueError('github_project_url configuration value is not set (%s)' % str(err))
|
||||
|
||||
ref = base + text
|
||||
node = nodes.reference(rawtext, text[:6], refuri=ref, **options)
|
||||
return [node], []
|
||||
|
||||
|
||||
def setup(app):
|
||||
"""Install the plugin.
|
||||
|
||||
:param app: Sphinx application context.
|
||||
"""
|
||||
app.info('Initializing GitHub plugin')
|
||||
app.add_role('ghissue', ghissue_role)
|
||||
app.add_role('ghpull', ghissue_role)
|
||||
app.add_role('ghuser', ghuser_role)
|
||||
app.add_role('ghcommit', ghcommit_role)
|
||||
app.add_config_value('github_project_url', None, 'env')
|
||||
return
|
|
@ -18,7 +18,7 @@ fix_cvs_files.py
|
|||
CVS. This script scans the given path to find binary files;
|
||||
checks with CVS to see if the sticky options are set to -kb;
|
||||
finally if sticky options are not -kb then uses 'cvs admin'
|
||||
to set the -kb option.
|
||||
to set the -kb option.
|
||||
|
||||
ftp.py
|
||||
This demonstrates an FTP "bookmark".
|
||||
|
@ -51,9 +51,6 @@ rippy.py
|
|||
removing interlace artifacts, fitting to a target file size, etc.
|
||||
There are lots of options, but the process is simple and easy to use.
|
||||
|
||||
sshls.py
|
||||
This lists a directory on a remote machine.
|
||||
|
||||
ssh_tunnel.py
|
||||
This starts an SSH tunnel to a remote machine. It monitors the connection
|
||||
and restarts the tunnel if it goes down.
|
||||
|
@ -70,3 +67,20 @@ df.py
|
|||
Tuples of filesystem name and percentage are stored in a list.
|
||||
A simple report is printed. Filesystems over 95% capacity are highlighted.
|
||||
|
||||
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.
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
'''This runs Apache Status on the remote host and returns the number of requests per second.
|
||||
|
||||
./astat.py [-s server_hostname] [-u username] [-p password]
|
||||
-s : hostname of the remote server to login to.
|
||||
-u : username to user for login.
|
||||
-p : Password to user for login.
|
||||
|
||||
Example:
|
||||
This will print information about the given host:
|
||||
./astat.py -s www.example.com -u mylogin -p mypassword
|
||||
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
import sys
|
||||
import getopt
|
||||
import getpass
|
||||
import pxssh
|
||||
|
||||
|
||||
try:
|
||||
raw_input
|
||||
except NameError:
|
||||
raw_input = input
|
||||
|
||||
|
||||
def exit_with_usage():
|
||||
|
||||
print(globals()['__doc__'])
|
||||
os._exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
######################################################################
|
||||
## Parse the options, arguments, get ready, etc.
|
||||
######################################################################
|
||||
try:
|
||||
optlist, args = getopt.getopt(sys.argv[1:], 'h?s:u:p:', ['help','h','?'])
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
exit_with_usage()
|
||||
options = dict(optlist)
|
||||
if len(args) > 1:
|
||||
exit_with_usage()
|
||||
|
||||
if [elem for elem in options if elem in ['-h','--h','-?','--?','--help']]:
|
||||
print("Help:")
|
||||
exit_with_usage()
|
||||
|
||||
if '-s' in options:
|
||||
hostname = options['-s']
|
||||
else:
|
||||
hostname = raw_input('hostname: ')
|
||||
if '-u' in options:
|
||||
username = options['-u']
|
||||
else:
|
||||
username = raw_input('username: ')
|
||||
if '-p' in options:
|
||||
password = options['-p']
|
||||
else:
|
||||
password = getpass.getpass('password: ')
|
||||
|
||||
#
|
||||
# Login via SSH
|
||||
#
|
||||
p = pxssh.pxssh()
|
||||
p.login(hostname, username, password)
|
||||
p.sendline('apachectl status')
|
||||
p.expect(r'([0-9]+\.[0-9]+)\s*requests/sec')
|
||||
requests_per_second = p.match.groups()[0]
|
||||
p.logout()
|
||||
print(requests_per_second)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
78
lldb/third_party/Python/module/pexpect-2.4/examples/cgishell.cgi → lldb/third_party/Python/module/pexpect-4.6/examples/cgishell.cgi
vendored
Normal file → Executable file
78
lldb/third_party/Python/module/pexpect-2.4/examples/cgishell.cgi → lldb/third_party/Python/module/pexpect-4.6/examples/cgishell.cgi
vendored
Normal file → Executable file
|
@ -12,16 +12,20 @@ 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.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
|
||||
import sys,os
|
||||
sys.path.insert (0,os.getcwd()) # let local modules precede any installed modules
|
||||
import socket, random, string, traceback, cgi, time, getopt, getpass, threading, resource, signal
|
||||
import pxssh, pexpect, ANSI
|
||||
|
||||
def exit_with_usage(exit_code=1):
|
||||
print globals()['__doc__']
|
||||
print(globals()['__doc__'])
|
||||
os._exit(exit_code)
|
||||
|
||||
def client (command, host='localhost', port=-1):
|
||||
|
@ -59,25 +63,25 @@ def server (hostname, username, password, socket_filename='/tmp/server_sock', da
|
|||
child.login (hostname, username, password, login_naked=True)
|
||||
except:
|
||||
return
|
||||
if verbose: print 'login OK'
|
||||
if verbose: print('login OK')
|
||||
virtual_screen.write (child.before)
|
||||
virtual_screen.write (child.after)
|
||||
|
||||
if os.path.exists(socket_filename): os.remove(socket_filename)
|
||||
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
s.bind(socket_filename)
|
||||
os.chmod(socket_filename, 0777)
|
||||
if verbose: print 'Listen'
|
||||
os.chmod(socket_filename, 0o777)
|
||||
if verbose: print('Listen')
|
||||
s.listen(1)
|
||||
|
||||
r = roller (endless_poll, (child, child.PROMPT, virtual_screen))
|
||||
r.start()
|
||||
if verbose: print "started screen-poll-updater in background thread"
|
||||
if verbose: print("started screen-poll-updater in background thread")
|
||||
sys.stdout.flush()
|
||||
try:
|
||||
while True:
|
||||
conn, addr = s.accept()
|
||||
if verbose: print 'Connected by', addr
|
||||
if verbose: print('Connected by', addr)
|
||||
data = conn.recv(1024)
|
||||
request = data.split(' ', 1)
|
||||
if len(request)>1:
|
||||
|
@ -109,18 +113,18 @@ def server (hostname, username, password, socket_filename='/tmp/server_sock', da
|
|||
|
||||
response = []
|
||||
response.append (shell_window)
|
||||
if verbose: print '\n'.join(response)
|
||||
if verbose: print('\n'.join(response))
|
||||
sent = conn.send('\n'.join(response))
|
||||
if sent < len (response):
|
||||
if verbose: print "Sent is too short. Some data was cut off."
|
||||
if verbose: print("Sent is too short. Some data was cut off.")
|
||||
conn.close()
|
||||
except e:
|
||||
pass
|
||||
r.cancel()
|
||||
if verbose: print "cleaning up socket"
|
||||
if verbose: print("cleaning up socket")
|
||||
s.close()
|
||||
if os.path.exists(socket_filename): os.remove(socket_filename)
|
||||
if verbose: print "server done!"
|
||||
if verbose: print("server done!")
|
||||
|
||||
class roller (threading.Thread):
|
||||
"""This class continuously loops a function in a thread.
|
||||
|
@ -172,11 +176,11 @@ def daemonize (stdin=None, stdout=None, stderr=None, daemon_pid_filename=None):
|
|||
if stderr is None: stderr = DEVNULL
|
||||
|
||||
try:
|
||||
pid = os.fork()
|
||||
except OSError, e:
|
||||
raise Exception, "%s [%d]" % (e.strerror, e.errno)
|
||||
pid = os.fork() # fork first child
|
||||
except OSError as e:
|
||||
raise Exception("%s [%d]" % (e.strerror, e.errno))
|
||||
|
||||
if pid != 0: # The first child.
|
||||
if pid != 0:
|
||||
os.waitpid(pid,0)
|
||||
if daemon_pid_filename is not None:
|
||||
daemon_pid = int(file(daemon_pid_filename,'r').read())
|
||||
|
@ -190,8 +194,8 @@ def daemonize (stdin=None, stdout=None, stderr=None, daemon_pid_filename=None):
|
|||
|
||||
try:
|
||||
pid = os.fork() # fork second child
|
||||
except OSError, e:
|
||||
raise Exception, "%s [%d]" % (e.strerror, e.errno)
|
||||
except OSError as e:
|
||||
raise Exception("%s [%d]" % (e.strerror, e.errno))
|
||||
|
||||
if pid != 0:
|
||||
if daemon_pid_filename is not None:
|
||||
|
@ -207,7 +211,7 @@ def daemonize (stdin=None, stdout=None, stderr=None, daemon_pid_filename=None):
|
|||
maxfd = MAXFD
|
||||
|
||||
# close all file descriptors
|
||||
for fd in xrange(0, maxfd):
|
||||
for fd in range(0, maxfd):
|
||||
try:
|
||||
os.close(fd)
|
||||
except OSError: # fd wasn't open to begin with (ignored)
|
||||
|
@ -233,10 +237,10 @@ def client_cgi ():
|
|||
TITLE="Shell"
|
||||
SHELL_OUTPUT=""
|
||||
SID="NOT"
|
||||
print "Content-type: text/html;charset=utf-8\r\n"
|
||||
print("Content-type: text/html;charset=utf-8\r\n")
|
||||
try:
|
||||
form = cgi.FieldStorage()
|
||||
if form.has_key('ajax'):
|
||||
if 'ajax' in form:
|
||||
ajax_mode = True
|
||||
ajax_cmd = form['ajax'].value
|
||||
SID=form['sid'].value
|
||||
|
@ -244,47 +248,47 @@ def client_cgi ():
|
|||
command = 'xsend'
|
||||
arg = form['arg'].value.encode('hex')
|
||||
result = client (command + ' ' + arg, '/tmp/'+SID)
|
||||
print result
|
||||
print(result)
|
||||
elif ajax_cmd == 'refresh':
|
||||
command = 'refresh'
|
||||
result = client (command, '/tmp/'+SID)
|
||||
print result
|
||||
print(result)
|
||||
elif ajax_cmd == 'cursor':
|
||||
command = 'cursor'
|
||||
result = client (command, '/tmp/'+SID)
|
||||
print result
|
||||
print(result)
|
||||
elif ajax_cmd == 'exit':
|
||||
command = 'exit'
|
||||
result = client (command, '/tmp/'+SID)
|
||||
print result
|
||||
print(result)
|
||||
elif ajax_cmd == 'hash':
|
||||
command = 'hash'
|
||||
result = client (command, '/tmp/'+SID)
|
||||
print result
|
||||
elif not form.has_key('sid'):
|
||||
print(result)
|
||||
elif 'sid' not in form:
|
||||
SID=random_sid()
|
||||
print LOGIN_HTML % locals();
|
||||
print(LOGIN_HTML % locals());
|
||||
else:
|
||||
SID=form['sid'].value
|
||||
if form.has_key('start_server'):
|
||||
if 'start_server' in form:
|
||||
USERNAME = form['username'].value
|
||||
PASSWORD = form['password'].value
|
||||
dpid = server ('127.0.0.1', USERNAME, PASSWORD, '/tmp/'+SID)
|
||||
SHELL_OUTPUT="daemon pid: " + str(dpid)
|
||||
else:
|
||||
if form.has_key('cli'):
|
||||
if 'cli' in form:
|
||||
command = 'sendline ' + form['cli'].value
|
||||
else:
|
||||
command = 'sendline'
|
||||
SHELL_OUTPUT = client (command, '/tmp/'+SID)
|
||||
print CGISH_HTML % locals()
|
||||
print(CGISH_HTML % locals())
|
||||
except:
|
||||
tb_dump = traceback.format_exc()
|
||||
if ajax_mode:
|
||||
print str(tb_dump)
|
||||
print(str(tb_dump))
|
||||
else:
|
||||
SHELL_OUTPUT=str(tb_dump)
|
||||
print CGISH_HTML % locals()
|
||||
print(CGISH_HTML % locals())
|
||||
|
||||
def server_cli():
|
||||
"""This is the command line interface to starting the server.
|
||||
|
@ -293,8 +297,8 @@ def server_cli():
|
|||
"""
|
||||
try:
|
||||
optlist, args = getopt.getopt(sys.argv[1:], 'h?d', ['help','h','?', 'hostname=', 'username=', 'password=', 'port=', 'watch'])
|
||||
except Exception, e:
|
||||
print str(e)
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
exit_with_usage()
|
||||
|
||||
command_line_options = dict(optlist)
|
||||
|
@ -755,8 +759,8 @@ password: <input name="password" type="password" size="30"><br>
|
|||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception, e:
|
||||
print str(e)
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
tb_dump = traceback.format_exc()
|
||||
print str(tb_dump)
|
||||
print(str(tb_dump))
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
'''This demonstrates controlling a screen oriented application (curses).
|
||||
It starts two instances of gnuchess and then pits them against each other.
|
||||
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import pexpect
|
||||
import ANSI
|
||||
|
||||
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:
|
||||
|
||||
def __init__(self, engine = "/usr/local/bin/gnuchess -a -h 1"):
|
||||
self.child = pexpect.spawn (engine)
|
||||
self.term = ANSI.ANSI ()
|
||||
|
||||
self.child.expect ('Chess')
|
||||
if self.child.after != 'Chess':
|
||||
raise IOError('incompatible chess program')
|
||||
self.term.process_list (self.before)
|
||||
self.term.process_list (self.after)
|
||||
self.last_computer_move = ''
|
||||
|
||||
def read_until_cursor (self, r,c):
|
||||
while 1:
|
||||
self.child.read(1, 60)
|
||||
self.term.process (c)
|
||||
if self.term.cur_r == r and self.term.cur_c == c:
|
||||
return 1
|
||||
|
||||
def do_first_move (self, move):
|
||||
self.child.expect ('Your move is')
|
||||
self.child.sendline (move)
|
||||
self.term.process_list (self.before)
|
||||
self.term.process_list (self.after)
|
||||
return move
|
||||
|
||||
def do_move (self, move):
|
||||
self.read_until_cursor (19,60)
|
||||
self.child.sendline (move)
|
||||
return move
|
||||
|
||||
def get_first_computer_move (self):
|
||||
self.child.expect ('My move is')
|
||||
self.child.expect (REGEX_MOVE)
|
||||
return self.child.after
|
||||
|
||||
def get_computer_move (self):
|
||||
print('Here')
|
||||
i = self.child.expect ([r'\[17;59H', r'\[17;58H'])
|
||||
print(i)
|
||||
if i == 0:
|
||||
self.child.expect (REGEX_MOVE)
|
||||
if len(self.child.after) < 4:
|
||||
self.child.after = self.child.after + self.last_computer_move[3]
|
||||
if i == 1:
|
||||
self.child.expect (REGEX_MOVE_PART)
|
||||
self.child.after = self.last_computer_move[0] + self.child.after
|
||||
print('', self.child.after)
|
||||
self.last_computer_move = self.child.after
|
||||
return self.child.after
|
||||
|
||||
def switch (self):
|
||||
self.child.sendline ('switch')
|
||||
|
||||
def set_depth (self, depth):
|
||||
self.child.sendline ('depth')
|
||||
self.child.expect ('depth=')
|
||||
self.child.sendline ('%d' % depth)
|
||||
|
||||
def quit(self):
|
||||
self.child.sendline ('quit')
|
||||
import sys
|
||||
print('Starting...')
|
||||
white = Chess()
|
||||
white.child.echo = 1
|
||||
white.child.expect ('Your move is')
|
||||
white.set_depth(2)
|
||||
white.switch()
|
||||
|
||||
move_white = white.get_first_computer_move()
|
||||
print('first move white:', move_white)
|
||||
|
||||
white.do_move ('e7e5')
|
||||
move_white = white.get_computer_move()
|
||||
print('move white:', move_white)
|
||||
white.do_move ('f8c5')
|
||||
move_white = white.get_computer_move()
|
||||
print('move white:', move_white)
|
||||
white.do_move ('b8a6')
|
||||
move_white = white.get_computer_move()
|
||||
print('move white:', move_white)
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
||||
black = Chess()
|
||||
white = Chess()
|
||||
white.child.expect ('Your move is')
|
||||
white.switch()
|
||||
|
||||
move_white = white.get_first_computer_move()
|
||||
print('first move white:', move_white)
|
||||
|
||||
black.do_first_move (move_white)
|
||||
move_black = black.get_first_computer_move()
|
||||
print('first move black:', move_black)
|
||||
|
||||
white.do_move (move_black)
|
||||
|
||||
done = 0
|
||||
while not done:
|
||||
move_white = white.get_computer_move()
|
||||
print('move white:', move_white)
|
||||
|
||||
black.do_move (move_white)
|
||||
move_black = black.get_computer_move()
|
||||
print('move black:', move_black)
|
||||
|
||||
white.do_move (move_black)
|
||||
print('tail of loop')
|
||||
|
||||
g.quit()
|
|
@ -0,0 +1,153 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
'''This demonstrates controlling a screen oriented application (curses).
|
||||
It starts two instances of gnuchess and then pits them against each other.
|
||||
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import pexpect
|
||||
import ANSI
|
||||
import sys
|
||||
import time
|
||||
|
||||
class Chess:
|
||||
|
||||
def __init__(self, engine = "/usr/local/bin/gnuchess -a -h 1"):
|
||||
self.child = pexpect.spawn (engine)
|
||||
self.term = ANSI.ANSI ()
|
||||
|
||||
#self.child.expect ('Chess')
|
||||
#if self.child.after != 'Chess':
|
||||
# raise IOError, 'incompatible chess program'
|
||||
#self.term.process_list (self.child.before)
|
||||
#self.term.process_list (self.child.after)
|
||||
|
||||
self.last_computer_move = ''
|
||||
|
||||
def read_until_cursor (self, r,c, e=0):
|
||||
'''Eventually something like this should move into the screen class or
|
||||
a subclass. Maybe a combination of pexpect and screen...
|
||||
'''
|
||||
fout = open ('log','a')
|
||||
while self.term.cur_r != r or self.term.cur_c != c:
|
||||
try:
|
||||
k = self.child.read(1, 10)
|
||||
except Exception as e:
|
||||
print('EXCEPTION, (r,c):(%d,%d)\n' %(self.term.cur_r, self.term.cur_c))
|
||||
sys.stdout.flush()
|
||||
self.term.process (k)
|
||||
fout.write ('(r,c):(%d,%d)\n' %(self.term.cur_r, self.term.cur_c))
|
||||
fout.flush()
|
||||
if e:
|
||||
sys.stdout.write (k)
|
||||
sys.stdout.flush()
|
||||
if self.term.cur_r == r and self.term.cur_c == c:
|
||||
fout.close()
|
||||
return 1
|
||||
print('DIDNT EVEN HIT.')
|
||||
fout.close()
|
||||
return 1
|
||||
|
||||
def expect_region (self):
|
||||
'''This is another method that would be moved into the
|
||||
screen class.
|
||||
'''
|
||||
pass
|
||||
def do_scan (self):
|
||||
fout = open ('log','a')
|
||||
while 1:
|
||||
c = self.child.read(1,10)
|
||||
self.term.process (c)
|
||||
fout.write ('(r,c):(%d,%d)\n' %(self.term.cur_r, self.term.cur_c))
|
||||
fout.flush()
|
||||
sys.stdout.write (c)
|
||||
sys.stdout.flush()
|
||||
|
||||
def do_move (self, move, e = 0):
|
||||
time.sleep(1)
|
||||
self.read_until_cursor (19,60, e)
|
||||
self.child.sendline (move)
|
||||
|
||||
def wait (self, color):
|
||||
while 1:
|
||||
r = self.term.get_region (14,50,14,60)[0]
|
||||
r = r.strip()
|
||||
if r == color:
|
||||
return
|
||||
time.sleep (1)
|
||||
|
||||
def parse_computer_move (self, s):
|
||||
i = s.find ('is: ')
|
||||
cm = s[i+3:i+9]
|
||||
return cm
|
||||
def get_computer_move (self, e = 0):
|
||||
time.sleep(1)
|
||||
self.read_until_cursor (19,60, e)
|
||||
time.sleep(1)
|
||||
r = self.term.get_region (17,50,17,62)[0]
|
||||
cm = self.parse_computer_move (r)
|
||||
return cm
|
||||
|
||||
def switch (self):
|
||||
print('switching')
|
||||
self.child.sendline ('switch')
|
||||
|
||||
def set_depth (self, depth):
|
||||
self.child.sendline ('depth')
|
||||
self.child.expect ('depth=')
|
||||
self.child.sendline ('%d' % depth)
|
||||
|
||||
def quit(self):
|
||||
self.child.sendline ('quit')
|
||||
|
||||
def LOG (s):
|
||||
print(s)
|
||||
sys.stdout.flush ()
|
||||
fout = open ('moves.log', 'a')
|
||||
fout.write (s + '\n')
|
||||
fout.close()
|
||||
|
||||
print('Starting...')
|
||||
|
||||
black = Chess()
|
||||
white = Chess()
|
||||
white.read_until_cursor (19,60,1)
|
||||
white.switch()
|
||||
|
||||
done = 0
|
||||
while not done:
|
||||
white.wait ('Black')
|
||||
move_white = white.get_computer_move(1)
|
||||
LOG ( 'move white:'+ move_white )
|
||||
|
||||
black.do_move (move_white)
|
||||
black.wait ('White')
|
||||
move_black = black.get_computer_move()
|
||||
LOG ( 'move black:'+ move_black )
|
||||
|
||||
white.do_move (move_black, 1)
|
||||
|
||||
g.quit()
|
||||
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
'''This demonstrates controlling a screen oriented application (curses).
|
||||
It starts two instances of gnuchess and then pits them against each other.
|
||||
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import pexpect
|
||||
import ANSI
|
||||
|
||||
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:
|
||||
|
||||
def __init__(self, engine = "/usr/local/bin/gnuchess -a -h 1"):
|
||||
self.child = pexpect.spawn (engine)
|
||||
self.term = ANSI.ANSI ()
|
||||
|
||||
# self.child.expect ('Chess')
|
||||
# if self.child.after != 'Chess':
|
||||
# raise IOError, 'incompatible chess program'
|
||||
# self.term.process_list (self.before)
|
||||
# self.term.process_list (self.after)
|
||||
self.last_computer_move = ''
|
||||
def read_until_cursor (self, r,c):
|
||||
fout = open ('log','a')
|
||||
while 1:
|
||||
k = self.child.read(1, 10)
|
||||
self.term.process (k)
|
||||
fout.write ('(r,c):(%d,%d)\n' %(self.term.cur_r, self.term.cur_c))
|
||||
fout.flush()
|
||||
if self.term.cur_r == r and self.term.cur_c == c:
|
||||
fout.close()
|
||||
return 1
|
||||
sys.stdout.write (k)
|
||||
sys.stdout.flush()
|
||||
|
||||
def do_scan (self):
|
||||
fout = open ('log','a')
|
||||
while 1:
|
||||
c = self.child.read(1,10)
|
||||
self.term.process (c)
|
||||
fout.write ('(r,c):(%d,%d)\n' %(self.term.cur_r, self.term.cur_c))
|
||||
fout.flush()
|
||||
sys.stdout.write (c)
|
||||
sys.stdout.flush()
|
||||
|
||||
def do_move (self, move):
|
||||
self.read_until_cursor (19,60)
|
||||
self.child.sendline (move)
|
||||
return move
|
||||
|
||||
def get_computer_move (self):
|
||||
print('Here')
|
||||
i = self.child.expect ([r'\[17;59H', r'\[17;58H'])
|
||||
print(i)
|
||||
if i == 0:
|
||||
self.child.expect (REGEX_MOVE)
|
||||
if len(self.child.after) < 4:
|
||||
self.child.after = self.child.after + self.last_computer_move[3]
|
||||
if i == 1:
|
||||
self.child.expect (REGEX_MOVE_PART)
|
||||
self.child.after = self.last_computer_move[0] + self.child.after
|
||||
print('', self.child.after)
|
||||
self.last_computer_move = self.child.after
|
||||
return self.child.after
|
||||
|
||||
def switch (self):
|
||||
self.child.sendline ('switch')
|
||||
|
||||
def set_depth (self, depth):
|
||||
self.child.sendline ('depth')
|
||||
self.child.expect ('depth=')
|
||||
self.child.sendline ('%d' % depth)
|
||||
|
||||
def quit(self):
|
||||
self.child.sendline ('quit')
|
||||
import sys
|
||||
print('Starting...')
|
||||
white = Chess()
|
||||
white.do_move('b2b4')
|
||||
white.read_until_cursor (19,60)
|
||||
c1 = white.term.get_abs(17,58)
|
||||
c2 = white.term.get_abs(17,59)
|
||||
c3 = white.term.get_abs(17,60)
|
||||
c4 = white.term.get_abs(17,61)
|
||||
fout = open ('log','a')
|
||||
fout.write ('Computer:%s%s%s%s\n' %(c1,c2,c3,c4))
|
||||
fout.close()
|
||||
white.do_move('c2c4')
|
||||
white.read_until_cursor (19,60)
|
||||
c1 = white.term.get_abs(17,58)
|
||||
c2 = white.term.get_abs(17,59)
|
||||
c3 = white.term.get_abs(17,60)
|
||||
c4 = white.term.get_abs(17,61)
|
||||
fout = open ('log','a')
|
||||
fout.write ('Computer:%s%s%s%s\n' %(c1,c2,c3,c4))
|
||||
fout.close()
|
||||
white.do_scan ()
|
||||
|
||||
#white.do_move ('b8a6')
|
||||
#move_white = white.get_computer_move()
|
||||
#print 'move white:', move_white
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
||||
black = Chess()
|
||||
white = Chess()
|
||||
white.child.expect ('Your move is')
|
||||
white.switch()
|
||||
|
||||
move_white = white.get_first_computer_move()
|
||||
print('first move white:', move_white)
|
||||
|
||||
black.do_first_move (move_white)
|
||||
move_black = black.get_first_computer_move()
|
||||
print('first move black:', move_black)
|
||||
|
||||
white.do_move (move_black)
|
||||
|
||||
done = 0
|
||||
while not done:
|
||||
move_white = white.get_computer_move()
|
||||
print('move white:', move_white)
|
||||
|
||||
black.do_move (move_white)
|
||||
move_black = black.get_computer_move()
|
||||
print('move black:', move_black)
|
||||
|
||||
white.do_move (move_black)
|
||||
print('tail of loop')
|
||||
|
||||
g.quit()
|
|
@ -0,0 +1,57 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
'''This collects filesystem capacity info using the 'df' command. Tuples of
|
||||
filesystem name and percentage are stored in a list. A simple report is
|
||||
printed. Filesystems over 95% capacity are highlighted. Note that this does not
|
||||
parse filesystem names after the first space, so names with spaces in them will
|
||||
be truncated. This will produce ambiguous results for automount filesystems on
|
||||
Apple OSX.
|
||||
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import pexpect
|
||||
|
||||
child = pexpect.spawn ('df')
|
||||
|
||||
# parse 'df' output into a list.
|
||||
pattern = r"\n(\S+).*?([0-9]+)%"
|
||||
filesystem_list = []
|
||||
for dummy in range (0, 1000):
|
||||
i = child.expect ([pattern, pexpect.EOF])
|
||||
if i == 0:
|
||||
filesystem_list.append (child.match.groups())
|
||||
else:
|
||||
break
|
||||
|
||||
# Print report
|
||||
print()
|
||||
for m in filesystem_list:
|
||||
s = "Filesystem %s is at %s%%" % (m[0], m[1])
|
||||
# highlight filesystems over 95% capacity
|
||||
if int(m[1]) > 95:
|
||||
s = '! ' + s
|
||||
else:
|
||||
s = ' ' + s
|
||||
print(s)
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
'''This demonstrates an FTP "bookmark". This connects to an ftp site; does a
|
||||
few ftp stuff; and then gives the user interactive control over the session. In
|
||||
this case the "bookmark" is to a directory on the OpenBSD ftp server. It puts
|
||||
you in the i386 packages directory. You can easily modify this for other sites.
|
||||
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import pexpect
|
||||
import sys
|
||||
|
||||
# Note that, for Python 3 compatibility reasons, we are using spawnu and
|
||||
# importing unicode_literals (above). spawnu accepts Unicode input and
|
||||
# unicode_literals makes all string literals in this script Unicode by default.
|
||||
child = pexpect.spawnu('ftp ftp.openbsd.org')
|
||||
|
||||
child.expect('(?i)name .*: ')
|
||||
child.sendline('anonymous')
|
||||
child.expect('(?i)password')
|
||||
child.sendline('pexpect@sourceforge.net')
|
||||
child.expect('ftp> ')
|
||||
child.sendline('cd /pub/OpenBSD/3.7/packages/i386')
|
||||
child.expect('ftp> ')
|
||||
child.sendline('bin')
|
||||
child.expect('ftp> ')
|
||||
child.sendline('prompt')
|
||||
child.expect('ftp> ')
|
||||
child.sendline('pwd')
|
||||
child.expect('ftp> ')
|
||||
print("Escape character is '^]'.\n")
|
||||
sys.stdout.write (child.after)
|
||||
sys.stdout.flush()
|
||||
child.interact() # Escape character defaults to ^]
|
||||
# At this point this script blocks until the user presses the escape character
|
||||
# or until the child exits. The human user and the child should be talking
|
||||
# to each other now.
|
||||
|
||||
# At this point the script is running again.
|
||||
print('Left interactve mode.')
|
||||
|
||||
# The rest is not strictly necessary. This just demonstrates a few functions.
|
||||
# This makes sure the child is dead; although it would be killed when Python exits.
|
||||
if child.isalive():
|
||||
child.sendline('bye') # Try to ask ftp child to exit.
|
||||
child.close()
|
||||
# Print the final state of the child. Normally isalive() should be FALSE.
|
||||
if child.isalive():
|
||||
print('Child did not exit gracefully.')
|
||||
else:
|
||||
print('Child exited gracefully.')
|
||||
|
332
lldb/third_party/Python/module/pexpect-2.4/examples/hive.py → lldb/third_party/Python/module/pexpect-4.6/examples/hive.py
vendored
Normal file → Executable file
332
lldb/third_party/Python/module/pexpect-2.4/examples/hive.py → lldb/third_party/Python/module/pexpect-4.6/examples/hive.py
vendored
Normal file → Executable file
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""hive -- Hive Shell
|
||||
'''hive -- Hive Shell
|
||||
|
||||
This lets you ssh to a group of servers and control them as if they were one.
|
||||
Each command you enter is sent to each host in parallel. The response of each
|
||||
|
@ -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
|
||||
|
@ -58,10 +58,28 @@ on your machine can see this auth information. This is not secure.
|
|||
This is a crude script that begs to be multithreaded. But it serves its
|
||||
purpose.
|
||||
|
||||
Noah Spurrier
|
||||
PEXPECT LICENSE
|
||||
|
||||
$Id: hive.py 509 2008-01-05 21:27:47Z noah $
|
||||
"""
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
# TODO add feature to support username:password@host combination
|
||||
# TODO add feature to log each host output in separate file
|
||||
|
@ -70,23 +88,34 @@ import sys
|
|||
import os
|
||||
import re
|
||||
import optparse
|
||||
import traceback
|
||||
import types
|
||||
import time
|
||||
import getpass
|
||||
import pexpect
|
||||
import pxssh
|
||||
import readline
|
||||
import atexit
|
||||
try:
|
||||
import pexpect
|
||||
import pxssh
|
||||
except ImportError:
|
||||
sys.stderr.write("You do not have 'pexpect' installed.\n")
|
||||
sys.stderr.write("On Ubuntu you need the 'python-pexpect' package.\n")
|
||||
sys.stderr.write(" aptitude -y install python-pexpect\n")
|
||||
exit(1)
|
||||
|
||||
#histfile = os.path.join(os.environ["HOME"], ".hive_history")
|
||||
# try:
|
||||
# readline.read_history_file(histfile)
|
||||
# except IOError:
|
||||
# pass
|
||||
#atexit.register(readline.write_history_file, histfile)
|
||||
|
||||
CMD_HELP = """Hive commands are preceded by a colon : (just think of vi).
|
||||
try:
|
||||
raw_input
|
||||
except NameError:
|
||||
raw_input = input
|
||||
|
||||
|
||||
histfile = os.path.join(os.environ["HOME"], ".hive_history")
|
||||
try:
|
||||
readline.read_history_file(histfile)
|
||||
except IOError:
|
||||
pass
|
||||
atexit.register(readline.write_history_file, histfile)
|
||||
|
||||
CMD_HELP='''Hive commands are preceded by a colon : (just think of vi).
|
||||
|
||||
:target name1 name2 name3 ...
|
||||
|
||||
|
@ -131,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
|
||||
|
||||
|
@ -141,34 +170,32 @@ 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
|
||||
|
||||
This will exit the hive shell.
|
||||
|
||||
"""
|
||||
'''
|
||||
|
||||
|
||||
def login(args, cli_username=None, cli_password=None):
|
||||
def login (args, cli_username=None, cli_password=None):
|
||||
|
||||
# I have to keep a separate list of host names because Python dicts are not ordered.
|
||||
# I want to keep the same order as in the args list.
|
||||
host_names = []
|
||||
hive_connect_info = {}
|
||||
hive = {}
|
||||
# build up the list of connection information (hostname, username,
|
||||
# password, port)
|
||||
# build up the list of connection information (hostname, username, password, port)
|
||||
for host_connect_string in args:
|
||||
hcd = parse_host_connect_string(host_connect_string)
|
||||
hcd = parse_host_connect_string (host_connect_string)
|
||||
hostname = hcd['hostname']
|
||||
port = hcd['port']
|
||||
port = hcd['port']
|
||||
if port == '':
|
||||
port = None
|
||||
if len(hcd['username']) > 0:
|
||||
|
@ -187,26 +214,33 @@ def login(args, cli_username=None, cli_password=None):
|
|||
hive_connect_info[hostname] = (hostname, username, password, port)
|
||||
# build up the list of hive connections using the connection information.
|
||||
for hostname in host_names:
|
||||
print 'connecting to', hostname
|
||||
print('connecting to', hostname)
|
||||
try:
|
||||
fout = file("log_" + hostname, "w")
|
||||
fout = file("log_"+hostname, "w")
|
||||
hive[hostname] = pxssh.pxssh()
|
||||
# Disable host key checking.
|
||||
hive[hostname].SSH_OPTS = (hive[hostname].SSH_OPTS
|
||||
+ " -o 'StrictHostKeyChecking=no'"
|
||||
+ " -o 'UserKnownHostsFile /dev/null' ")
|
||||
hive[hostname].force_password = True
|
||||
hive[hostname].login(*hive_connect_info[hostname])
|
||||
print hive[hostname].before
|
||||
print(hive[hostname].before)
|
||||
hive[hostname].logfile = fout
|
||||
print '- OK'
|
||||
print('- OK')
|
||||
except Exception as e:
|
||||
print '- ERROR',
|
||||
print str(e)
|
||||
print 'Skipping', hostname
|
||||
print('- ERROR', end=' ')
|
||||
print(str(e))
|
||||
print('Skipping', hostname)
|
||||
hive[hostname] = None
|
||||
return host_names, hive
|
||||
|
||||
|
||||
def main():
|
||||
def main ():
|
||||
|
||||
global options, args, CMD_HELP
|
||||
|
||||
rows = 24
|
||||
cols = 80
|
||||
|
||||
if options.sameuser:
|
||||
cli_username = raw_input('username: ')
|
||||
else:
|
||||
|
@ -221,123 +255,117 @@ 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()
|
||||
if cmd == '?' or cmd == ':help' or cmd == ':h':
|
||||
print CMD_HELP
|
||||
if cmd=='?' or cmd==':help' or cmd==':h':
|
||||
print(CMD_HELP)
|
||||
continue
|
||||
elif cmd == ':refresh':
|
||||
refresh(hive, target_hostnames, timeout=0.5)
|
||||
elif cmd==':refresh':
|
||||
refresh (hive, target_hostnames, timeout=0.5)
|
||||
for hostname in target_hostnames:
|
||||
print('/' + '=' * (cols - 2))
|
||||
print('| ' + hostname)
|
||||
print('\\' + '-' * (cols - 2))
|
||||
if hive[hostname] is None:
|
||||
print '/============================================================================='
|
||||
print '| ' + hostname + ' is DEAD'
|
||||
print '\\-----------------------------------------------------------------------------'
|
||||
print('# DEAD: %s' % hostname)
|
||||
else:
|
||||
print '/============================================================================='
|
||||
print '| ' + hostname
|
||||
print '\\-----------------------------------------------------------------------------'
|
||||
print hive[hostname].before
|
||||
print '=============================================================================='
|
||||
print(hive[hostname].before)
|
||||
print('#' * 79)
|
||||
continue
|
||||
elif cmd == ':resync':
|
||||
resync(hive, target_hostnames, timeout=0.5)
|
||||
elif cmd==':resync':
|
||||
resync (hive, target_hostnames, timeout=0.5)
|
||||
for hostname in target_hostnames:
|
||||
print('/' + '=' * (cols - 2))
|
||||
print('| ' + hostname)
|
||||
print('\\' + '-' * (cols - 2))
|
||||
if hive[hostname] is None:
|
||||
print '/============================================================================='
|
||||
print '| ' + hostname + ' is DEAD'
|
||||
print '\\-----------------------------------------------------------------------------'
|
||||
print('# DEAD: %s' % hostname)
|
||||
else:
|
||||
print '/============================================================================='
|
||||
print '| ' + hostname
|
||||
print '\\-----------------------------------------------------------------------------'
|
||||
print hive[hostname].before
|
||||
print '=============================================================================='
|
||||
print(hive[hostname].before)
|
||||
print('#' * 79)
|
||||
continue
|
||||
elif cmd == ':sync':
|
||||
elif cmd==':sync':
|
||||
synchronous_mode = True
|
||||
resync(hive, target_hostnames, timeout=0.5)
|
||||
resync (hive, target_hostnames, timeout=0.5)
|
||||
continue
|
||||
elif cmd == ':async':
|
||||
elif cmd==':async':
|
||||
synchronous_mode = False
|
||||
continue
|
||||
elif cmd == ':prompt':
|
||||
elif cmd==':prompt':
|
||||
for hostname in target_hostnames:
|
||||
try:
|
||||
if hive[hostname] is not None:
|
||||
hive[hostname].set_unique_prompt()
|
||||
except Exception as e:
|
||||
print "Had trouble communicating with %s, so removing it from the target list." % hostname
|
||||
print str(e)
|
||||
print("Had trouble communicating with %s, so removing it from the target list." % hostname)
|
||||
print(str(e))
|
||||
hive[hostname] = None
|
||||
continue
|
||||
elif cmd[:5] == ':send':
|
||||
cmd, txt = cmd.split(None, 1)
|
||||
cmd, txt = cmd.split(None,1)
|
||||
for hostname in target_hostnames:
|
||||
try:
|
||||
if hive[hostname] is not None:
|
||||
hive[hostname].send(txt)
|
||||
except Exception as e:
|
||||
print "Had trouble communicating with %s, so removing it from the target list." % hostname
|
||||
print str(e)
|
||||
print("Had trouble communicating with %s, so removing it from the target list." % hostname)
|
||||
print(str(e))
|
||||
hive[hostname] = None
|
||||
continue
|
||||
elif cmd[:3] == ':to':
|
||||
cmd, hostname, txt = cmd.split(None, 2)
|
||||
cmd, hostname, txt = cmd.split(None,2)
|
||||
print('/' + '=' * (cols - 2))
|
||||
print('| ' + hostname)
|
||||
print('\\' + '-' * (cols - 2))
|
||||
if hive[hostname] is None:
|
||||
print '/============================================================================='
|
||||
print '| ' + hostname + ' is DEAD'
|
||||
print '\\-----------------------------------------------------------------------------'
|
||||
print('# DEAD: %s' % hostname)
|
||||
continue
|
||||
try:
|
||||
hive[hostname].sendline(txt)
|
||||
hive[hostname].sendline (txt)
|
||||
hive[hostname].prompt(timeout=2)
|
||||
print '/============================================================================='
|
||||
print '| ' + hostname
|
||||
print '\\-----------------------------------------------------------------------------'
|
||||
print hive[hostname].before
|
||||
print(hive[hostname].before)
|
||||
except Exception as e:
|
||||
print "Had trouble communicating with %s, so removing it from the target list." % hostname
|
||||
print str(e)
|
||||
print("Had trouble communicating with %s, so removing it from the target list." % hostname)
|
||||
print(str(e))
|
||||
hive[hostname] = None
|
||||
continue
|
||||
elif cmd[:7] == ':expect':
|
||||
cmd, pattern = cmd.split(None, 1)
|
||||
print 'looking for', pattern
|
||||
cmd, pattern = cmd.split(None,1)
|
||||
print('looking for', pattern)
|
||||
try:
|
||||
for hostname in target_hostnames:
|
||||
if hive[hostname] is not None:
|
||||
hive[hostname].expect(pattern)
|
||||
print hive[hostname].before
|
||||
print(hive[hostname].before)
|
||||
except Exception as e:
|
||||
print "Had trouble communicating with %s, so removing it from the target list." % hostname
|
||||
print str(e)
|
||||
print("Had trouble communicating with %s, so removing it from the target list." % hostname)
|
||||
print(str(e))
|
||||
hive[hostname] = None
|
||||
continue
|
||||
elif cmd[:7] == ':target':
|
||||
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
|
||||
elif cmd[:8] == ':control' or cmd[:5] == ':ctrl':
|
||||
cmd, c = cmd.split(None, 1)
|
||||
if ord(c) - 96 < 0 or ord(c) - 96 > 255:
|
||||
print '/============================================================================='
|
||||
print '| Invalid character. Must be [a-zA-Z], @, [, ], \\, ^, _, or ?'
|
||||
print '\\-----------------------------------------------------------------------------'
|
||||
elif cmd[:8] == ':control' or cmd[:5] == ':ctrl' :
|
||||
cmd, c = cmd.split(None,1)
|
||||
if ord(c)-96 < 0 or ord(c)-96 > 255:
|
||||
print('/' + '=' * (cols - 2))
|
||||
print('| Invalid character. Must be [a-zA-Z], @, [, ], \\, ^, _, or ?')
|
||||
print('\\' + '-' * (cols - 2))
|
||||
continue
|
||||
for hostname in target_hostnames:
|
||||
try:
|
||||
if hive[hostname] is not None:
|
||||
hive[hostname].sendcontrol(c)
|
||||
except Exception as e:
|
||||
print "Had trouble communicating with %s, so removing it from the target list." % hostname
|
||||
print str(e)
|
||||
print("Had trouble communicating with %s, so removing it from the target list." % hostname)
|
||||
print(str(e))
|
||||
hive[hostname] = None
|
||||
continue
|
||||
elif cmd == ':esc':
|
||||
|
@ -351,10 +379,10 @@ def main():
|
|||
for hostname in target_hostnames:
|
||||
try:
|
||||
if hive[hostname] is not None:
|
||||
hive[hostname].sendline(cmd)
|
||||
hive[hostname].sendline (cmd)
|
||||
except Exception as e:
|
||||
print "Had trouble communicating with %s, so removing it from the target list." % hostname
|
||||
print str(e)
|
||||
print("Had trouble communicating with %s, so removing it from the target list." % hostname)
|
||||
print(str(e))
|
||||
hive[hostname] = None
|
||||
|
||||
#
|
||||
|
@ -363,110 +391,76 @@ def main():
|
|||
if synchronous_mode:
|
||||
for hostname in target_hostnames:
|
||||
try:
|
||||
print('/' + '=' * (cols - 2))
|
||||
print('| ' + hostname)
|
||||
print('\\' + '-' * (cols - 2))
|
||||
if hive[hostname] is None:
|
||||
print '/============================================================================='
|
||||
print '| ' + hostname + ' is DEAD'
|
||||
print '\\-----------------------------------------------------------------------------'
|
||||
print('# DEAD: %s' % hostname)
|
||||
else:
|
||||
hive[hostname].prompt(timeout=2)
|
||||
print '/============================================================================='
|
||||
print '| ' + hostname
|
||||
print '\\-----------------------------------------------------------------------------'
|
||||
print hive[hostname].before
|
||||
print(hive[hostname].before)
|
||||
except Exception as e:
|
||||
print "Had trouble communicating with %s, so removing it from the target list." % hostname
|
||||
print str(e)
|
||||
print("Had trouble communicating with %s, so removing it from the target list." % hostname)
|
||||
print(str(e))
|
||||
hive[hostname] = None
|
||||
print '=============================================================================='
|
||||
print('#' * 79)
|
||||
|
||||
def refresh (hive, hive_names, timeout=0.5):
|
||||
|
||||
def refresh(hive, hive_names, timeout=0.5):
|
||||
"""This waits for the TIMEOUT on each host.
|
||||
"""
|
||||
'''This waits for the TIMEOUT on each host.
|
||||
'''
|
||||
|
||||
# TODO This is ideal for threading.
|
||||
for hostname in hive_names:
|
||||
hive[hostname].expect([pexpect.TIMEOUT, pexpect.EOF], timeout=timeout)
|
||||
if hive[hostname] is not None:
|
||||
hive[hostname].expect([pexpect.TIMEOUT,pexpect.EOF],timeout=timeout)
|
||||
|
||||
def resync (hive, hive_names, timeout=2, max_attempts=5):
|
||||
|
||||
def resync(hive, hive_names, timeout=2, max_attempts=5):
|
||||
"""This waits for the shell prompt for each host in an effort to try to get
|
||||
'''This waits for the shell prompt for each host in an effort to try to get
|
||||
them all to the same state. The timeout is set low so that hosts that are
|
||||
already at the prompt will not slow things down too much. If a prompt match
|
||||
is made for a hosts then keep asking until it stops matching. This is a
|
||||
best effort to consume all input if it printed more than one prompt. It's
|
||||
kind of kludgy. Note that this will always introduce a delay equal to the
|
||||
timeout for each machine. So for 10 machines with a 2 second delay you will
|
||||
get AT LEAST a 20 second delay if not more. """
|
||||
get AT LEAST a 20 second delay if not more. '''
|
||||
|
||||
# TODO This is ideal for threading.
|
||||
for hostname in hive_names:
|
||||
for attempts in xrange(0, max_attempts):
|
||||
if not hive[hostname].prompt(timeout=timeout):
|
||||
break
|
||||
if hive[hostname] is not None:
|
||||
for attempts in range(0, max_attempts):
|
||||
if not hive[hostname].prompt(timeout=timeout):
|
||||
break
|
||||
|
||||
def parse_host_connect_string (hcs):
|
||||
|
||||
def parse_host_connect_string(hcs):
|
||||
"""This parses a host connection string in the form
|
||||
'''This parses a host connection string in the form
|
||||
username:password@hostname:port. All fields are options expcet hostname. A
|
||||
dictionary is returned with all four keys. Keys that were not included are
|
||||
set to empty strings ''. Note that if your password has the '@' character
|
||||
then you must backslash escape it. """
|
||||
then you must backslash escape it. '''
|
||||
|
||||
if '@' in hcs:
|
||||
p = re.compile(
|
||||
r'(?P<username>[^@:]*)(:?)(?P<password>.*)(?!\\)@(?P<hostname>[^:]*):?(?P<port>[0-9]*)')
|
||||
p = re.compile (r'(?P<username>[^@:]*)(:?)(?P<password>.*)(?!\\)@(?P<hostname>[^:]*):?(?P<port>[0-9]*)')
|
||||
else:
|
||||
p = re.compile(
|
||||
r'(?P<username>)(?P<password>)(?P<hostname>[^:]*):?(?P<port>[0-9]*)')
|
||||
m = p.search(hcs)
|
||||
p = re.compile (r'(?P<username>)(?P<password>)(?P<hostname>[^:]*):?(?P<port>[0-9]*)')
|
||||
m = p.search (hcs)
|
||||
d = m.groupdict()
|
||||
d['password'] = d['password'].replace('\\@', '@')
|
||||
d['password'] = d['password'].replace('\\@','@')
|
||||
return d
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
start_time = time.time()
|
||||
parser = optparse.OptionParser(
|
||||
formatter=optparse.TitledHelpFormatter(),
|
||||
usage=globals()['__doc__'],
|
||||
version='$Id: hive.py 509 2008-01-05 21:27:47Z noah $',
|
||||
conflict_handler="resolve")
|
||||
parser.add_option(
|
||||
'-v',
|
||||
'--verbose',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='verbose output')
|
||||
parser.add_option(
|
||||
'--samepass',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Use same password for each login.')
|
||||
parser.add_option(
|
||||
'--sameuser',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Use same username for each login.')
|
||||
(options, args) = parser.parse_args()
|
||||
if len(args) < 1:
|
||||
parser.error('missing argument')
|
||||
if options.verbose:
|
||||
print time.asctime()
|
||||
main()
|
||||
if options.verbose:
|
||||
print time.asctime()
|
||||
if options.verbose:
|
||||
print 'TOTAL TIME IN MINUTES:',
|
||||
if options.verbose:
|
||||
print (time.time() - start_time) / 60.0
|
||||
sys.exit(0)
|
||||
except KeyboardInterrupt as e: # Ctrl-C
|
||||
raise e
|
||||
except SystemExit as e: # sys.exit()
|
||||
raise e
|
||||
except Exception as e:
|
||||
print 'ERROR, UNEXPECTED EXCEPTION'
|
||||
print str(e)
|
||||
traceback.print_exc()
|
||||
os._exit(1)
|
||||
start_time = time.time()
|
||||
parser = optparse.OptionParser(formatter=optparse.TitledHelpFormatter(), usage=globals()['__doc__'], version='$Id: hive.py 533 2012-10-20 02:19:33Z noah $',conflict_handler="resolve")
|
||||
parser.add_option ('-v', '--verbose', action='store_true', default=False, help='verbose output')
|
||||
parser.add_option ('--samepass', action='store_true', default=False, help='Use same password for each login.')
|
||||
parser.add_option ('--sameuser', action='store_true', default=False, help='Use same username for each login.')
|
||||
(options, args) = parser.parse_args()
|
||||
if len(args) < 1:
|
||||
parser.error ('missing argument')
|
||||
if options.verbose: print(time.asctime())
|
||||
main()
|
||||
if options.verbose: print(time.asctime())
|
||||
if options.verbose: print('TOTAL TIME IN MINUTES:', end=' ')
|
||||
if options.verbose: print((time.time() - start_time) / 60.0)
|
|
@ -0,0 +1,229 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
''' This runs a sequence of commands on a remote host using SSH. It runs a
|
||||
simple system checks such as uptime and free to monitor the state of the remote
|
||||
host.
|
||||
|
||||
./monitor.py [-s server_hostname] [-u username] [-p password]
|
||||
-s : hostname of the remote server to login to.
|
||||
-u : username to user for login.
|
||||
-p : Password to user for login.
|
||||
|
||||
Example:
|
||||
This will print information about the given host:
|
||||
./monitor.py -s www.example.com -u mylogin -p mypassword
|
||||
|
||||
It works like this:
|
||||
Login via SSH (This is the hardest part).
|
||||
Run and parse 'uptime'.
|
||||
Run 'iostat'.
|
||||
Run 'vmstat'.
|
||||
Run 'netstat'
|
||||
Run 'free'.
|
||||
Exit the remote host.
|
||||
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os, sys, re, getopt, getpass
|
||||
import pexpect
|
||||
|
||||
|
||||
try:
|
||||
raw_input
|
||||
except NameError:
|
||||
raw_input = input
|
||||
|
||||
|
||||
#
|
||||
# Some constants.
|
||||
#
|
||||
COMMAND_PROMPT = '[#$] ' ### This is way too simple for industrial use -- we will change is ASAP.
|
||||
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'
|
||||
|
||||
def exit_with_usage():
|
||||
|
||||
print(globals()['__doc__'])
|
||||
os._exit(1)
|
||||
|
||||
def main():
|
||||
|
||||
global COMMAND_PROMPT, TERMINAL_PROMPT, TERMINAL_TYPE, SSH_NEWKEY
|
||||
######################################################################
|
||||
## Parse the options, arguments, get ready, etc.
|
||||
######################################################################
|
||||
try:
|
||||
optlist, args = getopt.getopt(sys.argv[1:], 'h?s:u:p:', ['help','h','?'])
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
exit_with_usage()
|
||||
options = dict(optlist)
|
||||
if len(args) > 1:
|
||||
exit_with_usage()
|
||||
|
||||
if [elem for elem in options if elem in ['-h','--h','-?','--?','--help']]:
|
||||
print("Help:")
|
||||
exit_with_usage()
|
||||
|
||||
if '-s' in options:
|
||||
host = options['-s']
|
||||
else:
|
||||
host = raw_input('hostname: ')
|
||||
if '-u' in options:
|
||||
user = options['-u']
|
||||
else:
|
||||
user = raw_input('username: ')
|
||||
if '-p' in options:
|
||||
password = options['-p']
|
||||
else:
|
||||
password = getpass.getpass('password: ')
|
||||
|
||||
#
|
||||
# Login via SSH
|
||||
#
|
||||
child = pexpect.spawn('ssh -l %s %s'%(user, host))
|
||||
i = child.expect([pexpect.TIMEOUT, SSH_NEWKEY, COMMAND_PROMPT, '(?i)password'])
|
||||
if i == 0: # Timeout
|
||||
print('ERROR! could not login with SSH. Here is what SSH said:')
|
||||
print(child.before, child.after)
|
||||
print(str(child))
|
||||
sys.exit (1)
|
||||
if i == 1: # In this case SSH does not have the public key cached.
|
||||
child.sendline ('yes')
|
||||
child.expect ('(?i)password')
|
||||
if i == 2:
|
||||
# This may happen if a public key was setup to automatically login.
|
||||
# But beware, the COMMAND_PROMPT at this point is very trivial and
|
||||
# could be fooled by some output in the MOTD or login message.
|
||||
pass
|
||||
if i == 3:
|
||||
child.sendline(password)
|
||||
# Now we are either at the command prompt or
|
||||
# the login process is asking for our terminal type.
|
||||
i = child.expect ([COMMAND_PROMPT, TERMINAL_PROMPT])
|
||||
if i == 1:
|
||||
child.sendline (TERMINAL_TYPE)
|
||||
child.expect (COMMAND_PROMPT)
|
||||
#
|
||||
# Set command prompt to something more unique.
|
||||
#
|
||||
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 (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.")
|
||||
print("Response was:")
|
||||
print(child.before)
|
||||
sys.exit (1)
|
||||
|
||||
# Now we should be at the command prompt and ready to run some commands.
|
||||
print('---------------------------------------')
|
||||
print('Report of commands run on remote host.')
|
||||
print('---------------------------------------')
|
||||
|
||||
# Run uname.
|
||||
child.sendline ('uname -a')
|
||||
child.expect (COMMAND_PROMPT)
|
||||
print(child.before)
|
||||
if 'linux' in child.before.lower():
|
||||
LINUX_MODE = 1
|
||||
else:
|
||||
LINUX_MODE = 0
|
||||
|
||||
# Run and parse 'uptime'.
|
||||
child.sendline ('uptime')
|
||||
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(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(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)' % (
|
||||
duration, users, av1, av5, av15))
|
||||
child.expect (COMMAND_PROMPT)
|
||||
|
||||
# Run iostat.
|
||||
child.sendline ('iostat')
|
||||
child.expect (COMMAND_PROMPT)
|
||||
print(child.before)
|
||||
|
||||
# Run vmstat.
|
||||
child.sendline ('vmstat')
|
||||
child.expect (COMMAND_PROMPT)
|
||||
print(child.before)
|
||||
|
||||
# Run free.
|
||||
if LINUX_MODE:
|
||||
child.sendline ('free') # Linux systems only.
|
||||
child.expect (COMMAND_PROMPT)
|
||||
print(child.before)
|
||||
|
||||
# Run df.
|
||||
child.sendline ('df')
|
||||
child.expect (COMMAND_PROMPT)
|
||||
print(child.before)
|
||||
|
||||
# Run lsof.
|
||||
child.sendline ('lsof')
|
||||
child.expect (COMMAND_PROMPT)
|
||||
print(child.before)
|
||||
|
||||
# # Run netstat
|
||||
# child.sendline ('netstat')
|
||||
# child.expect (COMMAND_PROMPT)
|
||||
# print child.before
|
||||
|
||||
# # Run MySQL show status.
|
||||
# child.sendline ('mysql -p -e "SHOW STATUS;"')
|
||||
# child.expect (PASSWORD_PROMPT_MYSQL)
|
||||
# child.sendline (password_mysql)
|
||||
# child.expect (COMMAND_PROMPT)
|
||||
# print
|
||||
# print child.before
|
||||
|
||||
# Now exit the remote host.
|
||||
child.sendline ('exit')
|
||||
index = child.expect([pexpect.EOF, "(?i)there are stopped jobs"])
|
||||
if index==1:
|
||||
child.sendline("exit")
|
||||
child.expect(EOF)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,116 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
'''Change passwords on the named machines. passmass host1 host2 host3 . . .
|
||||
Note that login shell prompt on remote machine must end in # or $.
|
||||
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import pexpect
|
||||
import sys, getpass
|
||||
|
||||
|
||||
try:
|
||||
raw_input
|
||||
except NameError:
|
||||
raw_input = input
|
||||
|
||||
|
||||
USAGE = '''passmass host1 host2 host3 . . .'''
|
||||
COMMAND_PROMPT = '[$#] '
|
||||
TERMINAL_PROMPT = r'Terminal type\?'
|
||||
TERMINAL_TYPE = 'vt100'
|
||||
SSH_NEWKEY = r'Are you sure you want to continue connecting \(yes/no\)\?'
|
||||
|
||||
def login(host, user, password):
|
||||
|
||||
child = pexpect.spawn('ssh -l %s %s'%(user, host))
|
||||
fout = file ("LOG.TXT","wb")
|
||||
child.logfile_read = fout #use child.logfile to also log writes (passwords!)
|
||||
|
||||
i = child.expect([pexpect.TIMEOUT, SSH_NEWKEY, '[Pp]assword: '])
|
||||
if i == 0: # Timeout
|
||||
print('ERROR!')
|
||||
print('SSH could not login. Here is what SSH said:')
|
||||
print(child.before, child.after)
|
||||
sys.exit (1)
|
||||
if i == 1: # SSH does not have the public key. Just accept it.
|
||||
child.sendline ('yes')
|
||||
child.expect ('[Pp]assword: ')
|
||||
child.sendline(password)
|
||||
# Now we are either at the command prompt or
|
||||
# the login process is asking for our terminal type.
|
||||
i = child.expect (['Permission denied', TERMINAL_PROMPT, COMMAND_PROMPT])
|
||||
if i == 0:
|
||||
print('Permission denied on host:', host)
|
||||
sys.exit (1)
|
||||
if i == 1:
|
||||
child.sendline (TERMINAL_TYPE)
|
||||
child.expect (COMMAND_PROMPT)
|
||||
return child
|
||||
|
||||
# (current) UNIX password:
|
||||
def change_password(child, user, oldpassword, newpassword):
|
||||
|
||||
child.sendline('passwd')
|
||||
i = child.expect(['[Oo]ld [Pp]assword', '.current.*password', '[Nn]ew [Pp]assword'])
|
||||
# Root does not require old password, so it gets to bypass the next step.
|
||||
if i == 0 or i == 1:
|
||||
child.sendline(oldpassword)
|
||||
child.expect('[Nn]ew [Pp]assword')
|
||||
child.sendline(newpassword)
|
||||
i = child.expect(['[Nn]ew [Pp]assword', '[Rr]etype', '[Rr]e-enter'])
|
||||
if i == 0:
|
||||
print('Host did not like new password. Here is what it said...')
|
||||
print(child.before)
|
||||
child.send (chr(3)) # Ctrl-C
|
||||
child.sendline('') # This should tell remote passwd command to quit.
|
||||
return
|
||||
child.sendline(newpassword)
|
||||
|
||||
def main():
|
||||
|
||||
if len(sys.argv) <= 1:
|
||||
print(USAGE)
|
||||
return 1
|
||||
|
||||
user = raw_input('Username: ')
|
||||
password = getpass.getpass('Current Password: ')
|
||||
newpassword = getpass.getpass('New Password: ')
|
||||
newpasswordconfirm = getpass.getpass('Confirm New Password: ')
|
||||
if newpassword != newpasswordconfirm:
|
||||
print('New Passwords do not match.')
|
||||
return 1
|
||||
|
||||
for host in sys.argv[1:]:
|
||||
child = login(host, user, password)
|
||||
if child == None:
|
||||
print('Could not login to host:', host)
|
||||
continue
|
||||
print('Changing password on host:', host)
|
||||
change_password(child, user, password, newpassword)
|
||||
child.expect(COMMAND_PROMPT)
|
||||
child.sendline('exit')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
'''This starts the python interpreter; captures the startup message; then gives
|
||||
the user interactive control over the session. Why? For fun...
|
||||
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import pexpect
|
||||
|
||||
# Don't do this unless you like being John Malkovich
|
||||
# c = pexpect.spawnu('/usr/bin/env python ./python.py')
|
||||
|
||||
# Note that, for Python 3 compatibility reasons, we are using spawnu and
|
||||
# importing unicode_literals (above). spawnu accepts Unicode input and
|
||||
# unicode_literals makes all string literals in this script Unicode by default.
|
||||
c = pexpect.spawnu('/usr/bin/env python')
|
||||
|
||||
c.expect('>>>')
|
||||
print('And now for something completely different...')
|
||||
print(''.join(reversed((c.before))))
|
||||
print('Yes, it\'s python, but it\'s backwards.')
|
||||
print()
|
||||
print('Escape character is \'^]\'.')
|
||||
print(c.after, end=' ')
|
||||
c.interact()
|
||||
c.kill(1)
|
||||
print('is alive:', c.isalive())
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
'''This spawns a sub-shell (bash) and gives the user interactive control. The
|
||||
entire shell session is logged to a file called script.log. This behaves much
|
||||
like the classic BSD command 'script'.
|
||||
|
||||
./script.py [-a] [-c command] {logfilename}
|
||||
|
||||
logfilename : This is the name of the log file. Default is script.log.
|
||||
-a : Append to log file. Default is to overwrite log file.
|
||||
-c : spawn command. Default is to spawn the sh shell.
|
||||
|
||||
Example:
|
||||
|
||||
This will start a bash shell and append to the log named my_session.log:
|
||||
|
||||
./script.py -a -c bash my_session.log
|
||||
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os, sys, time, getopt
|
||||
import signal, fcntl, termios, struct
|
||||
import pexpect
|
||||
|
||||
global_pexpect_instance = None # Used by signal handler
|
||||
|
||||
def exit_with_usage():
|
||||
|
||||
print(globals()['__doc__'])
|
||||
os._exit(1)
|
||||
|
||||
def main():
|
||||
|
||||
######################################################################
|
||||
# Parse the options, arguments, get ready, etc.
|
||||
######################################################################
|
||||
try:
|
||||
optlist, args = getopt.getopt(sys.argv[1:], 'h?ac:', ['help','h','?'])
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
exit_with_usage()
|
||||
options = dict(optlist)
|
||||
if len(args) > 1:
|
||||
exit_with_usage()
|
||||
|
||||
if [elem for elem in options if elem in ['-h','--h','-?','--?','--help']]:
|
||||
print("Help:")
|
||||
exit_with_usage()
|
||||
|
||||
if len(args) == 1:
|
||||
script_filename = args[0]
|
||||
else:
|
||||
script_filename = "script.log"
|
||||
if '-a' in options:
|
||||
fout = open(script_filename, "ab")
|
||||
else:
|
||||
fout = open(script_filename, "wb")
|
||||
if '-c' in options:
|
||||
command = options['-c']
|
||||
else:
|
||||
command = "sh"
|
||||
|
||||
# Begin log with date/time in the form CCCCyymm.hhmmss
|
||||
fout.write ('# %4d%02d%02d.%02d%02d%02d \n' % time.localtime()[:-3])
|
||||
|
||||
######################################################################
|
||||
# Start the interactive session
|
||||
######################################################################
|
||||
p = pexpect.spawn(command)
|
||||
p.logfile = fout
|
||||
global global_pexpect_instance
|
||||
global_pexpect_instance = p
|
||||
signal.signal(signal.SIGWINCH, sigwinch_passthrough)
|
||||
|
||||
print("Script recording started. Type ^] (ASCII 29) to escape from the script shell.")
|
||||
p.interact(chr(29))
|
||||
fout.close()
|
||||
return 0
|
||||
|
||||
def sigwinch_passthrough (sig, data):
|
||||
|
||||
# Check for buggy platforms (see pexpect.setwinsize()).
|
||||
if 'TIOCGWINSZ' in dir(termios):
|
||||
TIOCGWINSZ = termios.TIOCGWINSZ
|
||||
else:
|
||||
TIOCGWINSZ = 1074295912 # assume
|
||||
s = struct.pack ("HHHH", 0, 0, 0, 0)
|
||||
a = struct.unpack ('HHHH', fcntl.ioctl(sys.stdout.fileno(), TIOCGWINSZ , s))
|
||||
global global_pexpect_instance
|
||||
global_pexpect_instance.setwinsize(a[0],a[1])
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,105 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
'''This starts an SSH tunnel to a given host. If the SSH process ever dies then
|
||||
this script will detect that and restart it. I use this under Cygwin to keep
|
||||
open encrypted tunnels to port 25 (SMTP), port 143 (IMAP4), and port 110
|
||||
(POP3). I set my mail client to talk to localhost and I keep this script
|
||||
running in the background.
|
||||
|
||||
Note that this is a rather stupid script at the moment because it just looks to
|
||||
see if any ssh process is running. It should really make sure that our specific
|
||||
ssh process is running. The problem is that ssh is missing a very useful
|
||||
feature. It has no way to report the process id of the background daemon that
|
||||
it creates with the -f command. This would be a really useful script if I could
|
||||
figure a way around this problem.
|
||||
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import pexpect
|
||||
import getpass
|
||||
import time
|
||||
|
||||
|
||||
try:
|
||||
raw_input
|
||||
except NameError:
|
||||
raw_input = input
|
||||
|
||||
|
||||
# SMTP:25 IMAP4:143 POP3:110
|
||||
tunnel_command = 'ssh -C -N -f -L 25:127.0.0.1:25 -L 143:127.0.0.1:143 -L 110:127.0.0.1:110 %(user)@%(host)'
|
||||
host = raw_input('Hostname: ')
|
||||
user = raw_input('Username: ')
|
||||
X = getpass.getpass('Password: ')
|
||||
|
||||
def get_process_info ():
|
||||
|
||||
# This seems to work on both Linux and BSD, but should otherwise be considered highly UNportable.
|
||||
|
||||
ps = pexpect.run ('ps ax -O ppid')
|
||||
pass
|
||||
|
||||
def start_tunnel ():
|
||||
|
||||
try:
|
||||
ssh_tunnel = pexpect.spawn (tunnel_command % globals())
|
||||
ssh_tunnel.expect ('password:')
|
||||
time.sleep (0.1)
|
||||
ssh_tunnel.sendline (X)
|
||||
time.sleep (60) # Cygwin is slow to update process status.
|
||||
ssh_tunnel.expect (pexpect.EOF)
|
||||
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
|
||||
def main ():
|
||||
|
||||
while True:
|
||||
ps = pexpect.spawn ('ps')
|
||||
time.sleep (1)
|
||||
index = ps.expect (['/usr/bin/ssh', pexpect.EOF, pexpect.TIMEOUT])
|
||||
if index == 2:
|
||||
print('TIMEOUT in ps command...')
|
||||
print(str(ps))
|
||||
time.sleep (13)
|
||||
if index == 1:
|
||||
print(time.asctime(), end=' ')
|
||||
print('restarting tunnel')
|
||||
start_tunnel ()
|
||||
time.sleep (11)
|
||||
print('tunnel OK')
|
||||
else:
|
||||
# print 'tunnel OK'
|
||||
time.sleep (7)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
main ()
|
||||
|
||||
# This was for older SSH versions that didn't have -f option
|
||||
#tunnel_command = 'ssh -C -n -L 25:%(host)s:25 -L 110:%(host)s:110 %(user)s@%(host)s -f nothing.sh'
|
||||
#nothing_script = '''#!/bin/sh
|
||||
#while true; do sleep 53; done
|
||||
#'''
|
||||
|
218
lldb/third_party/Python/module/pexpect-2.4/examples/topip.py → lldb/third_party/Python/module/pexpect-4.6/examples/topip.py
vendored
Normal file → Executable file
218
lldb/third_party/Python/module/pexpect-2.4/examples/topip.py → lldb/third_party/Python/module/pexpect-4.6/examples/topip.py
vendored
Normal file → Executable file
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
""" This runs netstat on a local or remote server. It calculates some simple
|
||||
''' This runs netstat on a local or remote server. It calculates some simple
|
||||
statistical information on the number of external inet connections. It groups
|
||||
by IP address. This can be used to detect if one IP address is taking up an
|
||||
excessive number of connections. It can also send an email alert if a given IP
|
||||
|
@ -8,10 +8,11 @@ address exceeds a threshold between runs of the script. This script can be used
|
|||
as a drop-in Munin plugin or it can be used stand-alone from cron. I used this
|
||||
on a busy web server that would sometimes get hit with denial of service
|
||||
attacks. This made it easy to see if a script was opening many multiple
|
||||
connections. A typical browser would open fewer than 10 connections at once. A
|
||||
script might open over 100 simultaneous connections.
|
||||
connections. A typical browser would open fewer than 10 connections at once.
|
||||
A script might open over 100 simultaneous connections.
|
||||
|
||||
./topip.py [-s server_hostname] [-u username] [-p password] {-a from_addr,to_addr} {-n N} {-v} {--ipv6}
|
||||
./topip.py [-s server_hostname] [-u username] [-p password]
|
||||
{-a from_addr,to_addr} {-n N} {-v} {--ipv6}
|
||||
|
||||
-s : hostname of the remote server to login to.
|
||||
-u : username to user for login.
|
||||
|
@ -21,10 +22,11 @@ script might open over 100 simultaneous connections.
|
|||
-a : send alert if stddev goes over 20.
|
||||
-l : to log message to /var/log/topip.log
|
||||
--ipv6 : this parses netstat output that includes ipv6 format.
|
||||
Note that this actually only works with ipv4 addresses, but for versions of
|
||||
netstat that print in ipv6 format.
|
||||
--stdev=N : Where N is an integer. This sets the trigger point for alerts and logs.
|
||||
Default is to trigger if max value is above 5 standard deviations.
|
||||
Note that this actually only works with ipv4 addresses, but for
|
||||
versions of netstat that print in ipv6 format.
|
||||
--stdev=N : Where N is an integer. This sets the trigger point
|
||||
for alerts and logs. Default is to trigger if the
|
||||
max value is over 5 standard deviations.
|
||||
|
||||
Example:
|
||||
|
||||
|
@ -32,45 +34,71 @@ Example:
|
|||
|
||||
./topip.py -s www.example.com -u mylogin -p mypassword -n 10 -v
|
||||
|
||||
This will send an alert email if the maxip goes over the stddev trigger value and
|
||||
the the current top ip is the same as the last top ip (/tmp/topip.last):
|
||||
This will send an alert email if the maxip goes over the stddev trigger
|
||||
value and the the current top ip is the same as the last top ip
|
||||
(/tmp/topip.last):
|
||||
|
||||
./topip.py -s www.example.com -u mylogin -p mypassword -n 10 -v -a alert@example.com,user@example.com
|
||||
./topip.py -s www.example.com -u mylogin -p mypassword \\
|
||||
-n 10 -v -a alert@example.com,user@example.com
|
||||
|
||||
This will print the connection stats for the localhost in Munin format:
|
||||
|
||||
./topip.py
|
||||
|
||||
Noah Spurrier
|
||||
PEXPECT LICENSE
|
||||
|
||||
$Id: topip.py 489 2007-11-28 23:40:34Z noah $
|
||||
"""
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# See http://pexpect.sourceforge.net/
|
||||
import pexpect
|
||||
import pxssh # See http://pexpect.sourceforge.net/
|
||||
import pxssh
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import re
|
||||
import getopt
|
||||
import pickle
|
||||
import getpass
|
||||
import smtplib
|
||||
import traceback
|
||||
from pprint import pprint
|
||||
|
||||
|
||||
try:
|
||||
raw_input
|
||||
except NameError:
|
||||
raw_input = input
|
||||
|
||||
|
||||
TOPIP_LOG_FILE = '/var/log/topip.log'
|
||||
TOPIP_LAST_RUN_STATS = '/var/run/topip.last'
|
||||
|
||||
|
||||
def exit_with_usage():
|
||||
|
||||
print globals()['__doc__']
|
||||
print(globals()['__doc__'])
|
||||
os._exit(1)
|
||||
|
||||
|
||||
def stats(r):
|
||||
"""This returns a dict of the median, average, standard deviation, min and max of the given sequence.
|
||||
|
||||
'''This returns a dict of the median, average, standard deviation,
|
||||
min and max of the given sequence.
|
||||
|
||||
>>> from topip import stats
|
||||
>>> print stats([5,6,8,9])
|
||||
|
@ -81,61 +109,55 @@ def stats(r):
|
|||
{'med': 4, 'max': 18, 'avg': 6.8181818181818183, 'stddev': 5.6216817577237475, 'min': 1}
|
||||
>>> print stats([1,3,4,5,18,16,4,3,3,5,13,14,5,6,7,8,7,6,6,7,5,6,4,14,7])
|
||||
{'med': 6, 'max': 18, 'avg': 7.0800000000000001, 'stddev': 4.3259218670706474, 'min': 1}
|
||||
"""
|
||||
'''
|
||||
|
||||
total = sum(r)
|
||||
avg = float(total) / float(len(r))
|
||||
sdsq = sum([(i - avg)**2 for i in r])
|
||||
s = sorted(r)
|
||||
return dict(zip(['med', 'avg', 'stddev', 'min', 'max'],
|
||||
(s[len(s) // 2], avg, (sdsq / len(r))**.5, min(r), max(r))))
|
||||
avg = float(total)/float(len(r))
|
||||
sdsq = sum([(i-avg)**2 for i in r])
|
||||
s = sorted(list(r))
|
||||
return dict(list(zip(['med', 'avg', 'stddev', 'min', 'max'],
|
||||
(s[len(s)//2], avg, (sdsq/len(r))**.5, min(r), max(r)))))
|
||||
|
||||
def send_alert (message, subject, addr_from, addr_to, smtp_server='localhost'):
|
||||
|
||||
def send_alert(message, subject, addr_from, addr_to, smtp_server='localhost'):
|
||||
"""This sends an email alert.
|
||||
"""
|
||||
'''This sends an email alert.
|
||||
'''
|
||||
|
||||
message = 'From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n' % (
|
||||
addr_from, addr_to, subject) + message
|
||||
message = ( 'From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n'
|
||||
% (addr_from, addr_to, subject) + message )
|
||||
server = smtplib.SMTP(smtp_server)
|
||||
server.sendmail(addr_from, addr_to, message)
|
||||
server.quit()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
######################################################################
|
||||
# Parse the options, arguments, etc.
|
||||
######################################################################
|
||||
try:
|
||||
optlist, args = getopt.getopt(
|
||||
sys.argv[
|
||||
1:], 'h?valqs:u:p:n:', [
|
||||
'help', 'h', '?', 'ipv6', 'stddev='])
|
||||
optlist, args = getopt.getopt(sys.argv[1:],
|
||||
'h?valqs:u:p:n:', ['help','h','?','ipv6','stddev='])
|
||||
except Exception as e:
|
||||
print str(e)
|
||||
print(str(e))
|
||||
exit_with_usage()
|
||||
options = dict(optlist)
|
||||
|
||||
munin_flag = False
|
||||
if len(args) > 0:
|
||||
if args[0] == 'config':
|
||||
print 'graph_title Netstat Connections per IP'
|
||||
print 'graph_vlabel Socket connections per IP'
|
||||
print 'connections_max.label max'
|
||||
print 'connections_max.info Maximum number of connections per IP'
|
||||
print 'connections_avg.label avg'
|
||||
print 'connections_avg.info Average number of connections per IP'
|
||||
print 'connections_stddev.label stddev'
|
||||
print 'connections_stddev.info Standard deviation'
|
||||
print('graph_title Netstat Connections per IP')
|
||||
print('graph_vlabel Socket connections per IP')
|
||||
print('connections_max.label max')
|
||||
print('connections_max.info Maximum number of connections per IP')
|
||||
print('connections_avg.label avg')
|
||||
print('connections_avg.info Average number of connections per IP')
|
||||
print('connections_stddev.label stddev')
|
||||
print('connections_stddev.info Standard deviation')
|
||||
return 0
|
||||
elif args[0] != '':
|
||||
print args, len(args)
|
||||
print(args, len(args))
|
||||
return 0
|
||||
exit_with_usage()
|
||||
if [elem for elem in options if elem in [
|
||||
'-h', '--h', '-?', '--?', '--help']]:
|
||||
print 'Help:'
|
||||
if [elem for elem in options if elem in ['-h','--h','-?','--?','--help']]:
|
||||
print('Help:')
|
||||
exit_with_usage()
|
||||
if '-s' in options:
|
||||
hostname = options['-s']
|
||||
|
@ -153,6 +175,7 @@ def main():
|
|||
password = options['-p']
|
||||
else:
|
||||
password = getpass.getpass('password: ')
|
||||
use_localhost = False
|
||||
else:
|
||||
use_localhost = True
|
||||
|
||||
|
@ -183,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:
|
||||
|
@ -198,15 +221,14 @@ def main():
|
|||
p.sendline('netstat -n -t')
|
||||
PROMPT = p.PROMPT
|
||||
|
||||
# loop through each matching netstat_pattern and put the ip address in the
|
||||
# list.
|
||||
# For each matching netstat_pattern put the ip address in the list.
|
||||
ip_list = {}
|
||||
try:
|
||||
while True:
|
||||
while 1:
|
||||
i = p.expect([PROMPT, netstat_pattern])
|
||||
if i == 0:
|
||||
break
|
||||
k = p.match.groups()[4]
|
||||
k = p.match.groups()[4].decode('utf-8')
|
||||
if k in ip_list:
|
||||
ip_list[k] = ip_list[k] + 1
|
||||
else:
|
||||
|
@ -215,83 +237,63 @@ def main():
|
|||
pass
|
||||
|
||||
# remove a few common, uninteresting addresses from the dictionary.
|
||||
ip_list = dict([(key, value)
|
||||
for key, value in ip_list.items() if '192.168.' not in key])
|
||||
ip_list = dict([(key, value)
|
||||
for key, value in ip_list.items() if '127.0.0.1' not in key])
|
||||
ip_list = dict([ (key,value) for key,value in ip_list.items() if '192.168.' not in key])
|
||||
ip_list = dict([ (key,value) for key,value in ip_list.items() if '127.0.0.1' not in key])
|
||||
|
||||
# sort dict by value (count)
|
||||
#ip_list = sorted(ip_list.iteritems(),lambda x,y:cmp(x[1], y[1]),reverse=True)
|
||||
ip_list = ip_list.items()
|
||||
ip_list = list(ip_list.items())
|
||||
if len(ip_list) < 1:
|
||||
if verbose:
|
||||
print 'Warning: no networks connections worth looking at.'
|
||||
if verbose: print('Warning: no networks connections worth looking at.')
|
||||
return 0
|
||||
ip_list.sort(lambda x, y: cmp(y[1], x[1]))
|
||||
ip_list.sort(key=lambda x:x[1])
|
||||
|
||||
# generate some stats for the ip addresses found.
|
||||
if average_n <= 1:
|
||||
if average_n is not None and average_n <= 1:
|
||||
average_n = None
|
||||
# The * unary operator treats the list elements as arguments
|
||||
s = stats(zip(*ip_list[0:average_n])[1])
|
||||
# Reminder: the * unary operator treats the list elements as arguments.
|
||||
zipped = zip(*ip_list[0:average_n])
|
||||
s = stats(list(zipped)[1])
|
||||
s['maxip'] = ip_list[0]
|
||||
|
||||
# print munin-style or verbose results for the stats.
|
||||
if munin_flag:
|
||||
print 'connections_max.value', s['max']
|
||||
print 'connections_avg.value', s['avg']
|
||||
print 'connections_stddev.value', s['stddev']
|
||||
print('connections_max.value', s['max'])
|
||||
print('connections_avg.value', s['avg'])
|
||||
print('connections_stddev.value', s['stddev'])
|
||||
return 0
|
||||
if verbose:
|
||||
pprint(s)
|
||||
print
|
||||
pprint(ip_list[0:average_n])
|
||||
pprint (s)
|
||||
print()
|
||||
pprint (ip_list[0:average_n])
|
||||
|
||||
# load the stats from the last run.
|
||||
try:
|
||||
last_stats = pickle.load(file(TOPIP_LAST_RUN_STATS))
|
||||
except:
|
||||
last_stats = {'maxip': None}
|
||||
last_stats = {'maxip':None}
|
||||
|
||||
if s['maxip'][1] > (
|
||||
s['stddev'] *
|
||||
stddev_trigger) and s['maxip'] == last_stats['maxip']:
|
||||
if verbose:
|
||||
print 'The maxip has been above trigger for two consecutive samples.'
|
||||
if ( s['maxip'][1] > (s['stddev'] * stddev_trigger)
|
||||
and s['maxip']==last_stats['maxip'] ):
|
||||
if verbose: print('The maxip has been above trigger for two consecutive samples.')
|
||||
if alert_flag:
|
||||
if verbose:
|
||||
print 'SENDING ALERT EMAIL'
|
||||
send_alert(
|
||||
str(s),
|
||||
'ALERT on %s' %
|
||||
hostname,
|
||||
alert_addr_from,
|
||||
alert_addr_to)
|
||||
if verbose: print('SENDING ALERT EMAIL')
|
||||
send_alert(str(s), 'ALERT on %s'
|
||||
% hostname, alert_addr_from, alert_addr_to)
|
||||
if log_flag:
|
||||
if verbose:
|
||||
print 'LOGGING THIS EVENT'
|
||||
fout = file(TOPIP_LOG_FILE, 'a')
|
||||
if verbose: print('LOGGING THIS EVENT')
|
||||
fout = file(TOPIP_LOG_FILE,'a')
|
||||
#dts = time.strftime('%Y:%m:%d:%H:%M:%S', time.localtime())
|
||||
dts = time.asctime()
|
||||
fout.write('%s - %d connections from %s\n' %
|
||||
(dts, s['maxip'][1], str(s['maxip'][0])))
|
||||
fout.write ('%s - %d connections from %s\n'
|
||||
% (dts,s['maxip'][1],str(s['maxip'][0])))
|
||||
fout.close()
|
||||
|
||||
# save state to TOPIP_LAST_RUN_STATS
|
||||
try:
|
||||
pickle.dump(s, file(TOPIP_LAST_RUN_STATS, 'w'))
|
||||
os.chmod(TOPIP_LAST_RUN_STATS, 0o664)
|
||||
pickle.dump(s, file(TOPIP_LAST_RUN_STATS,'w'))
|
||||
os.chmod (TOPIP_LAST_RUN_STATS, 0o664)
|
||||
except:
|
||||
pass
|
||||
# p.logout()
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
sys.exit(0)
|
||||
except SystemExit as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
print str(e)
|
||||
traceback.print_exc()
|
||||
os._exit(1)
|
||||
main()
|
|
@ -0,0 +1,81 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
'''This displays uptime information using uptime. This is redundant,
|
||||
but it demonstrates expecting for a regular expression that uses subgroups.
|
||||
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import pexpect
|
||||
import re
|
||||
|
||||
# There are many different styles of uptime results. I try to parse them all. Yeee!
|
||||
# Examples from different machines:
|
||||
# [x86] Linux 2.4 (Redhat 7.3)
|
||||
# 2:06pm up 63 days, 18 min, 3 users, load average: 0.32, 0.08, 0.02
|
||||
# [x86] Linux 2.4.18-14 (Redhat 8.0)
|
||||
# 3:07pm up 29 min, 1 user, load average: 2.44, 2.51, 1.57
|
||||
# [PPC - G4] MacOS X 10.1 SERVER Edition
|
||||
# 2:11PM up 3 days, 13:50, 3 users, load averages: 0.01, 0.00, 0.00
|
||||
# [powerpc] Darwin v1-58.corefa.com 8.2.0 Darwin Kernel Version 8.2.0
|
||||
# 10:35 up 18:06, 4 users, load averages: 0.52 0.47 0.36
|
||||
# [Sparc - R220] Sun Solaris (8)
|
||||
# 2:13pm up 22 min(s), 1 user, load average: 0.02, 0.01, 0.01
|
||||
# [x86] Linux 2.4.18-14 (Redhat 8)
|
||||
# 11:36pm up 4 days, 17:58, 1 user, load average: 0.03, 0.01, 0.00
|
||||
# AIX jwdir 2 5 0001DBFA4C00
|
||||
# 09:43AM up 23:27, 1 user, load average: 0.49, 0.32, 0.23
|
||||
# OpenBSD box3 2.9 GENERIC#653 i386
|
||||
# 6:08PM up 4 days, 22:26, 1 user, load averages: 0.13, 0.09, 0.08
|
||||
|
||||
# Note that, for Python 3 compatibility reasons, we are using spawnu and
|
||||
# importing unicode_literals (above). spawnu accepts Unicode input and
|
||||
# unicode_literals makes all string literals in this script Unicode by default.
|
||||
p = pexpect.spawnu('uptime')
|
||||
|
||||
# This parses uptime output into the major groups using regex group matching.
|
||||
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
|
||||
# styles of uptime. I'm sure there is a way to do this all at once with
|
||||
# one single regex, but I bet it would be hard to read and maintain.
|
||||
# If anyone wants to send me a version using a single regex I'd be happy to see it.
|
||||
days = '0'
|
||||
hours = '0'
|
||||
mins = '0'
|
||||
if 'day' in 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(r'([0-9]+)\s+min',duration)
|
||||
mins = str(int(p.match.group(1)))
|
||||
|
||||
# Print the parsed fields in CSV format.
|
||||
print('days, hours, minutes, users, cpu avg 1 min, cpu avg 5 min, cpu avg 15 min')
|
||||
print('%s, %s, %s, %s, %s, %s, %s' % (days, hours, mins, users, av1, av5, av15))
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
import os, fcntl, termios
|
||||
import time
|
||||
|
||||
def my_forkpty():
|
||||
|
||||
(master_fd, slave_fd) = os.openpty()
|
||||
|
||||
if (master_fd < 0 or slave_fd < 0):
|
||||
raise ExceptionPexpect("Forkpty failed")
|
||||
|
||||
# slave_name = ptsname(master_fd);
|
||||
|
||||
pid = os.fork();
|
||||
if pid == -1:
|
||||
raise ExceptionPexpect("Forkpty failed")
|
||||
elif pid == 0: # Child
|
||||
if hasattr(termios, 'TIOCNOTTY'):
|
||||
# Some platforms require an explicit detach of the
|
||||
# current controlling tty before closing stdin, stdout, stderr.
|
||||
# OpenBSD says that this is obsolete, but doesn't hurt.
|
||||
try:
|
||||
fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
|
||||
except:
|
||||
pass
|
||||
else: #if fd >= 0:
|
||||
fcntl.ioctl(fd, termios.TIOCNOTTY, 0)
|
||||
os.close(fd)
|
||||
|
||||
# The setsid() system call will place the process into its own session
|
||||
# which has the effect of disassociating it from the controlling terminal.
|
||||
# This is known to be true for OpenBSD.
|
||||
os.setsid()
|
||||
# except: return posix_error();
|
||||
|
||||
# Verify that we are disconnected from the controlling tty.
|
||||
try:
|
||||
fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY)
|
||||
os.close(fd)
|
||||
raise ExceptionPexpect("Forkpty failed")
|
||||
except:
|
||||
pass
|
||||
if 'TIOCSCTTY' in dir(termios):
|
||||
# Make the pseudo terminal the controlling terminal for this process
|
||||
# (the process must not currently have a controlling terminal).
|
||||
if fcntl.ioctl(slave_fd, termios.TIOCSCTTY, '') < 0:
|
||||
raise ExceptionPexpect("Forkpty failed")
|
||||
|
||||
# # Verify that we can open to the slave pty file. */
|
||||
# fd = os.open(slave_name, os.O_RDWR);
|
||||
# if fd < 0:
|
||||
# raise ExceptionPexpect("Forkpty failed")
|
||||
# else:
|
||||
# os.close(fd);
|
||||
|
||||
# Verify that we now have a controlling tty.
|
||||
fd = os.open("/dev/tty", os.O_WRONLY)
|
||||
if fd < 0:
|
||||
raise ExceptionPexpect("This process could not get a controlling tty.")
|
||||
else:
|
||||
os.close(fd)
|
||||
|
||||
os.close(master_fd)
|
||||
os.dup2(slave_fd, 0)
|
||||
os.dup2(slave_fd, 1)
|
||||
os.dup2(slave_fd, 2)
|
||||
if slave_fd > 2:
|
||||
os.close(slave_fd)
|
||||
pid = 0
|
||||
|
||||
else:
|
||||
# PARENT
|
||||
os.close(slave_fd);
|
||||
|
||||
if pid == -1:
|
||||
raise ExceptionPexpect("This process could not get a controlling tty.")
|
||||
# if (pid == 0)
|
||||
# PyOS_AfterFork();
|
||||
|
||||
return (pid, master_fd)
|
||||
|
||||
pid, fd = my_forkpty ()
|
||||
if pid == 0: # child
|
||||
print 'I am not a robot!'
|
||||
else:
|
||||
print '(pid, fd) = (%d, %d)' % (pid, fd)
|
||||
time.sleep(1) # Give the child a chance to print.
|
||||
print 'Robots always say:', os.read(fd,100)
|
||||
os.close(fd)
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
|
||||
####################
|
||||
#
|
||||
# NOTES
|
||||
#
|
||||
####################
|
||||
|
||||
## def send_human(self, text, delay_min = 0, delay_max = 1):
|
||||
## pass
|
||||
## def spawn2(self, command, args):
|
||||
## """return pid, fd_stdio, fd_stderr
|
||||
## """
|
||||
## pass
|
||||
|
||||
|
||||
# Reason for double fork:
|
||||
# http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC15
|
||||
# Reason for ptys:
|
||||
# http://www.erlenstar.demon.co.uk/unix/faq_4.html#SEC52
|
||||
|
||||
# Nonblocking on 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:
|
||||
# if os.name=='posix':
|
||||
# import tty
|
||||
# tty.setraw(sys.stdin.fileno())
|
||||
# elif os.name=='nt':
|
||||
# import win32file, win32con
|
||||
# hstdin = win32file._get_osfhandle(sys.stdin.fileno())
|
||||
# modes = (win32file.GetConsoleMode(hstdin)
|
||||
# & ~(win32con.ENABLE_LINE_INPUT
|
||||
# |win32con.ENABLE_ECHO_INPUT))
|
||||
# win32file.SetConsoleMode(hstdin, modes)
|
||||
|
||||
# Basic documentation:
|
||||
# Explain use of lists of patterns and return index.
|
||||
# Explain exceptions for non-handled special cases like EOF
|
||||
|
||||
# Test bad fork
|
||||
# Test ENOENT. In other words, no more TTY devices.
|
||||
|
||||
#GLOBAL_SIGCHLD_RECEIVED = 0
|
||||
#def childdied (signum, frame):
|
||||
# print 'Signal handler called with signal', signum
|
||||
# frame.f_globals['pexpect'].GLOBAL_SIGCHLD_RECEIVED = 1
|
||||
# print str(frame.f_globals['pexpect'].GLOBAL_SIGCHLD_RECEIVED)
|
||||
# GLOBAL_SIGCHLD_RECEIVED = 1
|
||||
|
|
@ -0,0 +1,233 @@
|
|||
*** Python-2.2.1.orig/Modules/posixmodule.c Tue Mar 12 16:38:31 2002
|
||||
--- Python-2.2.1/Modules/posixmodule.c Tue May 21 01:16:29 2002
|
||||
***************
|
||||
*** 1904,1910 ****
|
||||
}
|
||||
#endif
|
||||
|
||||
! #if defined(HAVE_OPENPTY) || defined(HAVE_FORKPTY)
|
||||
#ifdef HAVE_PTY_H
|
||||
#include <pty.h>
|
||||
#else
|
||||
--- 1904,1913 ----
|
||||
}
|
||||
#endif
|
||||
|
||||
! #if defined(HAVE_OPENPTY) || defined(HAVE_FORKPTY) || defined(sun)
|
||||
! #ifdef sun
|
||||
! #include <sys/stropts.h>
|
||||
! #endif
|
||||
#ifdef HAVE_PTY_H
|
||||
#include <pty.h>
|
||||
#else
|
||||
***************
|
||||
*** 1914,1920 ****
|
||||
#endif /* HAVE_PTY_H */
|
||||
#endif /* defined(HAVE_OPENPTY) || defined(HAVE_FORKPTY) */
|
||||
|
||||
! #if defined(HAVE_OPENPTY) || defined(HAVE__GETPTY)
|
||||
static char posix_openpty__doc__[] =
|
||||
"openpty() -> (master_fd, slave_fd)\n\
|
||||
Open a pseudo-terminal, returning open fd's for both master and slave end.\n";
|
||||
--- 1917,1923 ----
|
||||
#endif /* HAVE_PTY_H */
|
||||
#endif /* defined(HAVE_OPENPTY) || defined(HAVE_FORKPTY) */
|
||||
|
||||
! #if defined(HAVE_OPENPTY) || defined(HAVE__GETPTY) || defined(sun)
|
||||
static char posix_openpty__doc__[] =
|
||||
"openpty() -> (master_fd, slave_fd)\n\
|
||||
Open a pseudo-terminal, returning open fd's for both master and slave end.\n";
|
||||
***************
|
||||
*** 1925,1932 ****
|
||||
int master_fd, slave_fd;
|
||||
#ifndef HAVE_OPENPTY
|
||||
char * slave_name;
|
||||
#endif
|
||||
!
|
||||
if (!PyArg_ParseTuple(args, ":openpty"))
|
||||
return NULL;
|
||||
|
||||
--- 1928,1941 ----
|
||||
int master_fd, slave_fd;
|
||||
#ifndef HAVE_OPENPTY
|
||||
char * slave_name;
|
||||
+ #ifdef sun
|
||||
+ void *sig_saved;
|
||||
#endif
|
||||
! #endif
|
||||
! #if !defined(HAVE_OPENPTY) && !defined(HAVE__GETPTY) && defined(sun)
|
||||
! extern char *ptsname();
|
||||
! #endif
|
||||
!
|
||||
if (!PyArg_ParseTuple(args, ":openpty"))
|
||||
return NULL;
|
||||
|
||||
***************
|
||||
*** 1933,1939 ****
|
||||
#ifdef HAVE_OPENPTY
|
||||
if (openpty(&master_fd, &slave_fd, NULL, NULL, NULL) != 0)
|
||||
return posix_error();
|
||||
! #else
|
||||
slave_name = _getpty(&master_fd, O_RDWR, 0666, 0);
|
||||
if (slave_name == NULL)
|
||||
return posix_error();
|
||||
--- 1942,1948 ----
|
||||
#ifdef HAVE_OPENPTY
|
||||
if (openpty(&master_fd, &slave_fd, NULL, NULL, NULL) != 0)
|
||||
return posix_error();
|
||||
! #elif HAVE__GETPTY
|
||||
slave_name = _getpty(&master_fd, O_RDWR, 0666, 0);
|
||||
if (slave_name == NULL)
|
||||
return posix_error();
|
||||
***************
|
||||
*** 1941,1946 ****
|
||||
--- 1950,1966 ----
|
||||
slave_fd = open(slave_name, O_RDWR);
|
||||
if (slave_fd < 0)
|
||||
return posix_error();
|
||||
+ #else
|
||||
+ master_fd = open("/dev/ptmx", O_RDWR|O_NOCTTY); /* open master */
|
||||
+ sig_saved = signal(SIGCHLD, SIG_DFL);
|
||||
+ grantpt(master_fd); /* change permission of slave */
|
||||
+ unlockpt(master_fd); /* unlock slave */
|
||||
+ signal(SIGCHLD,sig_saved);
|
||||
+ slave_name = ptsname(master_fd); /* get name of slave */
|
||||
+ slave_fd = open(slave_name, O_RDWR); /* open slave */
|
||||
+ ioctl(slave_fd, I_PUSH, "ptem"); /* push ptem */
|
||||
+ ioctl(slave_fd, I_PUSH, "ldterm"); /* push ldterm*/
|
||||
+ ioctl(slave_fd, I_PUSH, "ttcompat"); /* push ttcompat*/
|
||||
#endif /* HAVE_OPENPTY */
|
||||
|
||||
return Py_BuildValue("(ii)", master_fd, slave_fd);
|
||||
***************
|
||||
*** 1948,1954 ****
|
||||
}
|
||||
#endif /* defined(HAVE_OPENPTY) || defined(HAVE__GETPTY) */
|
||||
|
||||
! #ifdef HAVE_FORKPTY
|
||||
static char posix_forkpty__doc__[] =
|
||||
"forkpty() -> (pid, master_fd)\n\
|
||||
Fork a new process with a new pseudo-terminal as controlling tty.\n\n\
|
||||
--- 1968,1974 ----
|
||||
}
|
||||
#endif /* defined(HAVE_OPENPTY) || defined(HAVE__GETPTY) */
|
||||
|
||||
! #if defined(HAVE_FORKPTY) || defined(sun)
|
||||
static char posix_forkpty__doc__[] =
|
||||
"forkpty() -> (pid, master_fd)\n\
|
||||
Fork a new process with a new pseudo-terminal as controlling tty.\n\n\
|
||||
***************
|
||||
*** 1959,1968 ****
|
||||
--- 1979,2067 ----
|
||||
posix_forkpty(PyObject *self, PyObject *args)
|
||||
{
|
||||
int master_fd, pid;
|
||||
+ #if defined(sun)
|
||||
+ int slave;
|
||||
+ char * slave_name;
|
||||
+ void *sig_saved;
|
||||
+ int fd;
|
||||
+ #endif
|
||||
|
||||
if (!PyArg_ParseTuple(args, ":forkpty"))
|
||||
return NULL;
|
||||
+ #if defined(sun)
|
||||
+ master_fd = open("/dev/ptmx", O_RDWR|O_NOCTTY); /* open master */
|
||||
+ sig_saved = signal(SIGCHLD, SIG_DFL);
|
||||
+ grantpt(master_fd); /* change permission of slave */
|
||||
+ unlockpt(master_fd); /* unlock slave */
|
||||
+ signal(SIGCHLD,sig_saved);
|
||||
+ slave_name = ptsname(master_fd); /* get name of slave */
|
||||
+ slave = open(slave_name, O_RDWR); /* open slave */
|
||||
+ ioctl(slave, I_PUSH, "ptem"); /* push ptem */
|
||||
+ ioctl(slave, I_PUSH, "ldterm"); /* push ldterm*/
|
||||
+ ioctl(slave, I_PUSH, "ttcompat"); /* push ttcompat*/
|
||||
+ if (master_fd < 0 || slave < 0)
|
||||
+ {
|
||||
+ return posix_error();
|
||||
+ }
|
||||
+ switch (pid = fork()) {
|
||||
+ case -1:
|
||||
+ return posix_error();
|
||||
+ case 0:
|
||||
+ /* First disconnect from the old controlling tty. */
|
||||
+ #ifdef TIOCNOTTY
|
||||
+ fd = open("/dev/tty", O_RDWR | O_NOCTTY);
|
||||
+ if (fd >= 0) {
|
||||
+ (void) ioctl(fd, TIOCNOTTY, NULL);
|
||||
+ close(fd);
|
||||
+ }
|
||||
+ #endif /* TIOCNOTTY */
|
||||
+ if (setsid() < 0)
|
||||
+ return posix_error();
|
||||
+
|
||||
+ /*
|
||||
+ * Verify that we are successfully disconnected from the controlling
|
||||
+ * tty.
|
||||
+ */
|
||||
+ fd = open("/dev/tty", O_RDWR | O_NOCTTY);
|
||||
+ if (fd >= 0) {
|
||||
+ return posix_error();
|
||||
+ close(fd);
|
||||
+ }
|
||||
+ /* Make it our controlling tty. */
|
||||
+ #ifdef TIOCSCTTY
|
||||
+ if (ioctl(slave, TIOCSCTTY, NULL) < 0)
|
||||
+ return posix_error();
|
||||
+ #endif /* TIOCSCTTY */
|
||||
+ fd = open(slave_name, O_RDWR);
|
||||
+ if (fd < 0) {
|
||||
+ return posix_error();
|
||||
+ } else {
|
||||
+ close(fd);
|
||||
+ }
|
||||
+ /* Verify that we now have a controlling tty. */
|
||||
+ fd = open("/dev/tty", O_WRONLY);
|
||||
+ if (fd < 0)
|
||||
+ return posix_error();
|
||||
+ else {
|
||||
+ close(fd);
|
||||
+ }
|
||||
+ (void) close(master_fd);
|
||||
+ (void) dup2(slave, 0);
|
||||
+ (void) dup2(slave, 1);
|
||||
+ (void) dup2(slave, 2);
|
||||
+ if (slave > 2)
|
||||
+ (void) close(slave);
|
||||
+ pid = 0;
|
||||
+ break;
|
||||
+ defautlt:
|
||||
+ /*
|
||||
+ * parent
|
||||
+ */
|
||||
+ (void) close(slave);
|
||||
+ }
|
||||
+ #else
|
||||
pid = forkpty(&master_fd, NULL, NULL, NULL);
|
||||
+ #endif
|
||||
if (pid == -1)
|
||||
return posix_error();
|
||||
if (pid == 0)
|
||||
***************
|
||||
*** 5607,5616 ****
|
||||
#ifdef HAVE_FORK
|
||||
{"fork", posix_fork, METH_VARARGS, posix_fork__doc__},
|
||||
#endif /* HAVE_FORK */
|
||||
! #if defined(HAVE_OPENPTY) || defined(HAVE__GETPTY)
|
||||
{"openpty", posix_openpty, METH_VARARGS, posix_openpty__doc__},
|
||||
#endif /* HAVE_OPENPTY || HAVE__GETPTY */
|
||||
! #ifdef HAVE_FORKPTY
|
||||
{"forkpty", posix_forkpty, METH_VARARGS, posix_forkpty__doc__},
|
||||
#endif /* HAVE_FORKPTY */
|
||||
#ifdef HAVE_GETEGID
|
||||
--- 5706,5715 ----
|
||||
#ifdef HAVE_FORK
|
||||
{"fork", posix_fork, METH_VARARGS, posix_fork__doc__},
|
||||
#endif /* HAVE_FORK */
|
||||
! #if defined(HAVE_OPENPTY) || defined(HAVE__GETPTY) || defined(sun)
|
||||
{"openpty", posix_openpty, METH_VARARGS, posix_openpty__doc__},
|
||||
#endif /* HAVE_OPENPTY || HAVE__GETPTY */
|
||||
! #if defined(HAVE_FORKPTY) || defined(sun)
|
||||
{"forkpty", posix_forkpty, METH_VARARGS, posix_forkpty__doc__},
|
||||
#endif /* HAVE_FORKPTY */
|
||||
#ifdef HAVE_GETEGID
|
|
@ -0,0 +1,351 @@
|
|||
'''This implements an ANSI (VT100) terminal emulator as a subclass of screen.
|
||||
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
# references:
|
||||
# http://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
# http://www.retards.org/terminals/vt102.html
|
||||
# http://vt100.net/docs/vt102-ug/contents.html
|
||||
# http://vt100.net/docs/vt220-rm/
|
||||
# http://www.termsys.demon.co.uk/vtansi.htm
|
||||
|
||||
from . import screen
|
||||
from . import FSM
|
||||
import string
|
||||
|
||||
#
|
||||
# The 'Do.*' functions are helper functions for the ANSI class.
|
||||
#
|
||||
def DoEmit (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.write_ch(fsm.input_symbol)
|
||||
|
||||
def DoStartNumber (fsm):
|
||||
|
||||
fsm.memory.append (fsm.input_symbol)
|
||||
|
||||
def DoBuildNumber (fsm):
|
||||
|
||||
ns = fsm.memory.pop()
|
||||
ns = ns + fsm.input_symbol
|
||||
fsm.memory.append (ns)
|
||||
|
||||
def DoBackOne (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_back ()
|
||||
|
||||
def DoBack (fsm):
|
||||
|
||||
count = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_back (count)
|
||||
|
||||
def DoDownOne (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_down ()
|
||||
|
||||
def DoDown (fsm):
|
||||
|
||||
count = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_down (count)
|
||||
|
||||
def DoForwardOne (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_forward ()
|
||||
|
||||
def DoForward (fsm):
|
||||
|
||||
count = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_forward (count)
|
||||
|
||||
def DoUpReverse (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_up_reverse()
|
||||
|
||||
def DoUpOne (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_up ()
|
||||
|
||||
def DoUp (fsm):
|
||||
|
||||
count = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_up (count)
|
||||
|
||||
def DoHome (fsm):
|
||||
|
||||
c = int(fsm.memory.pop())
|
||||
r = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_home (r,c)
|
||||
|
||||
def DoHomeOrigin (fsm):
|
||||
|
||||
c = 1
|
||||
r = 1
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_home (r,c)
|
||||
|
||||
def DoEraseDown (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.erase_down()
|
||||
|
||||
def DoErase (fsm):
|
||||
|
||||
arg = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
if arg == 0:
|
||||
screen.erase_down()
|
||||
elif arg == 1:
|
||||
screen.erase_up()
|
||||
elif arg == 2:
|
||||
screen.erase_screen()
|
||||
|
||||
def DoEraseEndOfLine (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.erase_end_of_line()
|
||||
|
||||
def DoEraseLine (fsm):
|
||||
|
||||
arg = int(fsm.memory.pop())
|
||||
screen = fsm.memory[0]
|
||||
if arg == 0:
|
||||
screen.erase_end_of_line()
|
||||
elif arg == 1:
|
||||
screen.erase_start_of_line()
|
||||
elif arg == 2:
|
||||
screen.erase_line()
|
||||
|
||||
def DoEnableScroll (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.scroll_screen()
|
||||
|
||||
def DoCursorSave (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_save_attrs()
|
||||
|
||||
def DoCursorRestore (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
screen.cursor_restore_attrs()
|
||||
|
||||
def DoScrollRegion (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
r2 = int(fsm.memory.pop())
|
||||
r1 = int(fsm.memory.pop())
|
||||
screen.scroll_screen_rows (r1,r2)
|
||||
|
||||
def DoMode (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
mode = fsm.memory.pop() # Should be 4
|
||||
# screen.setReplaceMode ()
|
||||
|
||||
def DoLog (fsm):
|
||||
|
||||
screen = fsm.memory[0]
|
||||
fsm.memory = [screen]
|
||||
fout = open ('log', 'a')
|
||||
fout.write (fsm.input_symbol + ',' + fsm.current_state + '\n')
|
||||
fout.close()
|
||||
|
||||
class term (screen.screen):
|
||||
|
||||
'''This class is an abstract, generic terminal.
|
||||
This does nothing. This is a placeholder that
|
||||
provides a common base class for other terminals
|
||||
such as an ANSI terminal. '''
|
||||
|
||||
def __init__ (self, r=24, c=80, *args, **kwargs):
|
||||
|
||||
screen.screen.__init__(self, r,c,*args,**kwargs)
|
||||
|
||||
class ANSI (term):
|
||||
'''This class implements an ANSI (VT100) terminal.
|
||||
It is a stream filter that recognizes ANSI terminal
|
||||
escape sequences and maintains the state of a screen object. '''
|
||||
|
||||
def __init__ (self, r=24,c=80,*args,**kwargs):
|
||||
|
||||
term.__init__(self,r,c,*args,**kwargs)
|
||||
|
||||
#self.screen = screen (24,80)
|
||||
self.state = FSM.FSM ('INIT',[self])
|
||||
self.state.set_default_transition (DoLog, 'INIT')
|
||||
self.state.add_transition_any ('INIT', DoEmit, 'INIT')
|
||||
self.state.add_transition ('\x1b', 'INIT', None, 'ESC')
|
||||
self.state.add_transition_any ('ESC', DoLog, 'INIT')
|
||||
self.state.add_transition ('(', 'ESC', None, 'G0SCS')
|
||||
self.state.add_transition (')', 'ESC', None, 'G1SCS')
|
||||
self.state.add_transition_list ('AB012', 'G0SCS', None, 'INIT')
|
||||
self.state.add_transition_list ('AB012', 'G1SCS', None, 'INIT')
|
||||
self.state.add_transition ('7', 'ESC', DoCursorSave, 'INIT')
|
||||
self.state.add_transition ('8', 'ESC', DoCursorRestore, 'INIT')
|
||||
self.state.add_transition ('M', 'ESC', DoUpReverse, 'INIT')
|
||||
self.state.add_transition ('>', 'ESC', DoUpReverse, 'INIT')
|
||||
self.state.add_transition ('<', 'ESC', DoUpReverse, 'INIT')
|
||||
self.state.add_transition ('=', 'ESC', None, 'INIT') # Selects application keypad.
|
||||
self.state.add_transition ('#', 'ESC', None, 'GRAPHICS_POUND')
|
||||
self.state.add_transition_any ('GRAPHICS_POUND', None, 'INIT')
|
||||
self.state.add_transition ('[', 'ESC', None, 'ELB')
|
||||
# ELB means Escape Left Bracket. That is ^[[
|
||||
self.state.add_transition ('H', 'ELB', DoHomeOrigin, 'INIT')
|
||||
self.state.add_transition ('D', 'ELB', DoBackOne, 'INIT')
|
||||
self.state.add_transition ('B', 'ELB', DoDownOne, 'INIT')
|
||||
self.state.add_transition ('C', 'ELB', DoForwardOne, 'INIT')
|
||||
self.state.add_transition ('A', 'ELB', DoUpOne, 'INIT')
|
||||
self.state.add_transition ('J', 'ELB', DoEraseDown, 'INIT')
|
||||
self.state.add_transition ('K', 'ELB', DoEraseEndOfLine, 'INIT')
|
||||
self.state.add_transition ('r', 'ELB', DoEnableScroll, 'INIT')
|
||||
self.state.add_transition ('m', 'ELB', self.do_sgr, 'INIT')
|
||||
self.state.add_transition ('?', 'ELB', None, 'MODECRAP')
|
||||
self.state.add_transition_list (string.digits, 'ELB', DoStartNumber, 'NUMBER_1')
|
||||
self.state.add_transition_list (string.digits, 'NUMBER_1', DoBuildNumber, 'NUMBER_1')
|
||||
self.state.add_transition ('D', 'NUMBER_1', DoBack, 'INIT')
|
||||
self.state.add_transition ('B', 'NUMBER_1', DoDown, 'INIT')
|
||||
self.state.add_transition ('C', 'NUMBER_1', DoForward, 'INIT')
|
||||
self.state.add_transition ('A', 'NUMBER_1', DoUp, 'INIT')
|
||||
self.state.add_transition ('J', 'NUMBER_1', DoErase, 'INIT')
|
||||
self.state.add_transition ('K', 'NUMBER_1', DoEraseLine, 'INIT')
|
||||
self.state.add_transition ('l', 'NUMBER_1', DoMode, 'INIT')
|
||||
### It gets worse... the 'm' code can have infinite number of
|
||||
### number;number;number before it. I've never seen more than two,
|
||||
### but the specs say it's allowed. crap!
|
||||
self.state.add_transition ('m', 'NUMBER_1', self.do_sgr, 'INIT')
|
||||
### LED control. Same implementation problem as 'm' code.
|
||||
self.state.add_transition ('q', 'NUMBER_1', self.do_decsca, 'INIT')
|
||||
|
||||
# \E[?47h switch to alternate screen
|
||||
# \E[?47l restores to normal screen from alternate screen.
|
||||
self.state.add_transition_list (string.digits, 'MODECRAP', DoStartNumber, 'MODECRAP_NUM')
|
||||
self.state.add_transition_list (string.digits, 'MODECRAP_NUM', DoBuildNumber, 'MODECRAP_NUM')
|
||||
self.state.add_transition ('l', 'MODECRAP_NUM', self.do_modecrap, 'INIT')
|
||||
self.state.add_transition ('h', 'MODECRAP_NUM', self.do_modecrap, 'INIT')
|
||||
|
||||
#RM Reset Mode Esc [ Ps l none
|
||||
self.state.add_transition (';', 'NUMBER_1', None, 'SEMICOLON')
|
||||
self.state.add_transition_any ('SEMICOLON', DoLog, 'INIT')
|
||||
self.state.add_transition_list (string.digits, 'SEMICOLON', DoStartNumber, 'NUMBER_2')
|
||||
self.state.add_transition_list (string.digits, 'NUMBER_2', DoBuildNumber, 'NUMBER_2')
|
||||
self.state.add_transition_any ('NUMBER_2', DoLog, 'INIT')
|
||||
self.state.add_transition ('H', 'NUMBER_2', DoHome, 'INIT')
|
||||
self.state.add_transition ('f', 'NUMBER_2', DoHome, 'INIT')
|
||||
self.state.add_transition ('r', 'NUMBER_2', DoScrollRegion, 'INIT')
|
||||
### It gets worse... the 'm' code can have infinite number of
|
||||
### number;number;number before it. I've never seen more than two,
|
||||
### but the specs say it's allowed. crap!
|
||||
self.state.add_transition ('m', 'NUMBER_2', self.do_sgr, 'INIT')
|
||||
### LED control. Same problem as 'm' code.
|
||||
self.state.add_transition ('q', 'NUMBER_2', self.do_decsca, 'INIT')
|
||||
self.state.add_transition (';', 'NUMBER_2', None, 'SEMICOLON_X')
|
||||
|
||||
# Create a state for 'q' and 'm' which allows an infinite number of ignored numbers
|
||||
self.state.add_transition_any ('SEMICOLON_X', DoLog, 'INIT')
|
||||
self.state.add_transition_list (string.digits, 'SEMICOLON_X', DoStartNumber, 'NUMBER_X')
|
||||
self.state.add_transition_list (string.digits, 'NUMBER_X', DoBuildNumber, 'NUMBER_X')
|
||||
self.state.add_transition_any ('NUMBER_X', DoLog, 'INIT')
|
||||
self.state.add_transition ('m', 'NUMBER_X', self.do_sgr, 'INIT')
|
||||
self.state.add_transition ('q', 'NUMBER_X', self.do_decsca, 'INIT')
|
||||
self.state.add_transition (';', 'NUMBER_X', None, 'SEMICOLON_X')
|
||||
|
||||
def process (self, c):
|
||||
"""Process a single character. Called by :meth:`write`."""
|
||||
if isinstance(c, bytes):
|
||||
c = self._decode(c)
|
||||
self.state.process(c)
|
||||
|
||||
def process_list (self, l):
|
||||
|
||||
self.write(l)
|
||||
|
||||
def write (self, s):
|
||||
"""Process text, writing it to the virtual screen while handling
|
||||
ANSI escape codes.
|
||||
"""
|
||||
if isinstance(s, bytes):
|
||||
s = self._decode(s)
|
||||
for c in s:
|
||||
self.process(c)
|
||||
|
||||
def flush (self):
|
||||
pass
|
||||
|
||||
def write_ch (self, ch):
|
||||
'''This puts a character at the current cursor position. The cursor
|
||||
position is moved forward with wrap-around, but no scrolling is done if
|
||||
the cursor hits the lower-right corner of the screen. '''
|
||||
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)
|
||||
|
||||
#\r and \n both produce a call to cr() and lf(), respectively.
|
||||
ch = ch[0]
|
||||
|
||||
if ch == u'\r':
|
||||
self.cr()
|
||||
return
|
||||
if ch == u'\n':
|
||||
self.crlf()
|
||||
return
|
||||
if ch == chr(screen.BS):
|
||||
self.cursor_back()
|
||||
return
|
||||
self.put_abs(self.cur_r, self.cur_c, ch)
|
||||
old_r = self.cur_r
|
||||
old_c = self.cur_c
|
||||
self.cursor_forward()
|
||||
if old_c == self.cur_c:
|
||||
self.cursor_down()
|
||||
if old_r != self.cur_r:
|
||||
self.cursor_home (self.cur_r, 1)
|
||||
else:
|
||||
self.scroll_up ()
|
||||
self.cursor_home (self.cur_r, 1)
|
||||
self.erase_line()
|
||||
|
||||
def do_sgr (self, fsm):
|
||||
'''Select Graphic Rendition, e.g. color. '''
|
||||
screen = fsm.memory[0]
|
||||
fsm.memory = [screen]
|
||||
|
||||
def do_decsca (self, fsm):
|
||||
'''Select character protection attribute. '''
|
||||
screen = fsm.memory[0]
|
||||
fsm.memory = [screen]
|
||||
|
||||
def do_modecrap (self, fsm):
|
||||
'''Handler for \x1b[?<number>h and \x1b[?<number>l. If anyone
|
||||
wanted to actually use these, they'd need to add more states to the
|
||||
FSM rather than just improve or override this method. '''
|
||||
screen = fsm.memory[0]
|
||||
fsm.memory = [screen]
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""This module implements a Finite State Machine (FSM). In addition to state
|
||||
'''This module implements a Finite State Machine (FSM). In addition to state
|
||||
this FSM also maintains a user defined "memory". So this FSM can be used as a
|
||||
Push-down Automata (PDA) since a PDA is a FSM + memory.
|
||||
|
||||
|
@ -64,30 +64,47 @@ current_state then the FSM will raise an exception. This may be desirable, but
|
|||
you can always prevent this just by defining a default transition.
|
||||
|
||||
Noah Spurrier 20020822
|
||||
"""
|
||||
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
class ExceptionFSM(Exception):
|
||||
|
||||
"""This is the FSM Exception class."""
|
||||
'''This is the FSM Exception class.'''
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.value)
|
||||
|
||||
return 'ExceptionFSM: ' + str(self.value)
|
||||
|
||||
class FSM:
|
||||
|
||||
"""This is a Finite State Machine (FSM).
|
||||
"""
|
||||
'''This is a Finite State Machine (FSM).
|
||||
'''
|
||||
|
||||
def __init__(self, initial_state, memory=None):
|
||||
"""This creates the FSM. You set the initial state here. The "memory"
|
||||
|
||||
'''This creates the FSM. You set the initial state here. The "memory"
|
||||
attribute is any object that you want to pass along to the action
|
||||
functions. It is not used by the FSM. For parsing you would typically
|
||||
pass a list to be used as a stack. """
|
||||
pass a list to be used as a stack. '''
|
||||
|
||||
# Map (input_symbol, current_state) --> (action, next_state).
|
||||
self.state_transitions = {}
|
||||
|
@ -102,21 +119,18 @@ class FSM:
|
|||
self.action = None
|
||||
self.memory = memory
|
||||
|
||||
def reset(self):
|
||||
"""This sets the current_state to the initial_state and sets
|
||||
def reset (self):
|
||||
|
||||
'''This sets the current_state to the initial_state and sets
|
||||
input_symbol to None. The initial state was set by the constructor
|
||||
__init__(). """
|
||||
__init__(). '''
|
||||
|
||||
self.current_state = self.initial_state
|
||||
self.input_symbol = None
|
||||
|
||||
def add_transition(
|
||||
self,
|
||||
input_symbol,
|
||||
state,
|
||||
action=None,
|
||||
next_state=None):
|
||||
"""This adds a transition that associates:
|
||||
def add_transition (self, input_symbol, state, action=None, next_state=None):
|
||||
|
||||
'''This adds a transition that associates:
|
||||
|
||||
(input_symbol, current_state) --> (action, next_state)
|
||||
|
||||
|
@ -125,34 +139,31 @@ class FSM:
|
|||
set to None in which case the current state will be unchanged.
|
||||
|
||||
You can also set transitions for a list of symbols by using
|
||||
add_transition_list(). """
|
||||
add_transition_list(). '''
|
||||
|
||||
if next_state is None:
|
||||
next_state = state
|
||||
self.state_transitions[(input_symbol, state)] = (action, next_state)
|
||||
|
||||
def add_transition_list(
|
||||
self,
|
||||
list_input_symbols,
|
||||
state,
|
||||
action=None,
|
||||
next_state=None):
|
||||
"""This adds the same transition for a list of input symbols.
|
||||
def add_transition_list (self, list_input_symbols, state, action=None, next_state=None):
|
||||
|
||||
'''This adds the same transition for a list of input symbols.
|
||||
You can pass a list or a string. Note that it is handy to use
|
||||
string.digits, string.whitespace, string.letters, etc. to add
|
||||
transitions that match character classes.
|
||||
|
||||
The action may be set to None in which case the process() method will
|
||||
ignore the action and only set the next_state. The next_state may be
|
||||
set to None in which case the current state will be unchanged. """
|
||||
set to None in which case the current state will be unchanged. '''
|
||||
|
||||
if next_state is None:
|
||||
next_state = state
|
||||
for input_symbol in list_input_symbols:
|
||||
self.add_transition(input_symbol, state, action, next_state)
|
||||
self.add_transition (input_symbol, state, action, next_state)
|
||||
|
||||
def add_transition_any(self, state, action=None, next_state=None):
|
||||
"""This adds a transition that associates:
|
||||
def add_transition_any (self, state, action=None, next_state=None):
|
||||
|
||||
'''This adds a transition that associates:
|
||||
|
||||
(current_state) --> (action, next_state)
|
||||
|
||||
|
@ -162,26 +173,28 @@ class FSM:
|
|||
|
||||
The action may be set to None in which case the process() method will
|
||||
ignore the action and only set the next_state. The next_state may be
|
||||
set to None in which case the current state will be unchanged. """
|
||||
set to None in which case the current state will be unchanged. '''
|
||||
|
||||
if next_state is None:
|
||||
next_state = state
|
||||
self.state_transitions_any[state] = (action, next_state)
|
||||
self.state_transitions_any [state] = (action, next_state)
|
||||
|
||||
def set_default_transition(self, action, next_state):
|
||||
"""This sets the default transition. This defines an action and
|
||||
def set_default_transition (self, action, next_state):
|
||||
|
||||
'''This sets the default transition. This defines an action and
|
||||
next_state if the FSM cannot find the input symbol and the current
|
||||
state in the transition list and if the FSM cannot find the
|
||||
current_state in the transition_any list. This is useful as a final
|
||||
fall-through state for catching errors and undefined states.
|
||||
|
||||
The default transition can be removed by setting the attribute
|
||||
default_transition to None. """
|
||||
default_transition to None. '''
|
||||
|
||||
self.default_transition = (action, next_state)
|
||||
|
||||
def get_transition(self, input_symbol, state):
|
||||
"""This returns (action, next state) given an input_symbol and state.
|
||||
def get_transition (self, input_symbol, state):
|
||||
|
||||
'''This returns (action, next state) given an input_symbol and state.
|
||||
This does not modify the FSM state, so calling this method has no side
|
||||
effects. Normally you do not call this method directly. It is called by
|
||||
process().
|
||||
|
@ -200,7 +213,7 @@ class FSM:
|
|||
This is a handler for errors, undefined states, or defaults.
|
||||
|
||||
4. No transition was defined. If we get here then raise an exception.
|
||||
"""
|
||||
'''
|
||||
|
||||
if (input_symbol, state) in self.state_transitions:
|
||||
return self.state_transitions[(input_symbol, state)]
|
||||
|
@ -209,32 +222,33 @@ class FSM:
|
|||
elif self.default_transition is not None:
|
||||
return self.default_transition
|
||||
else:
|
||||
raise ExceptionFSM('Transition is undefined: (%s, %s).' %
|
||||
(str(input_symbol), str(state)))
|
||||
raise ExceptionFSM ('Transition is undefined: (%s, %s).' %
|
||||
(str(input_symbol), str(state)) )
|
||||
|
||||
def process(self, input_symbol):
|
||||
"""This is the main method that you call to process input. This may
|
||||
def process (self, input_symbol):
|
||||
|
||||
'''This is the main method that you call to process input. This may
|
||||
cause the FSM to change state and call an action. This method calls
|
||||
get_transition() to find the action and next_state associated with the
|
||||
input_symbol and current_state. If the action is None then the action
|
||||
is not called and only the current state is changed. This method
|
||||
processes one complete input symbol. You can process a list of symbols
|
||||
(or a string) by calling process_list(). """
|
||||
(or a string) by calling process_list(). '''
|
||||
|
||||
self.input_symbol = input_symbol
|
||||
(self.action, self.next_state) = self.get_transition(
|
||||
self.input_symbol, self.current_state)
|
||||
(self.action, self.next_state) = self.get_transition (self.input_symbol, self.current_state)
|
||||
if self.action is not None:
|
||||
self.action(self)
|
||||
self.action (self)
|
||||
self.current_state = self.next_state
|
||||
self.next_state = None
|
||||
|
||||
def process_list(self, input_symbols):
|
||||
"""This takes a list and sends each element to process(). The list may
|
||||
be a string or any iterable object. """
|
||||
def process_list (self, input_symbols):
|
||||
|
||||
'''This takes a list and sends each element to process(). The list may
|
||||
be a string or any iterable object. '''
|
||||
|
||||
for s in input_symbols:
|
||||
self.process(s)
|
||||
self.process (s)
|
||||
|
||||
##############################################################################
|
||||
# The following is an example that demonstrates the use of the FSM class to
|
||||
|
@ -251,120 +265,70 @@ class FSM:
|
|||
##############################################################################
|
||||
|
||||
import sys
|
||||
import os
|
||||
import traceback
|
||||
import optparse
|
||||
import time
|
||||
import string
|
||||
|
||||
PY3 = (sys.version_info[0] >= 3)
|
||||
|
||||
#
|
||||
# These define the actions.
|
||||
# Note that "memory" is a list being used as a stack.
|
||||
#
|
||||
|
||||
def BeginBuildNumber (fsm):
|
||||
fsm.memory.append (fsm.input_symbol)
|
||||
|
||||
def BeginBuildNumber(fsm):
|
||||
fsm.memory.append(fsm.input_symbol)
|
||||
|
||||
|
||||
def BuildNumber(fsm):
|
||||
s = fsm.memory.pop()
|
||||
def BuildNumber (fsm):
|
||||
s = fsm.memory.pop ()
|
||||
s = s + fsm.input_symbol
|
||||
fsm.memory.append(s)
|
||||
fsm.memory.append (s)
|
||||
|
||||
def EndBuildNumber (fsm):
|
||||
s = fsm.memory.pop ()
|
||||
fsm.memory.append (int(s))
|
||||
|
||||
def EndBuildNumber(fsm):
|
||||
s = fsm.memory.pop()
|
||||
fsm.memory.append(int(s))
|
||||
|
||||
|
||||
def DoOperator(fsm):
|
||||
def DoOperator (fsm):
|
||||
ar = fsm.memory.pop()
|
||||
al = fsm.memory.pop()
|
||||
if fsm.input_symbol == '+':
|
||||
fsm.memory.append(al + ar)
|
||||
fsm.memory.append (al + ar)
|
||||
elif fsm.input_symbol == '-':
|
||||
fsm.memory.append(al - ar)
|
||||
fsm.memory.append (al - ar)
|
||||
elif fsm.input_symbol == '*':
|
||||
fsm.memory.append(al * ar)
|
||||
fsm.memory.append (al * ar)
|
||||
elif fsm.input_symbol == '/':
|
||||
fsm.memory.append(al / ar)
|
||||
fsm.memory.append (al / ar)
|
||||
|
||||
def DoEqual (fsm):
|
||||
print(str(fsm.memory.pop()))
|
||||
|
||||
def DoEqual(fsm):
|
||||
print str(fsm.memory.pop())
|
||||
|
||||
|
||||
def Error(fsm):
|
||||
print 'That does not compute.'
|
||||
print str(fsm.input_symbol)
|
||||
|
||||
def Error (fsm):
|
||||
print('That does not compute.')
|
||||
print(str(fsm.input_symbol))
|
||||
|
||||
def main():
|
||||
"""This is where the example starts and the FSM state transitions are
|
||||
|
||||
'''This is where the example starts and the FSM state transitions are
|
||||
defined. Note that states are strings (such as 'INIT'). This is not
|
||||
necessary, but it makes the example easier to read. """
|
||||
necessary, but it makes the example easier to read. '''
|
||||
|
||||
f = FSM('INIT', []) # "memory" will be used as a stack.
|
||||
f.set_default_transition(Error, 'INIT')
|
||||
f.add_transition_any('INIT', None, 'INIT')
|
||||
f.add_transition('=', 'INIT', DoEqual, 'INIT')
|
||||
f.add_transition_list(
|
||||
string.digits,
|
||||
'INIT',
|
||||
BeginBuildNumber,
|
||||
'BUILDING_NUMBER')
|
||||
f.add_transition_list(
|
||||
string.digits,
|
||||
'BUILDING_NUMBER',
|
||||
BuildNumber,
|
||||
'BUILDING_NUMBER')
|
||||
f.add_transition_list(
|
||||
string.whitespace,
|
||||
'BUILDING_NUMBER',
|
||||
EndBuildNumber,
|
||||
'INIT')
|
||||
f.add_transition_list('+-*/', 'INIT', DoOperator, 'INIT')
|
||||
f = FSM ('INIT', [])
|
||||
f.set_default_transition (Error, 'INIT')
|
||||
f.add_transition_any ('INIT', None, 'INIT')
|
||||
f.add_transition ('=', 'INIT', DoEqual, 'INIT')
|
||||
f.add_transition_list (string.digits, 'INIT', BeginBuildNumber, 'BUILDING_NUMBER')
|
||||
f.add_transition_list (string.digits, 'BUILDING_NUMBER', BuildNumber, 'BUILDING_NUMBER')
|
||||
f.add_transition_list (string.whitespace, 'BUILDING_NUMBER', EndBuildNumber, 'INIT')
|
||||
f.add_transition_list ('+-*/', 'INIT', DoOperator, 'INIT')
|
||||
|
||||
print
|
||||
print 'Enter an RPN Expression.'
|
||||
print 'Numbers may be integers. Operators are * / + -'
|
||||
print 'Use the = sign to evaluate and print the expression.'
|
||||
print 'For example: '
|
||||
print ' 167 3 2 2 * * * 1 - ='
|
||||
inputstr = raw_input('> ')
|
||||
print()
|
||||
print('Enter an RPN Expression.')
|
||||
print('Numbers may be integers. Operators are * / + -')
|
||||
print('Use the = sign to evaluate and print the expression.')
|
||||
print('For example: ')
|
||||
print(' 167 3 2 2 * * * 1 - =')
|
||||
inputstr = (input if PY3 else raw_input)('> ') # analysis:ignore
|
||||
f.process_list(inputstr)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
start_time = time.time()
|
||||
parser = optparse.OptionParser(
|
||||
formatter=optparse.TitledHelpFormatter(),
|
||||
usage=globals()['__doc__'],
|
||||
version='$Id: FSM.py 490 2007-12-07 15:46:24Z noah $')
|
||||
parser.add_option(
|
||||
'-v',
|
||||
'--verbose',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='verbose output')
|
||||
(options, args) = parser.parse_args()
|
||||
if options.verbose:
|
||||
print time.asctime()
|
||||
main()
|
||||
if options.verbose:
|
||||
print time.asctime()
|
||||
if options.verbose:
|
||||
print 'TOTAL TIME IN MINUTES:',
|
||||
if options.verbose:
|
||||
print (time.time() - start_time) / 60.0
|
||||
sys.exit(0)
|
||||
except KeyboardInterrupt as e: # Ctrl-C
|
||||
raise e
|
||||
except SystemExit as e: # sys.exit()
|
||||
raise e
|
||||
except Exception as e:
|
||||
print 'ERROR, UNEXPECTED EXCEPTION'
|
||||
print str(e)
|
||||
traceback.print_exc()
|
||||
os._exit(1)
|
||||
main()
|
|
@ -0,0 +1,85 @@
|
|||
'''Pexpect is a Python module for spawning child applications and controlling
|
||||
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.
|
||||
|
||||
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
|
||||
output. This is a handy replacement for os.system().
|
||||
|
||||
For example::
|
||||
|
||||
pexpect.run('ls -la')
|
||||
|
||||
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).
|
||||
|
||||
For example::
|
||||
|
||||
child = pexpect.spawn('scp foo user@example.com:.')
|
||||
child.expect('Password:')
|
||||
child.sendline(mypassword)
|
||||
|
||||
This works even for commands that ask for passwords or other input outside of
|
||||
the normal stdio streams. For example, ssh reads input directly from the TTY
|
||||
device which bypasses stdin.
|
||||
|
||||
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,
|
||||
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
|
||||
Spiegel, Jan Grant, and Shane Kerr. Let me know if I forgot anyone.
|
||||
|
||||
Pexpect is free, open source, and all that good stuff.
|
||||
http://pexpect.sourceforge.net/
|
||||
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
import sys
|
||||
PY3 = (sys.version_info[0] >= 3)
|
||||
|
||||
from .exceptions import ExceptionPexpect, EOF, TIMEOUT
|
||||
from .utils import split_command_line, which, is_executable_file
|
||||
from .expect import Expecter, searcher_re, searcher_string
|
||||
|
||||
if sys.platform != 'win32':
|
||||
# On Unix, these are available at the top level for backwards compatibility
|
||||
from .pty_spawn import spawn, spawnu
|
||||
from .run import run, runu
|
||||
|
||||
__version__ = '4.6.0'
|
||||
__revision__ = ''
|
||||
__all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu',
|
||||
'which', 'split_command_line', '__version__', '__revision__']
|
||||
|
||||
|
||||
|
||||
# vim: set shiftround expandtab tabstop=4 shiftwidth=4 ft=python autoindent :
|
|
@ -0,0 +1,87 @@
|
|||
import asyncio
|
||||
import errno
|
||||
|
||||
from pexpect import EOF
|
||||
|
||||
@asyncio.coroutine
|
||||
def expect_async(expecter, timeout=None):
|
||||
# First process data that was previously read - if it maches, we don't need
|
||||
# async stuff.
|
||||
previously_read = expecter.spawn.buffer
|
||||
expecter.spawn._buffer = expecter.spawn.buffer_type()
|
||||
expecter.spawn._before = expecter.spawn.buffer_type()
|
||||
idx = expecter.new_data(previously_read)
|
||||
if idx is not None:
|
||||
return idx
|
||||
if not expecter.spawn.async_pw_transport:
|
||||
pw = PatternWaiter()
|
||||
pw.set_expecter(expecter)
|
||||
transport, pw = yield from asyncio.get_event_loop()\
|
||||
.connect_read_pipe(lambda: pw, expecter.spawn)
|
||||
expecter.spawn.async_pw_transport = pw, transport
|
||||
else:
|
||||
pw, transport = expecter.spawn.async_pw_transport
|
||||
pw.set_expecter(expecter)
|
||||
transport.resume_reading()
|
||||
try:
|
||||
return (yield from asyncio.wait_for(pw.fut, timeout))
|
||||
except asyncio.TimeoutError as e:
|
||||
transport.pause_reading()
|
||||
return expecter.timeout(e)
|
||||
|
||||
|
||||
class PatternWaiter(asyncio.Protocol):
|
||||
transport = None
|
||||
|
||||
def set_expecter(self, expecter):
|
||||
self.expecter = expecter
|
||||
self.fut = asyncio.Future()
|
||||
|
||||
def found(self, result):
|
||||
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)
|
||||
self.transport.pause_reading()
|
||||
|
||||
def connection_made(self, transport):
|
||||
self.transport = transport
|
||||
|
||||
def data_received(self, data):
|
||||
spawn = self.expecter.spawn
|
||||
s = spawn._decoder.decode(data)
|
||||
spawn._log(s, 'read')
|
||||
|
||||
if self.fut.done():
|
||||
spawn._buffer.write(s)
|
||||
return
|
||||
|
||||
try:
|
||||
index = self.expecter.new_data(s)
|
||||
if index is not None:
|
||||
# Found a match
|
||||
self.found(index)
|
||||
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
|
||||
try:
|
||||
self.expecter.spawn.flag_eof = True
|
||||
index = self.expecter.eof()
|
||||
except EOF as e:
|
||||
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
|
||||
self.eof_received()
|
||||
elif exc is not None:
|
||||
self.error(exc)
|
|
@ -0,0 +1,16 @@
|
|||
# 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
|
|
@ -0,0 +1,35 @@
|
|||
"""Exception classes used by Pexpect"""
|
||||
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
class ExceptionPexpect(Exception):
|
||||
'''Base class for all exceptions raised by this module.
|
||||
'''
|
||||
|
||||
def __init__(self, value):
|
||||
super(ExceptionPexpect, self).__init__(value)
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return str(self.value)
|
||||
|
||||
def get_trace(self):
|
||||
'''This returns an abbreviated stack trace with lines that only concern
|
||||
the caller. In other words, the stack trace inside the Pexpect module
|
||||
is not included. '''
|
||||
|
||||
tblist = traceback.extract_tb(sys.exc_info()[2])
|
||||
tblist = [item for item in tblist if ('pexpect/__init__' not in item[0])
|
||||
and ('pexpect/expect' not in item[0])]
|
||||
tblist = traceback.format_list(tblist)
|
||||
return ''.join(tblist)
|
||||
|
||||
|
||||
class EOF(ExceptionPexpect):
|
||||
'''Raised when EOF is read from a child.
|
||||
This usually means the child has exited.'''
|
||||
|
||||
|
||||
class TIMEOUT(ExceptionPexpect):
|
||||
'''Raised when a read time exceeds the timeout. '''
|
|
@ -0,0 +1,306 @@
|
|||
import time
|
||||
|
||||
from .exceptions import EOF, TIMEOUT
|
||||
|
||||
class Expecter(object):
|
||||
def __init__(self, spawn, searcher, searchwindowsize=-1):
|
||||
self.spawn = spawn
|
||||
self.searcher = searcher
|
||||
if searchwindowsize == -1:
|
||||
searchwindowsize = spawn.searchwindowsize
|
||||
self.searchwindowsize = searchwindowsize
|
||||
|
||||
def new_data(self, data):
|
||||
spawn = self.spawn
|
||||
searcher = self.searcher
|
||||
|
||||
pos = spawn._buffer.tell()
|
||||
spawn._buffer.write(data)
|
||||
spawn._before.write(data)
|
||||
|
||||
# determine which chunk of data to search; if a windowsize is
|
||||
# specified, this is the *new* data + the preceding <windowsize> bytes
|
||||
if self.searchwindowsize:
|
||||
spawn._buffer.seek(max(0, pos - self.searchwindowsize))
|
||||
window = spawn._buffer.read(self.searchwindowsize + len(data))
|
||||
else:
|
||||
# otherwise, search the whole buffer (really slow for large datasets)
|
||||
window = spawn.buffer
|
||||
index = searcher.search(window, len(data))
|
||||
if index >= 0:
|
||||
spawn._buffer = spawn.buffer_type()
|
||||
spawn._buffer.write(window[searcher.end:])
|
||||
spawn.before = spawn._before.getvalue()[0:-(len(window) - searcher.start)]
|
||||
spawn._before = spawn.buffer_type()
|
||||
spawn.after = window[searcher.start: searcher.end]
|
||||
spawn.match = searcher.match
|
||||
spawn.match_index = index
|
||||
# Found a match
|
||||
return index
|
||||
elif self.searchwindowsize:
|
||||
spawn._buffer = spawn.buffer_type()
|
||||
spawn._buffer.write(window)
|
||||
|
||||
def eof(self, err=None):
|
||||
spawn = self.spawn
|
||||
|
||||
spawn.before = spawn.buffer
|
||||
spawn._buffer = spawn.buffer_type()
|
||||
spawn._before = spawn.buffer_type()
|
||||
spawn.after = EOF
|
||||
index = self.searcher.eof_index
|
||||
if index >= 0:
|
||||
spawn.match = EOF
|
||||
spawn.match_index = index
|
||||
return index
|
||||
else:
|
||||
spawn.match = None
|
||||
spawn.match_index = None
|
||||
msg = str(spawn)
|
||||
msg += '\nsearcher: %s' % self.searcher
|
||||
if err is not None:
|
||||
msg = str(err) + '\n' + msg
|
||||
raise EOF(msg)
|
||||
|
||||
def timeout(self, err=None):
|
||||
spawn = self.spawn
|
||||
|
||||
spawn.before = spawn.buffer
|
||||
spawn.after = TIMEOUT
|
||||
index = self.searcher.timeout_index
|
||||
if index >= 0:
|
||||
spawn.match = TIMEOUT
|
||||
spawn.match_index = index
|
||||
return index
|
||||
else:
|
||||
spawn.match = None
|
||||
spawn.match_index = None
|
||||
msg = str(spawn)
|
||||
msg += '\nsearcher: %s' % self.searcher
|
||||
if err is not None:
|
||||
msg = str(err) + '\n' + msg
|
||||
raise TIMEOUT(msg)
|
||||
|
||||
def errored(self):
|
||||
spawn = self.spawn
|
||||
spawn.before = spawn.buffer
|
||||
spawn.after = None
|
||||
spawn.match = None
|
||||
spawn.match_index = None
|
||||
|
||||
def expect_loop(self, timeout=-1):
|
||||
"""Blocking expect"""
|
||||
spawn = self.spawn
|
||||
|
||||
if timeout is not None:
|
||||
end_time = time.time() + timeout
|
||||
|
||||
try:
|
||||
incoming = spawn.buffer
|
||||
spawn._buffer = spawn.buffer_type()
|
||||
spawn._before = spawn.buffer_type()
|
||||
while True:
|
||||
idx = self.new_data(incoming)
|
||||
# Keep reading until exception or return.
|
||||
if idx is not None:
|
||||
return idx
|
||||
# No match at this point
|
||||
if (timeout is not None) and (timeout < 0):
|
||||
return self.timeout()
|
||||
# Still have time left, so read more data
|
||||
incoming = spawn.read_nonblocking(spawn.maxread, timeout)
|
||||
if self.spawn.delayafterread is not None:
|
||||
time.sleep(self.spawn.delayafterread)
|
||||
if timeout is not None:
|
||||
timeout = end_time - time.time()
|
||||
except EOF as e:
|
||||
return self.eof(e)
|
||||
except TIMEOUT as e:
|
||||
return self.timeout(e)
|
||||
except:
|
||||
self.errored()
|
||||
raise
|
||||
|
||||
|
||||
class searcher_string(object):
|
||||
'''This is a plain string search helper for the spawn.expect_any() method.
|
||||
This helper class is for speed. For more powerful regex patterns
|
||||
see the helper class, searcher_re.
|
||||
|
||||
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
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, strings):
|
||||
'''This creates an instance of searcher_string. This argument 'strings'
|
||||
may be a list; a sequence of strings; or the EOF or TIMEOUT types. '''
|
||||
|
||||
self.eof_index = -1
|
||||
self.timeout_index = -1
|
||||
self._strings = []
|
||||
for n, s in enumerate(strings):
|
||||
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):
|
||||
'''This returns a human-readable string that represents the state of
|
||||
the object.'''
|
||||
|
||||
ss = [(ns[0], ' %d: %r' % ns) for ns in self._strings]
|
||||
ss.append((-1, 'searcher_string:'))
|
||||
if self.eof_index >= 0:
|
||||
ss.append((self.eof_index, ' %d: EOF' % self.eof_index))
|
||||
if self.timeout_index >= 0:
|
||||
ss.append((self.timeout_index,
|
||||
' %d: TIMEOUT' % self.timeout_index))
|
||||
ss.sort()
|
||||
ss = list(zip(*ss))[1]
|
||||
return '\n'.join(ss)
|
||||
|
||||
def search(self, buffer, freshlen, searchwindowsize=None):
|
||||
'''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.
|
||||
|
||||
See class spawn for the 'searchwindowsize' argument.
|
||||
|
||||
If there is a match this returns the index of that string, and sets
|
||||
'start', 'end' and 'match'. Otherwise, this returns -1. '''
|
||||
|
||||
first_match = None
|
||||
|
||||
# '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
|
||||
offset = -(freshlen + len(s))
|
||||
else:
|
||||
# better obey searchwindowsize
|
||||
offset = -searchwindowsize
|
||||
n = buffer.find(s, offset)
|
||||
if n >= 0 and (first_match is None or n < first_match):
|
||||
first_match = n
|
||||
best_index, best_match = index, s
|
||||
if first_match is None:
|
||||
return -1
|
||||
self.match = best_match
|
||||
self.start = first_match
|
||||
self.end = self.start + len(self.match)
|
||||
return best_index
|
||||
|
||||
|
||||
class searcher_re(object):
|
||||
'''This is regular expression string search helper for the
|
||||
spawn.expect_any() method. This helper class is for powerful
|
||||
pattern matching. For speed, see the helper class, searcher_string.
|
||||
|
||||
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 successful re.search
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, patterns):
|
||||
'''This creates an instance that searches for 'patterns' Where
|
||||
'patterns' may be a list or other sequence of compiled regular
|
||||
expressions, or the EOF or TIMEOUT types.'''
|
||||
|
||||
self.eof_index = -1
|
||||
self.timeout_index = -1
|
||||
self._searches = []
|
||||
for n, s in zip(list(range(len(patterns))), patterns):
|
||||
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):
|
||||
'''This returns a human-readable string that represents the state of
|
||||
the object.'''
|
||||
|
||||
#ss = [(n, ' %d: re.compile("%s")' %
|
||||
# (n, repr(s.pattern))) for n, s in self._searches]
|
||||
ss = list()
|
||||
for n, s in self._searches:
|
||||
ss.append((n, ' %d: re.compile(%r)' % (n, s.pattern)))
|
||||
ss.append((-1, 'searcher_re:'))
|
||||
if self.eof_index >= 0:
|
||||
ss.append((self.eof_index, ' %d: EOF' % self.eof_index))
|
||||
if self.timeout_index >= 0:
|
||||
ss.append((self.timeout_index, ' %d: TIMEOUT' %
|
||||
self.timeout_index))
|
||||
ss.sort()
|
||||
ss = list(zip(*ss))[1]
|
||||
return '\n'.join(ss)
|
||||
|
||||
def search(self, buffer, freshlen, searchwindowsize=None):
|
||||
'''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.
|
||||
|
||||
See class spawn for the 'searchwindowsize' argument.
|
||||
|
||||
If there is a match this returns the index of that string, and sets
|
||||
'start', 'end' and 'match'. Otherwise, returns -1.'''
|
||||
|
||||
first_match = None
|
||||
# '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:
|
||||
searchstart = max(0, len(buffer) - searchwindowsize)
|
||||
for index, s in self._searches:
|
||||
match = s.search(buffer, searchstart)
|
||||
if match is None:
|
||||
continue
|
||||
n = match.start()
|
||||
if first_match is None or n < first_match:
|
||||
first_match = n
|
||||
the_match = match
|
||||
best_index = index
|
||||
if first_match is None:
|
||||
return -1
|
||||
self.start = first_match
|
||||
self.match = the_match
|
||||
self.end = self.match.end()
|
||||
return best_index
|
|
@ -0,0 +1,148 @@
|
|||
'''This is like pexpect, but it will work with any file descriptor that you
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
from .spawnbase import SpawnBase
|
||||
from .exceptions import ExceptionPexpect, TIMEOUT
|
||||
from .utils import select_ignore_interrupts, poll_ignore_interrupts
|
||||
import os
|
||||
|
||||
__all__ = ['fdspawn']
|
||||
|
||||
class fdspawn(SpawnBase):
|
||||
'''This is like pexpect.spawn but allows you to supply your own open file
|
||||
descriptor. For example, you could use it to read through a file looking
|
||||
for patterns, or to control a modem or serial device. '''
|
||||
|
||||
def __init__ (self, fd, args=None, timeout=30, maxread=2000, searchwindowsize=None,
|
||||
logfile=None, encoding=None, codec_errors='strict', use_poll=False):
|
||||
'''This takes a file descriptor (an int) or an object that support the
|
||||
fileno() method (returning an int). All Python file-like objects
|
||||
support fileno(). '''
|
||||
|
||||
if type(fd) != type(0) and hasattr(fd, 'fileno'):
|
||||
fd = fd.fileno()
|
||||
|
||||
if type(fd) != type(0):
|
||||
raise ExceptionPexpect('The fd argument is not an int. If this is a command string then maybe you want to use pexpect.spawn.')
|
||||
|
||||
try: # make sure fd is a valid file descriptor
|
||||
os.fstat(fd)
|
||||
except OSError:
|
||||
raise ExceptionPexpect('The fd argument is not a valid file descriptor.')
|
||||
|
||||
self.args = None
|
||||
self.command = None
|
||||
SpawnBase.__init__(self, timeout, maxread, searchwindowsize, logfile,
|
||||
encoding=encoding, codec_errors=codec_errors)
|
||||
self.child_fd = fd
|
||||
self.own_fd = False
|
||||
self.closed = False
|
||||
self.name = '<file descriptor %d>' % fd
|
||||
self.use_poll = use_poll
|
||||
|
||||
def close (self):
|
||||
"""Close the file descriptor.
|
||||
|
||||
Calling this method a second time does nothing, but if the file
|
||||
descriptor was closed elsewhere, :class:`OSError` will be raised.
|
||||
"""
|
||||
if self.child_fd == -1:
|
||||
return
|
||||
|
||||
self.flush()
|
||||
os.close(self.child_fd)
|
||||
self.child_fd = -1
|
||||
self.closed = True
|
||||
|
||||
def isalive (self):
|
||||
'''This checks if the file descriptor is still valid. If :func:`os.fstat`
|
||||
does not raise an exception then we assume it is alive. '''
|
||||
|
||||
if self.child_fd == -1:
|
||||
return False
|
||||
try:
|
||||
os.fstat(self.child_fd)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def terminate (self, force=False): # pragma: no cover
|
||||
'''Deprecated and invalid. Just raises an exception.'''
|
||||
raise ExceptionPexpect('This method is not valid for file descriptors.')
|
||||
|
||||
# These four methods are left around for backwards compatibility, but not
|
||||
# documented as part of fdpexpect. You're encouraged to use os.write
|
||||
# directly.
|
||||
def send(self, s):
|
||||
"Write to fd, return number of bytes written"
|
||||
s = self._coerce_send_string(s)
|
||||
self._log(s, 'send')
|
||||
|
||||
b = self._encoder.encode(s, final=False)
|
||||
return os.write(self.child_fd, b)
|
||||
|
||||
def sendline(self, s):
|
||||
"Write to fd with trailing newline, return number of bytes written"
|
||||
s = self._coerce_send_string(s)
|
||||
return self.send(s + self.linesep)
|
||||
|
||||
def write(self, s):
|
||||
"Write to fd, return None"
|
||||
self.send(s)
|
||||
|
||||
def writelines(self, sequence):
|
||||
"Call self.write() for each item in sequence"
|
||||
for s in sequence:
|
||||
self.write(s)
|
||||
|
||||
def read_nonblocking(self, size=1, timeout=-1):
|
||||
"""
|
||||
Read from the file descriptor and return the result as a string.
|
||||
|
||||
The read_nonblocking method of :class:`SpawnBase` assumes that a call
|
||||
to os.read will not block (timeout parameter is ignored). This is not
|
||||
the case for POSIX file-like objects such as sockets and serial ports.
|
||||
|
||||
Use :func:`select.select`, timeout is implemented conditionally for
|
||||
POSIX systems.
|
||||
|
||||
:param int size: Read at most *size* bytes.
|
||||
:param int timeout: Wait timeout seconds for file descriptor to be
|
||||
ready to read. When -1 (default), use self.timeout. When 0, poll.
|
||||
:return: String containing the bytes read
|
||||
"""
|
||||
if os.name == 'posix':
|
||||
if timeout == -1:
|
||||
timeout = self.timeout
|
||||
rlist = [self.child_fd]
|
||||
wlist = []
|
||||
xlist = []
|
||||
if self.use_poll:
|
||||
rlist = poll_ignore_interrupts(rlist, timeout)
|
||||
else:
|
||||
rlist, wlist, xlist = select_ignore_interrupts(
|
||||
rlist, wlist, xlist, timeout
|
||||
)
|
||||
if self.child_fd not in rlist:
|
||||
raise TIMEOUT('Timeout exceeded.')
|
||||
return super(fdspawn, self).read_nonblocking(size)
|
|
@ -0,0 +1,188 @@
|
|||
"""Provides an interface like pexpect.spawn interface using subprocess.Popen
|
||||
"""
|
||||
import os
|
||||
import threading
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import signal
|
||||
import shlex
|
||||
|
||||
try:
|
||||
from queue import Queue, Empty # Python 3
|
||||
except ImportError:
|
||||
from Queue import Queue, Empty # Python 2
|
||||
|
||||
from .spawnbase import SpawnBase, PY3
|
||||
from .exceptions import EOF
|
||||
from .utils import string_types
|
||||
|
||||
class PopenSpawn(SpawnBase):
|
||||
def __init__(self, cmd, timeout=30, maxread=2000, searchwindowsize=None,
|
||||
logfile=None, cwd=None, env=None, encoding=None,
|
||||
codec_errors='strict', preexec_fn=None):
|
||||
super(PopenSpawn, self).__init__(timeout=timeout, maxread=maxread,
|
||||
searchwindowsize=searchwindowsize, logfile=logfile,
|
||||
encoding=encoding, codec_errors=codec_errors)
|
||||
|
||||
# Note that `SpawnBase` initializes `self.crlf` to `\r\n`
|
||||
# because the default behaviour for a PTY is to convert
|
||||
# incoming LF to `\r\n` (see the `onlcr` flag and
|
||||
# https://stackoverflow.com/a/35887657/5397009). Here we set
|
||||
# it to `os.linesep` because that is what the spawned
|
||||
# application outputs by default and `popen` doesn't translate
|
||||
# anything.
|
||||
if encoding is None:
|
||||
self.crlf = os.linesep.encode ("ascii")
|
||||
else:
|
||||
self.crlf = self.string_type (os.linesep)
|
||||
|
||||
kwargs = dict(bufsize=0, stdin=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT, stdout=subprocess.PIPE,
|
||||
cwd=cwd, preexec_fn=preexec_fn, env=env)
|
||||
|
||||
if sys.platform == 'win32':
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
kwargs['startupinfo'] = startupinfo
|
||||
kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP
|
||||
|
||||
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
|
||||
self.closed = False
|
||||
self._buf = self.string_type()
|
||||
|
||||
self._read_queue = Queue()
|
||||
self._read_thread = threading.Thread(target=self._read_incoming)
|
||||
self._read_thread.setDaemon(True)
|
||||
self._read_thread.start()
|
||||
|
||||
_read_reached_eof = False
|
||||
|
||||
def read_nonblocking(self, size, timeout):
|
||||
buf = self._buf
|
||||
if self._read_reached_eof:
|
||||
# We have already finished reading. Use up any buffered data,
|
||||
# then raise EOF
|
||||
if buf:
|
||||
self._buf = buf[size:]
|
||||
return buf[:size]
|
||||
else:
|
||||
self.flag_eof = True
|
||||
raise EOF('End Of File (EOF).')
|
||||
|
||||
if timeout == -1:
|
||||
timeout = self.timeout
|
||||
elif timeout is None:
|
||||
timeout = 1e6
|
||||
|
||||
t0 = time.time()
|
||||
while (time.time() - t0) < timeout and size and len(buf) < size:
|
||||
try:
|
||||
incoming = self._read_queue.get_nowait()
|
||||
except Empty:
|
||||
break
|
||||
else:
|
||||
if incoming is None:
|
||||
self._read_reached_eof = True
|
||||
break
|
||||
|
||||
buf += self._decoder.decode(incoming, final=False)
|
||||
|
||||
r, self._buf = buf[:size], buf[size:]
|
||||
|
||||
self._log(r, 'read')
|
||||
return r
|
||||
|
||||
def _read_incoming(self):
|
||||
"""Run in a thread to move output from a pipe to a queue."""
|
||||
fileno = self.proc.stdout.fileno()
|
||||
while 1:
|
||||
buf = b''
|
||||
try:
|
||||
buf = os.read(fileno, 1024)
|
||||
except OSError as e:
|
||||
self._log(e, 'read')
|
||||
|
||||
if not buf:
|
||||
# This indicates we have reached EOF
|
||||
self._read_queue.put(None)
|
||||
return
|
||||
|
||||
self._read_queue.put(buf)
|
||||
|
||||
def write(self, s):
|
||||
'''This is similar to send() except that there is no return value.
|
||||
'''
|
||||
self.send(s)
|
||||
|
||||
def writelines(self, sequence):
|
||||
'''This calls write() for each element in the sequence.
|
||||
|
||||
The sequence can be any iterable object producing strings, typically a
|
||||
list of strings. This does not add line separators. There is no return
|
||||
value.
|
||||
'''
|
||||
for s in sequence:
|
||||
self.send(s)
|
||||
|
||||
def send(self, s):
|
||||
'''Send data to the subprocess' stdin.
|
||||
|
||||
Returns the number of bytes written.
|
||||
'''
|
||||
s = self._coerce_send_string(s)
|
||||
self._log(s, 'send')
|
||||
|
||||
b = self._encoder.encode(s, final=False)
|
||||
if PY3:
|
||||
return self.proc.stdin.write(b)
|
||||
else:
|
||||
# On Python 2, .write() returns None, so we return the length of
|
||||
# bytes written ourselves. This assumes they all got written.
|
||||
self.proc.stdin.write(b)
|
||||
return len(b)
|
||||
|
||||
def sendline(self, s=''):
|
||||
'''Wraps send(), sending string ``s`` to child process, with os.linesep
|
||||
automatically appended. Returns number of bytes written. '''
|
||||
|
||||
n = self.send(s)
|
||||
return n + self.send(self.linesep)
|
||||
|
||||
def wait(self):
|
||||
'''Wait for the subprocess to finish.
|
||||
|
||||
Returns the exit code.
|
||||
'''
|
||||
status = self.proc.wait()
|
||||
if status >= 0:
|
||||
self.exitstatus = status
|
||||
self.signalstatus = None
|
||||
else:
|
||||
self.exitstatus = None
|
||||
self.signalstatus = -status
|
||||
self.terminated = True
|
||||
return status
|
||||
|
||||
def kill(self, sig):
|
||||
'''Sends a Unix signal to the subprocess.
|
||||
|
||||
Use constants from the :mod:`signal` module to specify which signal.
|
||||
'''
|
||||
if sys.platform == 'win32':
|
||||
if sig in [signal.SIGINT, signal.CTRL_C_EVENT]:
|
||||
sig = signal.CTRL_C_EVENT
|
||||
elif sig in [signal.SIGBREAK, signal.CTRL_BREAK_EVENT]:
|
||||
sig = signal.CTRL_BREAK_EVENT
|
||||
else:
|
||||
sig = signal.SIGTERM
|
||||
|
||||
os.kill(self.proc.pid, sig)
|
||||
|
||||
def sendeof(self):
|
||||
'''Closes the stdin pipe from the writing end.'''
|
||||
self.proc.stdin.close()
|
|
@ -0,0 +1,833 @@
|
|||
import os
|
||||
import sys
|
||||
import time
|
||||
import pty
|
||||
import tty
|
||||
import errno
|
||||
import signal
|
||||
from contextlib import contextmanager
|
||||
|
||||
import ptyprocess
|
||||
from ptyprocess.ptyprocess import use_native_pty_fork
|
||||
|
||||
from .exceptions import ExceptionPexpect, EOF, TIMEOUT
|
||||
from .spawnbase import SpawnBase
|
||||
from .utils import (
|
||||
which, split_command_line, select_ignore_interrupts, poll_ignore_interrupts
|
||||
)
|
||||
|
||||
@contextmanager
|
||||
def _wrap_ptyprocess_err():
|
||||
"""Turn ptyprocess errors into our own ExceptionPexpect errors"""
|
||||
try:
|
||||
yield
|
||||
except ptyprocess.PtyProcessError as e:
|
||||
raise ExceptionPexpect(*e.args)
|
||||
|
||||
PY3 = (sys.version_info[0] >= 3)
|
||||
|
||||
class spawn(SpawnBase):
|
||||
'''This is the main class interface for Pexpect. Use this class to start
|
||||
and control child applications. '''
|
||||
|
||||
# This is purely informational now - changing it has no effect
|
||||
use_native_pty_fork = use_native_pty_fork
|
||||
|
||||
def __init__(self, command, args=[], timeout=30, maxread=2000,
|
||||
searchwindowsize=None, logfile=None, cwd=None, env=None,
|
||||
ignore_sighup=False, echo=True, preexec_fn=None,
|
||||
encoding=None, codec_errors='strict', dimensions=None,
|
||||
use_poll=False):
|
||||
'''This is the constructor. The command parameter may be a string that
|
||||
includes a command and any arguments to the command. For example::
|
||||
|
||||
child = pexpect.spawn('/usr/bin/ftp')
|
||||
child = pexpect.spawn('/usr/bin/ssh user@example.com')
|
||||
child = pexpect.spawn('ls -latr /tmp')
|
||||
|
||||
You may also construct it with a list of arguments like so::
|
||||
|
||||
child = pexpect.spawn('/usr/bin/ftp', [])
|
||||
child = pexpect.spawn('/usr/bin/ssh', ['user@example.com'])
|
||||
child = pexpect.spawn('ls', ['-latr', '/tmp'])
|
||||
|
||||
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
|
||||
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::
|
||||
|
||||
child = pexpect.spawn('/bin/bash -c "ls -l | grep LOG > logs.txt"')
|
||||
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::
|
||||
|
||||
shell_cmd = 'ls -l | grep LOG > logs.txt'
|
||||
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.
|
||||
|
||||
When the keyword argument *searchwindowsize* is None (default), the
|
||||
full buffer is searched at each iteration of receiving incoming data.
|
||||
The default number of bytes scanned at each iteration is very large
|
||||
and may be reduced to collaterally reduce search cost. After
|
||||
:meth:`~.expect` returns, the full buffer attribute remains up to
|
||||
size *maxread* irrespective of *searchwindowsize* value.
|
||||
|
||||
When the keyword argument ``timeout`` is specified as a number,
|
||||
(default: *30*), then :class:`TIMEOUT` will be raised after the value
|
||||
specified has elapsed, in seconds, for any of the :meth:`~.expect`
|
||||
family of method calls. When None, TIMEOUT will not be raised, and
|
||||
:meth:`~.expect` may block indefinitely until match.
|
||||
|
||||
|
||||
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')
|
||||
fout = open('mylog.txt','wb')
|
||||
child.logfile = fout
|
||||
|
||||
Example log to stdout::
|
||||
|
||||
# In Python 2:
|
||||
child = pexpect.spawn('some_command')
|
||||
child.logfile = sys.stdout
|
||||
|
||||
# In Python 3, we'll use the ``encoding`` argument to decode data
|
||||
# from the subprocess and handle it as unicode:
|
||||
child = pexpect.spawn('some_command', encoding='utf-8')
|
||||
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
|
||||
|
||||
You will need to pass an encoding to spawn in the above code if you are
|
||||
using Python 3.
|
||||
|
||||
To separately log output sent to the child use logfile_send::
|
||||
|
||||
child.logfile_send = fout
|
||||
|
||||
If ``ignore_sighup`` is True, the child process will ignore SIGHUP
|
||||
signals. The default is False from Pexpect 4.0, meaning that SIGHUP
|
||||
will be handled normally by the child.
|
||||
|
||||
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 None to return to the old behavior.
|
||||
|
||||
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::
|
||||
|
||||
child = pexpect.spawn('some_command')
|
||||
child.close()
|
||||
print(child.exitstatus, child.signalstatus)
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
If preexec_fn is given, it will be called in the child process before
|
||||
launching the given command. This is useful to e.g. reset inherited
|
||||
signal handlers.
|
||||
|
||||
The dimensions attribute specifies the size of the pseudo-terminal as
|
||||
seen by the subprocess, and is specified as a two-entry tuple (rows,
|
||||
columns). If this is unspecified, the defaults in ptyprocess will apply.
|
||||
|
||||
The use_poll attribute enables using select.poll() over select.select()
|
||||
for socket handling. This is handy if your system could have > 1024 fds
|
||||
'''
|
||||
super(spawn, self).__init__(timeout=timeout, maxread=maxread, searchwindowsize=searchwindowsize,
|
||||
logfile=logfile, encoding=encoding, codec_errors=codec_errors)
|
||||
self.STDIN_FILENO = pty.STDIN_FILENO
|
||||
self.STDOUT_FILENO = pty.STDOUT_FILENO
|
||||
self.STDERR_FILENO = pty.STDERR_FILENO
|
||||
self.cwd = cwd
|
||||
self.env = env
|
||||
self.echo = echo
|
||||
self.ignore_sighup = ignore_sighup
|
||||
self.__irix_hack = sys.platform.lower().startswith('irix')
|
||||
if command is None:
|
||||
self.command = None
|
||||
self.args = None
|
||||
self.name = '<pexpect factory incomplete>'
|
||||
else:
|
||||
self._spawn(command, args, preexec_fn, dimensions)
|
||||
self.use_poll = use_poll
|
||||
|
||||
def __str__(self):
|
||||
'''This returns a human-readable string that represents the state of
|
||||
the object. '''
|
||||
|
||||
s = []
|
||||
s.append(repr(self))
|
||||
s.append('command: ' + str(self.command))
|
||||
s.append('args: %r' % (self.args,))
|
||||
s.append('buffer (last 100 chars): %r' % self.buffer[-100:])
|
||||
s.append('before (last 100 chars): %r' % self.before[-100:] if self.before else '')
|
||||
s.append('after: %r' % (self.after,))
|
||||
s.append('match: %r' % (self.match,))
|
||||
s.append('match_index: ' + str(self.match_index))
|
||||
s.append('exitstatus: ' + str(self.exitstatus))
|
||||
if hasattr(self, 'ptyproc'):
|
||||
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)
|
||||
|
||||
def _spawn(self, command, args=[], preexec_fn=None, dimensions=None):
|
||||
'''This starts the given command in a child process. This does all the
|
||||
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
|
||||
set to parsed arguments. '''
|
||||
|
||||
# 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.
|
||||
# That may not necessarily be bad because you may have spawned a child
|
||||
# that performs some task; creates no stdout output; and then dies.
|
||||
|
||||
# If command is an int type then it may represent a file descriptor.
|
||||
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.')
|
||||
|
||||
if not isinstance(args, type([])):
|
||||
raise TypeError('The argument, args, must be a list.')
|
||||
|
||||
if args == []:
|
||||
self.args = split_command_line(command)
|
||||
self.command = self.args[0]
|
||||
else:
|
||||
# Make a shallow copy of the args list.
|
||||
self.args = args[:]
|
||||
self.args.insert(0, command)
|
||||
self.command = command
|
||||
|
||||
command_with_path = which(self.command, env=self.env)
|
||||
if command_with_path is None:
|
||||
raise ExceptionPexpect('The command was not found or was not ' +
|
||||
'executable: %s.' % self.command)
|
||||
self.command = command_with_path
|
||||
self.args[0] = self.command
|
||||
|
||||
self.name = '<' + ' '.join(self.args) + '>'
|
||||
|
||||
assert self.pid is None, 'The pid member must be None.'
|
||||
assert self.command is not None, 'The command member must not be None.'
|
||||
|
||||
kwargs = {'echo': self.echo, 'preexec_fn': preexec_fn}
|
||||
if self.ignore_sighup:
|
||||
def preexec_wrapper():
|
||||
"Set SIGHUP to be ignored, then call the real preexec_fn"
|
||||
signal.signal(signal.SIGHUP, signal.SIG_IGN)
|
||||
if preexec_fn is not None:
|
||||
preexec_fn()
|
||||
kwargs['preexec_fn'] = preexec_wrapper
|
||||
|
||||
if dimensions is not None:
|
||||
kwargs['dimensions'] = dimensions
|
||||
|
||||
if self.encoding is not None:
|
||||
# Encode command line using the specified encoding
|
||||
self.args = [a if isinstance(a, bytes) else a.encode(self.encoding)
|
||||
for a in self.args]
|
||||
|
||||
self.ptyproc = self._spawnpty(self.args, env=self.env,
|
||||
cwd=self.cwd, **kwargs)
|
||||
|
||||
self.pid = self.ptyproc.pid
|
||||
self.child_fd = self.ptyproc.fd
|
||||
|
||||
|
||||
self.terminated = False
|
||||
self.closed = False
|
||||
|
||||
def _spawnpty(self, args, **kwargs):
|
||||
'''Spawn a pty and return an instance of PtyProcess.'''
|
||||
return ptyprocess.PtyProcess.spawn(args, **kwargs)
|
||||
|
||||
def close(self, force=True):
|
||||
'''This closes the connection with the child application. Note that
|
||||
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
|
||||
and SIGINT). '''
|
||||
|
||||
self.flush()
|
||||
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
|
||||
self.closed = True
|
||||
|
||||
def isatty(self):
|
||||
'''This returns True if the file descriptor is open and connected to a
|
||||
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. '''
|
||||
|
||||
return os.isatty(self.child_fd)
|
||||
|
||||
def waitnoecho(self, timeout=-1):
|
||||
'''This waits until the terminal ECHO flag is set False. This returns
|
||||
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::
|
||||
|
||||
p = pexpect.spawn('ssh user@example.com')
|
||||
p.waitnoecho()
|
||||
p.sendline(mypassword)
|
||||
|
||||
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.
|
||||
'''
|
||||
|
||||
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)
|
||||
|
||||
def getecho(self):
|
||||
'''This returns the terminal echo mode. This returns True if echo is
|
||||
on or False if echo is off. Child applications that are expecting you
|
||||
to enter a password often set ECHO False. See waitnoecho().
|
||||
|
||||
Not supported on platforms where ``isatty()`` returns False. '''
|
||||
return self.ptyproc.getecho()
|
||||
|
||||
def setecho(self, state):
|
||||
'''This sets the terminal echo mode on or off. Note that anything the
|
||||
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::
|
||||
|
||||
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.
|
||||
p.setecho(False) # Turn off tty echo
|
||||
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'])
|
||||
|
||||
The following WILL NOT WORK because the lines sent before the setecho
|
||||
will be lost::
|
||||
|
||||
p = pexpect.spawn('cat')
|
||||
p.sendline('1234')
|
||||
p.setecho(False) # Turn off tty echo
|
||||
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'])
|
||||
|
||||
|
||||
Not supported on platforms where ``isatty()`` returns False.
|
||||
'''
|
||||
return self.ptyproc.setecho(state)
|
||||
|
||||
def read_nonblocking(self, size=1, timeout=-1):
|
||||
'''This reads at most size characters from the child application. It
|
||||
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 logfile is specified, a
|
||||
copy is written to that log.
|
||||
|
||||
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.
|
||||
|
||||
The timeout refers only to the amount of time to read at least one
|
||||
character. This is not affected 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
|
||||
implement the timeout. '''
|
||||
|
||||
if self.closed:
|
||||
raise ValueError('I/O operation on closed file.')
|
||||
|
||||
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.')
|
||||
elif self.__irix_hack:
|
||||
# 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.
|
||||
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 self.child_fd in r:
|
||||
return super(spawn, self).read_nonblocking(size)
|
||||
|
||||
raise ExceptionPexpect('Reached an unexpected state.') # pragma: no cover
|
||||
|
||||
def write(self, s):
|
||||
'''This is similar to send() except that there is no return value.
|
||||
'''
|
||||
|
||||
self.send(s)
|
||||
|
||||
def writelines(self, sequence):
|
||||
'''This calls write() for each element in the sequence. The sequence
|
||||
can be any iterable object producing strings, typically a list of
|
||||
strings. This does not add line separators. There is no return value.
|
||||
'''
|
||||
|
||||
for s in sequence:
|
||||
self.write(s)
|
||||
|
||||
def send(self, s):
|
||||
'''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.
|
||||
|
||||
The default terminal input mode is canonical processing unless set
|
||||
otherwise by the child process. This allows backspace and other line
|
||||
processing to be performed prior to transmitting to the receiving
|
||||
program. As this is buffered, there is a limited size of such buffer.
|
||||
|
||||
On Linux systems, this is 4096 (defined by N_TTY_BUF_SIZE). All
|
||||
other systems honor the POSIX.1 definition PC_MAX_CANON -- 1024
|
||||
on OSX, 256 on OpenSolaris, and 1920 on FreeBSD.
|
||||
|
||||
This value may be discovered using fpathconf(3)::
|
||||
|
||||
>>> from os import fpathconf
|
||||
>>> print(fpathconf(0, 'PC_MAX_CANON'))
|
||||
256
|
||||
|
||||
On such a system, only 256 bytes may be received per line. Any
|
||||
subsequent bytes received will be discarded. BEL (``'\a'``) is then
|
||||
sent to output if IMAXBEL (termios.h) is set by the tty driver.
|
||||
This is usually enabled by default. Linux does not honor this as
|
||||
an option -- it behaves as though it is always set on.
|
||||
|
||||
Canonical input processing may be disabled altogether by executing
|
||||
a shell, then stty(1), before executing the final program::
|
||||
|
||||
>>> bash = pexpect.spawn('/bin/bash', echo=False)
|
||||
>>> bash.sendline('stty -icanon')
|
||||
>>> bash.sendline('base64')
|
||||
>>> bash.sendline('x' * 5000)
|
||||
'''
|
||||
|
||||
if self.delaybeforesend is not None:
|
||||
time.sleep(self.delaybeforesend)
|
||||
|
||||
s = self._coerce_send_string(s)
|
||||
self._log(s, 'send')
|
||||
|
||||
b = self._encoder.encode(s, final=False)
|
||||
return os.write(self.child_fd, b)
|
||||
|
||||
def sendline(self, s=''):
|
||||
'''Wraps send(), sending string ``s`` to child process, with
|
||||
``os.linesep`` automatically appended. Returns number of bytes
|
||||
written. Only a limited number of bytes may be sent for each
|
||||
line in the default terminal mode, see docstring of :meth:`send`.
|
||||
'''
|
||||
s = self._coerce_send_string(s)
|
||||
return self.send(s + self.linesep)
|
||||
|
||||
def _log_control(self, s):
|
||||
"""Write control characters to the appropriate log files"""
|
||||
if self.encoding is not None:
|
||||
s = s.decode(self.encoding, 'replace')
|
||||
self._log(s, 'send')
|
||||
|
||||
def sendcontrol(self, char):
|
||||
'''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')::
|
||||
|
||||
child.sendcontrol('g')
|
||||
|
||||
See also, sendintr() and sendeof().
|
||||
'''
|
||||
n, byte = self.ptyproc.sendcontrol(char)
|
||||
self._log_control(byte)
|
||||
return n
|
||||
|
||||
def sendeof(self):
|
||||
'''This sends an EOF to the child. This sends a character which causes
|
||||
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
|
||||
beginning of a line. '''
|
||||
|
||||
n, byte = self.ptyproc.sendeof()
|
||||
self._log_control(byte)
|
||||
|
||||
def sendintr(self):
|
||||
'''This sends a SIGINT to the child. It does not require
|
||||
the SIGINT to be the first character on a line. '''
|
||||
|
||||
n, byte = self.ptyproc.sendintr()
|
||||
self._log_control(byte)
|
||||
|
||||
@property
|
||||
def flag_eof(self):
|
||||
return self.ptyproc.flag_eof
|
||||
|
||||
@flag_eof.setter
|
||||
def flag_eof(self, value):
|
||||
self.ptyproc.flag_eof = value
|
||||
|
||||
def eof(self):
|
||||
'''This returns True if the EOF exception was ever raised.
|
||||
'''
|
||||
return self.flag_eof
|
||||
|
||||
def terminate(self, force=False):
|
||||
'''This forces a child process to terminate. It starts nicely with
|
||||
SIGHUP and SIGINT. If "force" is True then moves onto SIGKILL. This
|
||||
returns True if the child was terminated. This returns False if the
|
||||
child could not be terminated. '''
|
||||
|
||||
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
|
||||
except OSError:
|
||||
# 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):
|
||||
'''This waits until the child exits. This is a blocking call. This will
|
||||
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
|
||||
may have printed output then called exit(), but, the child is
|
||||
technically still alive until its output is read by the parent.
|
||||
|
||||
This method is non-blocking if :meth:`wait` has already been called
|
||||
previously or :meth:`isalive` method returns False. It simply returns
|
||||
the previously determined exit status.
|
||||
'''
|
||||
|
||||
ptyproc = self.ptyproc
|
||||
with _wrap_ptyprocess_err():
|
||||
# exception may occur if "Is some other process attempting
|
||||
# "job control with our child pid?"
|
||||
exitstatus = ptyproc.wait()
|
||||
self.status = ptyproc.status
|
||||
self.exitstatus = ptyproc.exitstatus
|
||||
self.signalstatus = ptyproc.signalstatus
|
||||
self.terminated = True
|
||||
|
||||
return exitstatus
|
||||
|
||||
def isalive(self):
|
||||
'''This tests if the child process is running or not. This is
|
||||
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
|
||||
SECONDS for Solaris to return the right status. '''
|
||||
|
||||
ptyproc = self.ptyproc
|
||||
with _wrap_ptyprocess_err():
|
||||
alive = ptyproc.isalive()
|
||||
|
||||
if not alive:
|
||||
self.status = ptyproc.status
|
||||
self.exitstatus = ptyproc.exitstatus
|
||||
self.signalstatus = ptyproc.signalstatus
|
||||
self.terminated = True
|
||||
|
||||
return alive
|
||||
|
||||
def kill(self, sig):
|
||||
|
||||
'''This sends the given signal to the child application. In keeping
|
||||
with UNIX tradition it has a misleading name. It does not necessarily
|
||||
kill the child unless you send the right signal. '''
|
||||
|
||||
# Same as os.kill, but the pid is given for you.
|
||||
if self.isalive():
|
||||
os.kill(self.pid, sig)
|
||||
|
||||
def getwinsize(self):
|
||||
'''This returns the terminal window size of the child tty. The return
|
||||
value is a tuple of (rows, cols). '''
|
||||
return self.ptyproc.getwinsize()
|
||||
|
||||
def setwinsize(self, rows, cols):
|
||||
'''This sets the terminal window size of the child tty. This will cause
|
||||
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
|
||||
SIGWINCH signal. '''
|
||||
return self.ptyproc.setwinsize(rows, cols)
|
||||
|
||||
|
||||
def interact(self, escape_character=chr(29),
|
||||
input_filter=None, output_filter=None):
|
||||
|
||||
'''This gives control of the child process to the interactive user (the
|
||||
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 return None. The escape_character
|
||||
will not be transmitted. The default for escape_character is
|
||||
entered as ``Ctrl - ]``, the very same as BSD telnet. To prevent
|
||||
escaping, escape_character may be set to None.
|
||||
|
||||
If a logfile is specified, then the data sent and received from the
|
||||
child process in interact mode is duplicated to the given log.
|
||||
|
||||
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)
|
||||
a = struct.unpack('hhhh', fcntl.ioctl(sys.stdout.fileno(),
|
||||
termios.TIOCGWINSZ , s))
|
||||
if not p.closed:
|
||||
p.setwinsize(a[0],a[1])
|
||||
|
||||
# Note this 'p' is global and used in sigwinch_passthrough.
|
||||
p = pexpect.spawn('/bin/bash')
|
||||
signal.signal(signal.SIGWINCH, sigwinch_passthrough)
|
||||
p.interact()
|
||||
'''
|
||||
|
||||
# Flush the buffer.
|
||||
self.write_to_stdout(self.buffer)
|
||||
self.stdout.flush()
|
||||
self._buffer = self.buffer_type()
|
||||
mode = tty.tcgetattr(self.STDIN_FILENO)
|
||||
tty.setraw(self.STDIN_FILENO)
|
||||
if escape_character is not None and PY3:
|
||||
escape_character = escape_character.encode('latin-1')
|
||||
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):
|
||||
'''This is used by the interact() method.
|
||||
'''
|
||||
|
||||
while data != b'' and self.isalive():
|
||||
n = os.write(fd, data)
|
||||
data = data[n:]
|
||||
|
||||
def __interact_read(self, fd):
|
||||
'''This is used by the interact() method.
|
||||
'''
|
||||
|
||||
return os.read(fd, 1000)
|
||||
|
||||
def __interact_copy(
|
||||
self, escape_character=None, input_filter=None, output_filter=None
|
||||
):
|
||||
|
||||
'''This is used by the interact() method.
|
||||
'''
|
||||
|
||||
while self.isalive():
|
||||
if self.use_poll:
|
||||
r = poll_ignore_interrupts([self.child_fd, self.STDIN_FILENO])
|
||||
else:
|
||||
r, w, e = select_ignore_interrupts(
|
||||
[self.child_fd, self.STDIN_FILENO], [], []
|
||||
)
|
||||
if self.child_fd in r:
|
||||
try:
|
||||
data = self.__interact_read(self.child_fd)
|
||||
except OSError as err:
|
||||
if err.args[0] == errno.EIO:
|
||||
# Linux-style EOF
|
||||
break
|
||||
raise
|
||||
if data == b'':
|
||||
# BSD-style EOF
|
||||
break
|
||||
if output_filter:
|
||||
data = output_filter(data)
|
||||
self._log(data, 'read')
|
||||
os.write(self.STDOUT_FILENO, data)
|
||||
if self.STDIN_FILENO in r:
|
||||
data = self.__interact_read(self.STDIN_FILENO)
|
||||
if input_filter:
|
||||
data = input_filter(data)
|
||||
i = -1
|
||||
if escape_character is not None:
|
||||
i = data.rfind(escape_character)
|
||||
if i != -1:
|
||||
data = data[:i]
|
||||
if data:
|
||||
self._log(data, 'send')
|
||||
self.__interact_writen(self.child_fd, data)
|
||||
break
|
||||
self._log(data, 'send')
|
||||
self.__interact_writen(self.child_fd, data)
|
||||
|
||||
|
||||
def spawnu(*args, **kwargs):
|
||||
"""Deprecated: pass encoding to spawn() instead."""
|
||||
kwargs.setdefault('encoding', 'utf-8')
|
||||
return spawn(*args, **kwargs)
|
|
@ -0,0 +1,499 @@
|
|||
'''This class extends pexpect.spawn to specialize setting up SSH connections.
|
||||
This adds methods for login, logout, and expecting the shell prompt.
|
||||
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
from pexpect import ExceptionPexpect, TIMEOUT, EOF, spawn
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
|
||||
__all__ = ['ExceptionPxssh', 'pxssh']
|
||||
|
||||
# Exception classes used by this module.
|
||||
class ExceptionPxssh(ExceptionPexpect):
|
||||
'''Raised for pxssh exceptions.
|
||||
'''
|
||||
|
||||
if sys.version_info > (3, 0):
|
||||
from shlex import quote
|
||||
else:
|
||||
_find_unsafe = re.compile(r'[^\w@%+=:,./-]').search
|
||||
|
||||
def quote(s):
|
||||
"""Return a shell-escaped version of the string *s*."""
|
||||
if not s:
|
||||
return "''"
|
||||
if _find_unsafe(s) is None:
|
||||
return s
|
||||
|
||||
# use single quotes, and put single quotes into double quotes
|
||||
# the string $'b is then quoted as '$'"'"'b'
|
||||
return "'" + s.replace("'", "'\"'\"'") + "'"
|
||||
|
||||
class pxssh (spawn):
|
||||
'''This class extends pexpect.spawn to specialize setting up SSH
|
||||
connections. This adds methods for login, logout, and expecting the shell
|
||||
prompt. It does various tricky things to handle many situations in the SSH
|
||||
login process. For example, if the session is your first login, then pxssh
|
||||
automatically accepts the remote certificate; or if you have public key
|
||||
authentication setup then pxssh won't wait for the password prompt.
|
||||
|
||||
pxssh uses the shell prompt to synchronize output from the remote host. In
|
||||
order to make this more robust it sets the shell prompt to something more
|
||||
unique than just $ or #. This should work on most Borne/Bash or Csh style
|
||||
shells.
|
||||
|
||||
Example that runs a few commands on a remote server and prints the result::
|
||||
|
||||
from pexpect import pxssh
|
||||
import getpass
|
||||
try:
|
||||
s = pxssh.pxssh()
|
||||
hostname = raw_input('hostname: ')
|
||||
username = raw_input('username: ')
|
||||
password = getpass.getpass('password: ')
|
||||
s.login(hostname, username, password)
|
||||
s.sendline('uptime') # run a command
|
||||
s.prompt() # match the prompt
|
||||
print(s.before) # print everything before the prompt.
|
||||
s.sendline('ls -l')
|
||||
s.prompt()
|
||||
print(s.before)
|
||||
s.sendline('df')
|
||||
s.prompt()
|
||||
print(s.before)
|
||||
s.logout()
|
||||
except pxssh.ExceptionPxssh as e:
|
||||
print("pxssh failed on login.")
|
||||
print(e)
|
||||
|
||||
Example showing how to specify SSH options::
|
||||
|
||||
from pexpect import pxssh
|
||||
s = pxssh.pxssh(options={
|
||||
"StrictHostKeyChecking": "no",
|
||||
"UserKnownHostsFile": "/dev/null"})
|
||||
...
|
||||
|
||||
Note that if you have ssh-agent running while doing development with pxssh
|
||||
then this can lead to a lot of confusion. Many X display managers (xdm,
|
||||
gdm, kdm, etc.) will automatically start a GUI agent. You may see a GUI
|
||||
dialog box popup asking for a password during development. You should turn
|
||||
off any key agents during testing. The 'force_password' attribute will turn
|
||||
off public key authentication. This will only work if the remote SSH server
|
||||
is configured to allow password logins. Example of using 'force_password'
|
||||
attribute::
|
||||
|
||||
s = pxssh.pxssh()
|
||||
s.force_password = True
|
||||
hostname = raw_input('hostname: ')
|
||||
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()`.
|
||||
'''
|
||||
|
||||
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):
|
||||
|
||||
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)
|
||||
|
||||
self.name = '<pxssh>'
|
||||
|
||||
#SUBTLE HACK ALERT! Note that the command that SETS the prompt uses a
|
||||
#slightly different string than the regular expression to match it. This
|
||||
#is because when you set the prompt the command will echo back, but we
|
||||
#don't want to match the echoed command. So if we make the set command
|
||||
#slightly different than the regex we eliminate the problem. To make the
|
||||
#set command different we add a backslash in front of $. The $ doesn't
|
||||
#need to be escaped, but it doesn't hurt and serves to make the set
|
||||
#prompt command different than the regex.
|
||||
|
||||
# used to match the command-line prompt
|
||||
self.UNIQUE_PROMPT = r"\[PEXPECT\][\$\#] "
|
||||
self.PROMPT = self.UNIQUE_PROMPT
|
||||
|
||||
# used to set shell command-line prompt to UNIQUE_PROMPT.
|
||||
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.
|
||||
# + " -o 'StrictHostKeyChecking=no'"
|
||||
# + " -o 'UserKnownHostsFile /dev/null' ")
|
||||
# Disabling X11 forwarding gets rid of the annoying SSH_ASKPASS from
|
||||
# displaying a GUI password dialog. I have not figured out how to
|
||||
# disable only SSH_ASKPASS without also disabling X11 forwarding.
|
||||
# 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,
|
||||
# ssh.otions = dict(StrictHostKeyChecking="no",UserKnownHostsFile="/dev/null")
|
||||
self.options = options
|
||||
|
||||
def levenshtein_distance(self, a, b):
|
||||
'''This calculates the Levenshtein distance between a and b.
|
||||
'''
|
||||
|
||||
n, m = len(a), len(b)
|
||||
if n > m:
|
||||
a,b = b,a
|
||||
n,m = m,n
|
||||
current = range(n+1)
|
||||
for i in range(1,m+1):
|
||||
previous, current = current, [i]+[0]*n
|
||||
for j in range(1,n+1):
|
||||
add, delete = previous[j]+1, current[j-1]+1
|
||||
change = previous[j-1]
|
||||
if a[j-1] != b[i-1]:
|
||||
change = change + 1
|
||||
current[j] = min(add, delete, change)
|
||||
return current[n]
|
||||
|
||||
def try_read_prompt(self, timeout_multiplier):
|
||||
'''This facilitates using communication timeouts to perform
|
||||
synchronization as quickly as possible, while supporting high latency
|
||||
connections with a tunable worst case performance. Fast connections
|
||||
should be read almost immediately. Worst case performance for this
|
||||
method is timeout_multiplier * 3 seconds.
|
||||
'''
|
||||
|
||||
# maximum time allowed to read the first response
|
||||
first_char_timeout = timeout_multiplier * 0.5
|
||||
|
||||
# maximum time allowed between subsequent characters
|
||||
inter_char_timeout = timeout_multiplier * 0.1
|
||||
|
||||
# maximum time for reading the entire prompt
|
||||
total_timeout = timeout_multiplier * 3.0
|
||||
|
||||
prompt = self.string_type()
|
||||
begin = time.time()
|
||||
expired = 0.0
|
||||
timeout = first_char_timeout
|
||||
|
||||
while expired < total_timeout:
|
||||
try:
|
||||
prompt += self.read_nonblocking(size=1, timeout=timeout)
|
||||
expired = time.time() - begin # updated total time expired
|
||||
timeout = inter_char_timeout
|
||||
except TIMEOUT:
|
||||
break
|
||||
|
||||
return prompt
|
||||
|
||||
def sync_original_prompt (self, sync_multiplier=1.0):
|
||||
'''This attempts to find the prompt. Basically, press enter and record
|
||||
the response; press enter again and record the response; if the two
|
||||
responses are similar then assume we are at the original prompt.
|
||||
This can be a slow function. Worst case with the default sync_multiplier
|
||||
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.
|
||||
self.sendline()
|
||||
time.sleep(0.1)
|
||||
|
||||
try:
|
||||
# Clear the buffer before getting the prompt.
|
||||
self.try_read_prompt(sync_multiplier)
|
||||
except TIMEOUT:
|
||||
pass
|
||||
|
||||
self.sendline()
|
||||
x = self.try_read_prompt(sync_multiplier)
|
||||
|
||||
self.sendline()
|
||||
a = self.try_read_prompt(sync_multiplier)
|
||||
|
||||
self.sendline()
|
||||
b = self.try_read_prompt(sync_multiplier)
|
||||
|
||||
ld = self.levenshtein_distance(a,b)
|
||||
len_a = len(a)
|
||||
if len_a == 0:
|
||||
return False
|
||||
if float(ld)/len_a < 0.4:
|
||||
return True
|
||||
return False
|
||||
|
||||
### 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',
|
||||
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):
|
||||
'''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
|
||||
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
|
||||
called :file:`~/.hushlogin` on the remote server. If a prompt cannot be found
|
||||
then this will not necessarily cause the login to fail. In the case of
|
||||
a timeout when looking for the prompt we assume that the original
|
||||
prompt was so weird that we could not match it, so we use a few tricks
|
||||
to guess when we have reached the prompt. Then we hope for the best and
|
||||
blindly try to reset the prompt to something more unique. If that fails
|
||||
then login() raises an :class:`ExceptionPxssh` exception.
|
||||
|
||||
In some situations it is not possible or desirable to reset the
|
||||
original prompt. In this case, pass ``auto_prompt_reset=False`` to
|
||||
inhibit setting the prompt to the UNIQUE_PROMPT. Remember that pxssh
|
||||
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.
|
||||
'''
|
||||
|
||||
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)
|
||||
session_init_regex_array.extend(["(?i)connection closed by remote host", EOF])
|
||||
|
||||
ssh_options = ''.join([" -o '%s=%s'" % (o, v) for (o, v) in self.options.items()])
|
||||
if quiet:
|
||||
ssh_options = ssh_options + ' -q'
|
||||
if not check_local_ip:
|
||||
ssh_options = ssh_options + " -o'NoHostAuthenticationForLocalhost=yes'"
|
||||
if self.force_password:
|
||||
ssh_options = ssh_options + ' ' + self.SSH_OPTS
|
||||
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
|
||||
if port is not None:
|
||||
ssh_options = ssh_options + ' -p %s'%(str(port))
|
||||
if ssh_key is not None:
|
||||
# Allow forwarding our SSH key to the current session
|
||||
if ssh_key==True:
|
||||
ssh_options = ssh_options + ' -A'
|
||||
else:
|
||||
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.
|
||||
#
|
||||
# The structure should be like this:
|
||||
# { 'local': ['2424:localhost:22'], # Local SSH tunnels
|
||||
# 'remote': ['2525:localhost:22'], # Remote SSH tunnels
|
||||
# 'dynamic': [8888] } # Dynamic/SOCKS tunnels
|
||||
if ssh_tunnels!={} and isinstance({},type(ssh_tunnels)):
|
||||
tunnel_types = {
|
||||
'local':'L',
|
||||
'remote':'R',
|
||||
'dynamic':'D'
|
||||
}
|
||||
for tunnel_type in tunnel_types:
|
||||
cmd_type = tunnel_types[tunnel_type]
|
||||
if tunnel_type in ssh_tunnels:
|
||||
tunnels = ssh_tunnels[tunnel_type]
|
||||
for tunnel in tunnels:
|
||||
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 self.debug_command_string:
|
||||
return(cmd)
|
||||
|
||||
# Are we asking for a local ssh command or to spawn one in another session?
|
||||
if spawn_local_ssh:
|
||||
spawn._spawn(self, cmd)
|
||||
else:
|
||||
self.sendline(cmd)
|
||||
|
||||
# This does not distinguish between a remote server 'password' prompt
|
||||
# and a local ssh 'passphrase' prompt (for unlocking a private key).
|
||||
i = self.expect(session_init_regex_array, timeout=login_timeout)
|
||||
|
||||
# First phase
|
||||
if i==0:
|
||||
# New certificate -- always accept it.
|
||||
# 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(session_regex_array)
|
||||
if i==2: # password or passphrase
|
||||
self.sendline(password)
|
||||
i = self.expect(session_regex_array)
|
||||
if i==4:
|
||||
self.sendline(terminal_type)
|
||||
i = self.expect(session_regex_array)
|
||||
if i==7:
|
||||
self.close()
|
||||
raise ExceptionPxssh('Could not establish connection to host')
|
||||
|
||||
# Second phase
|
||||
if i==0:
|
||||
# This is weird. This should not happen twice in a row.
|
||||
self.close()
|
||||
raise ExceptionPxssh('Weird error. Got "are you sure" prompt twice.')
|
||||
elif i==1: # can occur if you have a public key pair set to authenticate.
|
||||
### TODO: May NOT be OK if expect() got tricked and matched a false prompt.
|
||||
pass
|
||||
elif i==2: # password prompt again
|
||||
# For incorrect passwords, some ssh servers will
|
||||
# ask for the password again, others return 'denied' right away.
|
||||
# If we get the password prompt again then this means
|
||||
# we didn't get the password right the first time.
|
||||
self.close()
|
||||
raise ExceptionPxssh('password refused')
|
||||
elif i==3: # permission denied -- password was bad.
|
||||
self.close()
|
||||
raise ExceptionPxssh('permission denied')
|
||||
elif i==4: # terminal type again? WTF?
|
||||
self.close()
|
||||
raise ExceptionPxssh('Weird error. Got "terminal type" prompt twice.')
|
||||
elif i==5: # Timeout
|
||||
#This is tricky... I presume that we are at the command-line prompt.
|
||||
#It may be that the shell prompt was so weird that we couldn't match
|
||||
#it. Or it may be that we couldn't log in for some other reason. I
|
||||
#can't be sure, but it's safe to guess that we did login because if
|
||||
#I presume wrong and we are not logged in then this should be caught
|
||||
#later when I try to set the shell prompt.
|
||||
pass
|
||||
elif i==6: # Connection closed by remote host
|
||||
self.close()
|
||||
raise ExceptionPxssh('connection closed')
|
||||
else: # Unexpected
|
||||
self.close()
|
||||
raise ExceptionPxssh('unexpected login response')
|
||||
if sync_original_prompt:
|
||||
if not self.sync_original_prompt(sync_multiplier):
|
||||
self.close()
|
||||
raise ExceptionPxssh('could not synchronize with original prompt')
|
||||
# We appear to be in.
|
||||
# set shell prompt to something unique.
|
||||
if auto_prompt_reset:
|
||||
if not self.set_unique_prompt():
|
||||
self.close()
|
||||
raise ExceptionPxssh('could not set shell prompt '
|
||||
'(received: %r, expected: %r).' % (
|
||||
self.before, self.PROMPT,))
|
||||
return True
|
||||
|
||||
def logout (self):
|
||||
'''Sends exit to the remote shell.
|
||||
|
||||
If there are stopped jobs then this automatically sends exit twice.
|
||||
'''
|
||||
self.sendline("exit")
|
||||
index = self.expect([EOF, "(?i)there are stopped jobs"])
|
||||
if index==1:
|
||||
self.sendline("exit")
|
||||
self.expect(EOF)
|
||||
self.close()
|
||||
|
||||
def prompt(self, timeout=-1):
|
||||
'''Match the next shell prompt.
|
||||
|
||||
This is little more than a short-cut to the :meth:`~pexpect.spawn.expect`
|
||||
method. Note that if you called :meth:`login` with
|
||||
``auto_prompt_reset=False``, then before calling :meth:`prompt` you must
|
||||
set the :attr:`PROMPT` attribute to a regex that it will use for
|
||||
matching the prompt.
|
||||
|
||||
Calling :meth:`prompt` will erase the contents of the :attr:`before`
|
||||
attribute even if no prompt is ever matched. If timeout is not given or
|
||||
it is set to -1 then self.timeout is used.
|
||||
|
||||
:return: True if the shell prompt was matched, False if the timeout was
|
||||
reached.
|
||||
'''
|
||||
|
||||
if timeout == -1:
|
||||
timeout = self.timeout
|
||||
i = self.expect([self.PROMPT, TIMEOUT], timeout=timeout)
|
||||
if i==1:
|
||||
return False
|
||||
return True
|
||||
|
||||
def set_unique_prompt(self):
|
||||
'''This sets the remote prompt to something more unique than ``#`` or ``$``.
|
||||
This makes it easier for the :meth:`prompt` method to match the shell prompt
|
||||
unambiguously. This method is called automatically by the :meth:`login`
|
||||
method, but you may want to call it manually if you somehow reset the
|
||||
shell prompt. For example, if you 'su' to a different user then you
|
||||
will need to manually reset the prompt. This sends shell commands to
|
||||
the remote host to set the prompt, so this assumes the remote host is
|
||||
ready to receive commands.
|
||||
|
||||
Alternatively, you may use your own prompt pattern. In this case you
|
||||
should call :meth:`login` with ``auto_prompt_reset=False``; then set the
|
||||
:attr:`PROMPT` attribute to a regular expression. After that, the
|
||||
:meth:`prompt` method will try to match your prompt pattern.
|
||||
'''
|
||||
|
||||
self.sendline("unset PROMPT_COMMAND")
|
||||
self.sendline(self.PROMPT_SET_SH) # sh-style
|
||||
i = self.expect ([TIMEOUT, self.PROMPT], timeout=10)
|
||||
if i == 0: # csh-style
|
||||
self.sendline(self.PROMPT_SET_CSH)
|
||||
i = self.expect([TIMEOUT, self.PROMPT], timeout=10)
|
||||
if i == 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
# vi:ts=4:sw=4:expandtab:ft=python:
|
|
@ -0,0 +1,122 @@
|
|||
"""Generic wrapper for read-eval-print-loops, a.k.a. interactive shells
|
||||
"""
|
||||
import os.path
|
||||
import signal
|
||||
import sys
|
||||
|
||||
import pexpect
|
||||
|
||||
PY3 = (sys.version_info[0] >= 3)
|
||||
|
||||
if PY3:
|
||||
basestring = str
|
||||
|
||||
PEXPECT_PROMPT = u'[PEXPECT_PROMPT>'
|
||||
PEXPECT_CONTINUATION_PROMPT = u'[PEXPECT_PROMPT+'
|
||||
|
||||
class REPLWrapper(object):
|
||||
"""Wrapper for a REPL.
|
||||
|
||||
:param cmd_or_spawn: This can either be an instance of :class:`pexpect.spawn`
|
||||
in which a REPL has already been started, or a str command to start a new
|
||||
REPL process.
|
||||
:param str orig_prompt: The prompt to expect at first.
|
||||
:param str prompt_change: A command to change the prompt to something more
|
||||
unique. If this is ``None``, the prompt will not be changed. This will
|
||||
be formatted with the new and continuation prompts as positional
|
||||
parameters, so you can use ``{}`` style formatting to insert them into
|
||||
the command.
|
||||
:param str new_prompt: The more unique prompt to expect after the change.
|
||||
:param str extra_init_cmd: Commands to do extra initialisation, such as
|
||||
disabling pagers.
|
||||
"""
|
||||
def __init__(self, cmd_or_spawn, orig_prompt, prompt_change,
|
||||
new_prompt=PEXPECT_PROMPT,
|
||||
continuation_prompt=PEXPECT_CONTINUATION_PROMPT,
|
||||
extra_init_cmd=None):
|
||||
if isinstance(cmd_or_spawn, basestring):
|
||||
self.child = pexpect.spawn(cmd_or_spawn, echo=False, encoding='utf-8')
|
||||
else:
|
||||
self.child = cmd_or_spawn
|
||||
if self.child.echo:
|
||||
# Existing spawn instance has echo enabled, disable it
|
||||
# to prevent our input from being repeated to output.
|
||||
self.child.setecho(False)
|
||||
self.child.waitnoecho()
|
||||
|
||||
if prompt_change is None:
|
||||
self.prompt = orig_prompt
|
||||
else:
|
||||
self.set_prompt(orig_prompt,
|
||||
prompt_change.format(new_prompt, continuation_prompt))
|
||||
self.prompt = new_prompt
|
||||
self.continuation_prompt = continuation_prompt
|
||||
|
||||
self._expect_prompt()
|
||||
|
||||
if extra_init_cmd is not None:
|
||||
self.run_command(extra_init_cmd)
|
||||
|
||||
def set_prompt(self, orig_prompt, prompt_change):
|
||||
self.child.expect(orig_prompt)
|
||||
self.child.sendline(prompt_change)
|
||||
|
||||
def _expect_prompt(self, timeout=-1):
|
||||
return self.child.expect_exact([self.prompt, self.continuation_prompt],
|
||||
timeout=timeout)
|
||||
|
||||
def run_command(self, command, timeout=-1):
|
||||
"""Send a command to the REPL, wait for and return output.
|
||||
|
||||
:param str command: The command to send. Trailing newlines are not needed.
|
||||
This should be a complete block of input that will trigger execution;
|
||||
if a continuation prompt is found after sending input, :exc:`ValueError`
|
||||
will be raised.
|
||||
: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.
|
||||
"""
|
||||
# Split up multiline commands and feed them in bit-by-bit
|
||||
cmdlines = command.splitlines()
|
||||
# splitlines ignores trailing newlines - add it back in manually
|
||||
if command.endswith('\n'):
|
||||
cmdlines.append('')
|
||||
if not cmdlines:
|
||||
raise ValueError("No command was given")
|
||||
|
||||
res = []
|
||||
self.child.sendline(cmdlines[0])
|
||||
for line in cmdlines[1:]:
|
||||
self._expect_prompt(timeout=timeout)
|
||||
res.append(self.child.before)
|
||||
self.child.sendline(line)
|
||||
|
||||
# Command was fully submitted, now wait for the next prompt
|
||||
if self._expect_prompt(timeout=timeout) == 1:
|
||||
# We got the continuation prompt - command was incomplete
|
||||
self.child.kill(signal.SIGINT)
|
||||
self._expect_prompt(timeout=1)
|
||||
raise ValueError("Continuation prompt found - input was incomplete:\n"
|
||||
+ command)
|
||||
return u''.join(res + [self.child.before])
|
||||
|
||||
def python(command="python"):
|
||||
"""Start a Python shell and return a :class:`REPLWrapper` object."""
|
||||
return REPLWrapper(command, u">>> ", u"import sys; sys.ps1={0!r}; sys.ps2={1!r}")
|
||||
|
||||
def bash(command="bash"):
|
||||
"""Start a bash shell and return a :class:`REPLWrapper` object."""
|
||||
bashrc = os.path.join(os.path.dirname(__file__), 'bashrc.sh')
|
||||
child = pexpect.spawn(command, ['--rcfile', bashrc], echo=False,
|
||||
encoding='utf-8')
|
||||
|
||||
# If the user runs 'env', the value of PS1 will be in the output. To avoid
|
||||
# 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:]
|
||||
prompt_change = u"PS1='{0}' PS2='{1}' PROMPT_COMMAND=''".format(ps1, ps2)
|
||||
|
||||
return REPLWrapper(child, u'\\$', prompt_change,
|
||||
extra_init_cmd="export PAGER=cat")
|
|
@ -0,0 +1,157 @@
|
|||
import sys
|
||||
import types
|
||||
|
||||
from .exceptions import EOF, TIMEOUT
|
||||
from .pty_spawn import spawn
|
||||
|
||||
def run(command, timeout=30, withexitstatus=False, events=None,
|
||||
extra_args=None, logfile=None, cwd=None, env=None, **kwargs):
|
||||
|
||||
'''
|
||||
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
|
||||
UNIX-like systems because this is the standard for pseudottys. If you set
|
||||
'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 *
|
||||
child = spawn('scp foo user@example.com:.')
|
||||
child.expect('(?i)password')
|
||||
child.sendline(mypassword)
|
||||
|
||||
The previous code can be replace with the following::
|
||||
|
||||
from pexpect import *
|
||||
run('scp foo user@example.com:.', events={'(?i)password': mypassword})
|
||||
|
||||
**Examples**
|
||||
|
||||
Start the apache daemon on the local machine::
|
||||
|
||||
from pexpect import *
|
||||
run("/usr/local/apache/bin/apachectl start")
|
||||
|
||||
Check in a file using SVN::
|
||||
|
||||
from pexpect import *
|
||||
run("svn ci -m 'automatic commit' my_file.py")
|
||||
|
||||
Run a command and capture exit status::
|
||||
|
||||
from pexpect import *
|
||||
(command_output, exitstatus) = run('ls -l /bin', withexitstatus=1)
|
||||
|
||||
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::
|
||||
|
||||
run("ssh username@machine.example.com 'ls -l'",
|
||||
events={'(?i)password':'secret\\n'})
|
||||
|
||||
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'],
|
||||
run("mencoder dvd://1 -o video.avi -oac copy -ovc copy",
|
||||
events={TIMEOUT:print_ticks}, timeout=5)
|
||||
|
||||
The 'events' argument should be either a dictionary or a tuple list that
|
||||
contains patterns and responses. Whenever one of the patterns is seen
|
||||
in the command output, run() will send the associated response string.
|
||||
So, run() in the above example can be also written as:
|
||||
|
||||
run("mencoder dvd://1 -o video.avi -oac copy -ovc copy",
|
||||
events=[(TIMEOUT,print_ticks)], timeout=5)
|
||||
|
||||
Use a tuple list for events if the command output requires a delicate
|
||||
control over what pattern should be matched, since the tuple list is passed
|
||||
to pexpect() as its pattern list, with the order of patterns preserved.
|
||||
|
||||
Note that you should put newlines in your string if Enter is necessary.
|
||||
|
||||
Like the example above, the responses may also contain a callback, either
|
||||
a function or method. It should accept a dictionary value 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
|
||||
dictionary passed to a callback.
|
||||
|
||||
Like :class:`spawn`, passing *encoding* will make it work with unicode
|
||||
instead of bytes. You can pass *codec_errors* to control how errors in
|
||||
encoding and decoding are handled.
|
||||
'''
|
||||
if timeout == -1:
|
||||
child = spawn(command, maxread=2000, logfile=logfile, cwd=cwd, env=env,
|
||||
**kwargs)
|
||||
else:
|
||||
child = spawn(command, timeout=timeout, maxread=2000, logfile=logfile,
|
||||
cwd=cwd, env=env, **kwargs)
|
||||
if isinstance(events, list):
|
||||
patterns= [x for x,y in events]
|
||||
responses = [y for x,y in events]
|
||||
elif isinstance(events, dict):
|
||||
patterns = list(events.keys())
|
||||
responses = list(events.values())
|
||||
else:
|
||||
# This assumes EOF or TIMEOUT will eventually cause run to terminate.
|
||||
patterns = None
|
||||
responses = None
|
||||
child_result_list = []
|
||||
event_count = 0
|
||||
while True:
|
||||
try:
|
||||
index = child.expect(patterns)
|
||||
if isinstance(child.after, child.allowed_string_types):
|
||||
child_result_list.append(child.before + child.after)
|
||||
else:
|
||||
# child.after may have been a TIMEOUT or EOF,
|
||||
# which we don't want appended to the list.
|
||||
child_result_list.append(child.before)
|
||||
if isinstance(responses[index], child.allowed_string_types):
|
||||
child.send(responses[index])
|
||||
elif (isinstance(responses[index], types.FunctionType) or
|
||||
isinstance(responses[index], types.MethodType)):
|
||||
callback_result = responses[index](locals())
|
||||
sys.stdout.flush()
|
||||
if isinstance(callback_result, child.allowed_string_types):
|
||||
child.send(callback_result)
|
||||
elif callback_result:
|
||||
break
|
||||
else:
|
||||
raise TypeError("parameter `event' at index {index} must be "
|
||||
"a string, method, or function: {value!r}"
|
||||
.format(index=index, value=responses[index]))
|
||||
event_count = event_count + 1
|
||||
except TIMEOUT:
|
||||
child_result_list.append(child.before)
|
||||
break
|
||||
except EOF:
|
||||
child_result_list.append(child.before)
|
||||
break
|
||||
child_result = child.string_type().join(child_result_list)
|
||||
if withexitstatus:
|
||||
child.close()
|
||||
return (child_result, child.exitstatus)
|
||||
else:
|
||||
return child_result
|
||||
|
||||
def runu(command, timeout=30, withexitstatus=False, events=None,
|
||||
extra_args=None, logfile=None, cwd=None, env=None, **kwargs):
|
||||
"""Deprecated: pass encoding to run() instead.
|
||||
"""
|
||||
kwargs.setdefault('encoding', 'utf-8')
|
||||
return run(command, timeout=timeout, withexitstatus=withexitstatus,
|
||||
events=events, extra_args=extra_args, logfile=logfile, cwd=cwd,
|
||||
env=env, **kwargs)
|
|
@ -0,0 +1,431 @@
|
|||
'''This implements a virtual screen. This is used to support ANSI terminal
|
||||
emulation. The screen representation and state is implemented in this class.
|
||||
Most of the methods are inspired by ANSI screen control codes. The
|
||||
:class:`~pexpect.ANSI.ANSI` class extends this class to add parsing of ANSI
|
||||
escape codes.
|
||||
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
import codecs
|
||||
import copy
|
||||
import sys
|
||||
|
||||
import warnings
|
||||
|
||||
warnings.warn(("pexpect.screen and pexpect.ANSI are deprecated. "
|
||||
"We recommend using pyte to emulate a terminal screen: "
|
||||
"https://pypi.python.org/pypi/pyte"),
|
||||
stacklevel=2)
|
||||
|
||||
NUL = 0 # Fill character; ignored on input.
|
||||
ENQ = 5 # Transmit answerback message.
|
||||
BEL = 7 # Ring the bell.
|
||||
BS = 8 # Move cursor left.
|
||||
HT = 9 # Move cursor to next tab stop.
|
||||
LF = 10 # Line feed.
|
||||
VT = 11 # Same as LF.
|
||||
FF = 12 # Same as LF.
|
||||
CR = 13 # Move cursor to left margin or newline.
|
||||
SO = 14 # Invoke G1 character set.
|
||||
SI = 15 # Invoke G0 character set.
|
||||
XON = 17 # Resume transmission.
|
||||
XOFF = 19 # Halt transmission.
|
||||
CAN = 24 # Cancel escape sequence.
|
||||
SUB = 26 # Same as CAN.
|
||||
ESC = 27 # Introduce a control sequence.
|
||||
DEL = 127 # Fill character; ignored on input.
|
||||
SPACE = u' ' # Space or blank character.
|
||||
|
||||
PY3 = (sys.version_info[0] >= 3)
|
||||
if PY3:
|
||||
unicode = str
|
||||
|
||||
def constrain (n, min, max):
|
||||
|
||||
'''This returns a number, n constrained to the min and max bounds. '''
|
||||
|
||||
if n < min:
|
||||
return min
|
||||
if n > max:
|
||||
return max
|
||||
return n
|
||||
|
||||
class screen:
|
||||
'''This object maintains the state of a virtual text screen as a
|
||||
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).
|
||||
|
||||
Characters are represented internally using unicode. Methods that accept
|
||||
input characters, when passed 'bytes' (which in Python 2 is equivalent to
|
||||
'str'), convert them from the encoding specified in the 'encoding'
|
||||
parameter to the constructor. Methods that return screen contents return
|
||||
unicode strings, with the exception of __str__() under Python 2. Passing
|
||||
``encoding=None`` limits the API to only accept unicode input, so passing
|
||||
bytes in will raise :exc:`TypeError`.
|
||||
'''
|
||||
def __init__(self, r=24, c=80, encoding='latin-1', encoding_errors='replace'):
|
||||
'''This initializes a blank screen of the given dimensions.'''
|
||||
|
||||
self.rows = r
|
||||
self.cols = c
|
||||
self.encoding = encoding
|
||||
self.encoding_errors = encoding_errors
|
||||
if encoding is not None:
|
||||
self.decoder = codecs.getincrementaldecoder(encoding)(encoding_errors)
|
||||
else:
|
||||
self.decoder = None
|
||||
self.cur_r = 1
|
||||
self.cur_c = 1
|
||||
self.cur_saved_r = 1
|
||||
self.cur_saved_c = 1
|
||||
self.scroll_row_start = 1
|
||||
self.scroll_row_end = self.rows
|
||||
self.w = [ [SPACE] * self.cols for _ in range(self.rows)]
|
||||
|
||||
def _decode(self, s):
|
||||
'''This converts from the external coding system (as passed to
|
||||
the constructor) to the internal one (unicode). '''
|
||||
if self.decoder is not None:
|
||||
return self.decoder.decode(s)
|
||||
else:
|
||||
raise TypeError("This screen was constructed with encoding=None, "
|
||||
"so it does not handle bytes.")
|
||||
|
||||
def _unicode(self):
|
||||
'''This returns a printable representation of the screen as a unicode
|
||||
string (which, under Python 3.x, is the same as 'str'). The end of each
|
||||
screen line is terminated by a newline.'''
|
||||
|
||||
return u'\n'.join ([ u''.join(c) for c in self.w ])
|
||||
|
||||
if PY3:
|
||||
__str__ = _unicode
|
||||
else:
|
||||
__unicode__ = _unicode
|
||||
|
||||
def __str__(self):
|
||||
'''This returns a printable representation of the screen. The end of
|
||||
each screen line is terminated by a newline. '''
|
||||
encoding = self.encoding or 'ascii'
|
||||
return self._unicode().encode(encoding, 'replace')
|
||||
|
||||
def dump (self):
|
||||
'''This returns a copy of the screen as a unicode string. This is similar to
|
||||
__str__/__unicode__ except that lines are not terminated with line
|
||||
feeds.'''
|
||||
|
||||
return u''.join ([ u''.join(c) for c in self.w ])
|
||||
|
||||
def pretty (self):
|
||||
'''This returns a copy of the screen as a unicode string with an ASCII
|
||||
text box around the screen border. This is similar to
|
||||
__str__/__unicode__ except that it adds a box.'''
|
||||
|
||||
top_bot = u'+' + u'-'*self.cols + u'+\n'
|
||||
return top_bot + u'\n'.join([u'|'+line+u'|' for line in unicode(self).split(u'\n')]) + u'\n' + top_bot
|
||||
|
||||
def fill (self, ch=SPACE):
|
||||
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)
|
||||
|
||||
self.fill_region (1,1,self.rows,self.cols, ch)
|
||||
|
||||
def fill_region (self, rs,cs, re,ce, ch=SPACE):
|
||||
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)
|
||||
|
||||
rs = constrain (rs, 1, self.rows)
|
||||
re = constrain (re, 1, self.rows)
|
||||
cs = constrain (cs, 1, self.cols)
|
||||
ce = constrain (ce, 1, self.cols)
|
||||
if rs > re:
|
||||
rs, re = re, rs
|
||||
if cs > ce:
|
||||
cs, ce = ce, cs
|
||||
for r in range (rs, re+1):
|
||||
for c in range (cs, ce + 1):
|
||||
self.put_abs (r,c,ch)
|
||||
|
||||
def cr (self):
|
||||
'''This moves the cursor to the beginning (col 1) of the current row.
|
||||
'''
|
||||
|
||||
self.cursor_home (self.cur_r, 1)
|
||||
|
||||
def lf (self):
|
||||
'''This moves the cursor down with scrolling.
|
||||
'''
|
||||
|
||||
old_r = self.cur_r
|
||||
self.cursor_down()
|
||||
if old_r == self.cur_r:
|
||||
self.scroll_up ()
|
||||
self.erase_line()
|
||||
|
||||
def crlf (self):
|
||||
'''This advances the cursor with CRLF properties.
|
||||
The cursor will line wrap and the screen may scroll.
|
||||
'''
|
||||
|
||||
self.cr ()
|
||||
self.lf ()
|
||||
|
||||
def newline (self):
|
||||
'''This is an alias for crlf().
|
||||
'''
|
||||
|
||||
self.crlf()
|
||||
|
||||
def put_abs (self, r, c, ch):
|
||||
'''Screen array starts at 1 index.'''
|
||||
|
||||
r = constrain (r, 1, self.rows)
|
||||
c = constrain (c, 1, self.cols)
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)[0]
|
||||
else:
|
||||
ch = ch[0]
|
||||
self.w[r-1][c-1] = ch
|
||||
|
||||
def put (self, ch):
|
||||
'''This puts a characters at the current cursor position.
|
||||
'''
|
||||
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)
|
||||
|
||||
self.put_abs (self.cur_r, self.cur_c, ch)
|
||||
|
||||
def insert_abs (self, r, c, ch):
|
||||
'''This inserts a character at (r,c). Everything under
|
||||
and to the right is shifted right one character.
|
||||
The last character of the line is lost.
|
||||
'''
|
||||
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)
|
||||
|
||||
r = constrain (r, 1, self.rows)
|
||||
c = constrain (c, 1, self.cols)
|
||||
for ci in range (self.cols, c, -1):
|
||||
self.put_abs (r,ci, self.get_abs(r,ci-1))
|
||||
self.put_abs (r,c,ch)
|
||||
|
||||
def insert (self, ch):
|
||||
|
||||
if isinstance(ch, bytes):
|
||||
ch = self._decode(ch)
|
||||
|
||||
self.insert_abs (self.cur_r, self.cur_c, ch)
|
||||
|
||||
def get_abs (self, r, c):
|
||||
|
||||
r = constrain (r, 1, self.rows)
|
||||
c = constrain (c, 1, self.cols)
|
||||
return self.w[r-1][c-1]
|
||||
|
||||
def get (self):
|
||||
|
||||
self.get_abs (self.cur_r, self.cur_c)
|
||||
|
||||
def get_region (self, rs,cs, re,ce):
|
||||
'''This returns a list of lines representing the region.
|
||||
'''
|
||||
|
||||
rs = constrain (rs, 1, self.rows)
|
||||
re = constrain (re, 1, self.rows)
|
||||
cs = constrain (cs, 1, self.cols)
|
||||
ce = constrain (ce, 1, self.cols)
|
||||
if rs > re:
|
||||
rs, re = re, rs
|
||||
if cs > ce:
|
||||
cs, ce = ce, cs
|
||||
sc = []
|
||||
for r in range (rs, re+1):
|
||||
line = u''
|
||||
for c in range (cs, ce + 1):
|
||||
ch = self.get_abs (r,c)
|
||||
line = line + ch
|
||||
sc.append (line)
|
||||
return sc
|
||||
|
||||
def cursor_constrain (self):
|
||||
'''This keeps the cursor within the screen area.
|
||||
'''
|
||||
|
||||
self.cur_r = constrain (self.cur_r, 1, self.rows)
|
||||
self.cur_c = constrain (self.cur_c, 1, self.cols)
|
||||
|
||||
def cursor_home (self, r=1, c=1): # <ESC>[{ROW};{COLUMN}H
|
||||
|
||||
self.cur_r = r
|
||||
self.cur_c = c
|
||||
self.cursor_constrain ()
|
||||
|
||||
def cursor_back (self,count=1): # <ESC>[{COUNT}D (not confused with down)
|
||||
|
||||
self.cur_c = self.cur_c - count
|
||||
self.cursor_constrain ()
|
||||
|
||||
def cursor_down (self,count=1): # <ESC>[{COUNT}B (not confused with back)
|
||||
|
||||
self.cur_r = self.cur_r + count
|
||||
self.cursor_constrain ()
|
||||
|
||||
def cursor_forward (self,count=1): # <ESC>[{COUNT}C
|
||||
|
||||
self.cur_c = self.cur_c + count
|
||||
self.cursor_constrain ()
|
||||
|
||||
def cursor_up (self,count=1): # <ESC>[{COUNT}A
|
||||
|
||||
self.cur_r = self.cur_r - count
|
||||
self.cursor_constrain ()
|
||||
|
||||
def cursor_up_reverse (self): # <ESC> M (called RI -- Reverse Index)
|
||||
|
||||
old_r = self.cur_r
|
||||
self.cursor_up()
|
||||
if old_r == self.cur_r:
|
||||
self.scroll_up()
|
||||
|
||||
def cursor_force_position (self, r, c): # <ESC>[{ROW};{COLUMN}f
|
||||
'''Identical to Cursor Home.'''
|
||||
|
||||
self.cursor_home (r, c)
|
||||
|
||||
def cursor_save (self): # <ESC>[s
|
||||
'''Save current cursor position.'''
|
||||
|
||||
self.cursor_save_attrs()
|
||||
|
||||
def cursor_unsave (self): # <ESC>[u
|
||||
'''Restores cursor position after a Save Cursor.'''
|
||||
|
||||
self.cursor_restore_attrs()
|
||||
|
||||
def cursor_save_attrs (self): # <ESC>7
|
||||
'''Save current cursor position.'''
|
||||
|
||||
self.cur_saved_r = self.cur_r
|
||||
self.cur_saved_c = self.cur_c
|
||||
|
||||
def cursor_restore_attrs (self): # <ESC>8
|
||||
'''Restores cursor position after a Save Cursor.'''
|
||||
|
||||
self.cursor_home (self.cur_saved_r, self.cur_saved_c)
|
||||
|
||||
def scroll_constrain (self):
|
||||
'''This keeps the scroll region within the screen region.'''
|
||||
|
||||
if self.scroll_row_start <= 0:
|
||||
self.scroll_row_start = 1
|
||||
if self.scroll_row_end > self.rows:
|
||||
self.scroll_row_end = self.rows
|
||||
|
||||
def scroll_screen (self): # <ESC>[r
|
||||
'''Enable scrolling for entire display.'''
|
||||
|
||||
self.scroll_row_start = 1
|
||||
self.scroll_row_end = self.rows
|
||||
|
||||
def scroll_screen_rows (self, rs, re): # <ESC>[{start};{end}r
|
||||
'''Enable scrolling from row {start} to row {end}.'''
|
||||
|
||||
self.scroll_row_start = rs
|
||||
self.scroll_row_end = re
|
||||
self.scroll_constrain()
|
||||
|
||||
def scroll_down (self): # <ESC>D
|
||||
'''Scroll display down one line.'''
|
||||
|
||||
# Screen is indexed from 1, but arrays are indexed from 0.
|
||||
s = self.scroll_row_start - 1
|
||||
e = self.scroll_row_end - 1
|
||||
self.w[s+1:e+1] = copy.deepcopy(self.w[s:e])
|
||||
|
||||
def scroll_up (self): # <ESC>M
|
||||
'''Scroll display up one line.'''
|
||||
|
||||
# Screen is indexed from 1, but arrays are indexed from 0.
|
||||
s = self.scroll_row_start - 1
|
||||
e = self.scroll_row_end - 1
|
||||
self.w[s:e] = copy.deepcopy(self.w[s+1:e+1])
|
||||
|
||||
def erase_end_of_line (self): # <ESC>[0K -or- <ESC>[K
|
||||
'''Erases from the current cursor position to the end of the current
|
||||
line.'''
|
||||
|
||||
self.fill_region (self.cur_r, self.cur_c, self.cur_r, self.cols)
|
||||
|
||||
def erase_start_of_line (self): # <ESC>[1K
|
||||
'''Erases from the current cursor position to the start of the current
|
||||
line.'''
|
||||
|
||||
self.fill_region (self.cur_r, 1, self.cur_r, self.cur_c)
|
||||
|
||||
def erase_line (self): # <ESC>[2K
|
||||
'''Erases the entire current line.'''
|
||||
|
||||
self.fill_region (self.cur_r, 1, self.cur_r, self.cols)
|
||||
|
||||
def erase_down (self): # <ESC>[0J -or- <ESC>[J
|
||||
'''Erases the screen from the current line down to the bottom of the
|
||||
screen.'''
|
||||
|
||||
self.erase_end_of_line ()
|
||||
self.fill_region (self.cur_r + 1, 1, self.rows, self.cols)
|
||||
|
||||
def erase_up (self): # <ESC>[1J
|
||||
'''Erases the screen from the current line up to the top of the
|
||||
screen.'''
|
||||
|
||||
self.erase_start_of_line ()
|
||||
self.fill_region (self.cur_r-1, 1, 1, self.cols)
|
||||
|
||||
def erase_screen (self): # <ESC>[2J
|
||||
'''Erases the screen with the background color.'''
|
||||
|
||||
self.fill ()
|
||||
|
||||
def set_tab (self): # <ESC>H
|
||||
'''Sets a tab at the current position.'''
|
||||
|
||||
pass
|
||||
|
||||
def clear_tab (self): # <ESC>[g
|
||||
'''Clears tab at the current position.'''
|
||||
|
||||
pass
|
||||
|
||||
def clear_all_tabs (self): # <ESC>[3g
|
||||
'''Clears all tabs.'''
|
||||
|
||||
pass
|
||||
|
||||
# Insert line Esc [ Pn L
|
||||
# Delete line Esc [ Pn M
|
||||
# Delete character Esc [ Pn P
|
||||
# Scrolling region Esc [ Pn(top);Pn(bot) r
|
||||
|
|
@ -0,0 +1,522 @@
|
|||
from io import StringIO, BytesIO
|
||||
import codecs
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
import errno
|
||||
from .exceptions import ExceptionPexpect, EOF, TIMEOUT
|
||||
from .expect import Expecter, searcher_string, searcher_re
|
||||
|
||||
PY3 = (sys.version_info[0] >= 3)
|
||||
text_type = str if PY3 else unicode
|
||||
|
||||
class _NullCoder(object):
|
||||
"""Pass bytes through unchanged."""
|
||||
@staticmethod
|
||||
def encode(b, final=False):
|
||||
return b
|
||||
|
||||
@staticmethod
|
||||
def decode(b, final=False):
|
||||
return b
|
||||
|
||||
class SpawnBase(object):
|
||||
"""A base class providing the backwards-compatible spawn API for Pexpect.
|
||||
|
||||
This should not be instantiated directly: use :class:`pexpect.spawn` or
|
||||
:class:`pexpect.fdpexpect.fdspawn`.
|
||||
"""
|
||||
encoding = None
|
||||
pid = None
|
||||
flag_eof = False
|
||||
|
||||
def __init__(self, timeout=30, maxread=2000, searchwindowsize=None,
|
||||
logfile=None, encoding=None, codec_errors='strict'):
|
||||
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
|
||||
# status returned by os.waitpid
|
||||
self.status = None
|
||||
# the child file descriptor is initially closed
|
||||
self.child_fd = -1
|
||||
self.timeout = timeout
|
||||
self.delimiter = EOF
|
||||
self.logfile = logfile
|
||||
# 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
|
||||
# Data before searchwindowsize point is preserved, but not searched.
|
||||
self.searchwindowsize = searchwindowsize
|
||||
# Delay used before sending data to child. Time in seconds.
|
||||
# Set this to None to skip the time.sleep() call completely.
|
||||
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
|
||||
# Delay in seconds to sleep after each call to read_nonblocking().
|
||||
# Set this to None to skip the time.sleep() call completely: that
|
||||
# would restore the behavior from pexpect-2.0 (for performance
|
||||
# reasons or because you don't want to release Python's global
|
||||
# interpreter lock).
|
||||
self.delayafterread = 0.0001
|
||||
self.softspace = False
|
||||
self.name = '<' + repr(self) + '>'
|
||||
self.closed = True
|
||||
|
||||
# Unicode interface
|
||||
self.encoding = encoding
|
||||
self.codec_errors = codec_errors
|
||||
if encoding is None:
|
||||
# bytes mode (accepts some unicode for backwards compatibility)
|
||||
self._encoder = self._decoder = _NullCoder()
|
||||
self.string_type = bytes
|
||||
self.buffer_type = BytesIO
|
||||
self.crlf = b'\r\n'
|
||||
if PY3:
|
||||
self.allowed_string_types = (bytes, str)
|
||||
self.linesep = os.linesep.encode('ascii')
|
||||
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'))
|
||||
self.write_to_stdout = write_to_stdout
|
||||
else:
|
||||
self.allowed_string_types = (basestring,) # analysis:ignore
|
||||
self.linesep = os.linesep
|
||||
self.write_to_stdout = sys.stdout.write
|
||||
else:
|
||||
# unicode mode
|
||||
self._encoder = codecs.getincrementalencoder(encoding)(codec_errors)
|
||||
self._decoder = codecs.getincrementaldecoder(encoding)(codec_errors)
|
||||
self.string_type = text_type
|
||||
self.buffer_type = StringIO
|
||||
self.crlf = u'\r\n'
|
||||
self.allowed_string_types = (text_type, )
|
||||
if PY3:
|
||||
self.linesep = os.linesep
|
||||
else:
|
||||
self.linesep = os.linesep.decode('ascii')
|
||||
# This can handle unicode in both Python 2 and 3
|
||||
self.write_to_stdout = sys.stdout.write
|
||||
# storage for async transport
|
||||
self.async_pw_transport = None
|
||||
# This is the read buffer. See maxread.
|
||||
self._buffer = self.buffer_type()
|
||||
|
||||
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()
|
||||
|
||||
# For backwards compatibility, in bytes mode (when encoding is None)
|
||||
# unicode is accepted for send and expect. Unicode mode is strictly unicode
|
||||
# only.
|
||||
def _coerce_expect_string(self, s):
|
||||
if self.encoding is None and not isinstance(s, bytes):
|
||||
return s.encode('ascii')
|
||||
return s
|
||||
|
||||
def _coerce_send_string(self, s):
|
||||
if self.encoding is None and not isinstance(s, bytes):
|
||||
return s.encode('utf-8')
|
||||
return s
|
||||
|
||||
def _get_buffer(self):
|
||||
return self._buffer.getvalue()
|
||||
|
||||
def _set_buffer(self, value):
|
||||
self._buffer = self.buffer_type()
|
||||
self._buffer.write(value)
|
||||
|
||||
# This property is provided for backwards compatability (self.buffer used
|
||||
# to be a string/bytes object)
|
||||
buffer = property(_get_buffer, _set_buffer)
|
||||
|
||||
def read_nonblocking(self, size=1, timeout=None):
|
||||
"""This reads data from the file descriptor.
|
||||
|
||||
This is a simple implementation suitable for a regular file. Subclasses using ptys or pipes should override it.
|
||||
|
||||
The timeout parameter is ignored.
|
||||
"""
|
||||
|
||||
try:
|
||||
s = os.read(self.child_fd, size)
|
||||
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
|
||||
if s == b'':
|
||||
# BSD-style EOF
|
||||
self.flag_eof = True
|
||||
raise EOF('End Of File (EOF). Empty string style platform.')
|
||||
|
||||
s = self._decoder.decode(s, final=False)
|
||||
self._log(s, 'read')
|
||||
return s
|
||||
|
||||
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])
|
||||
)
|
||||
)
|
||||
|
||||
def compile_pattern_list(self, patterns):
|
||||
'''This compiles a pattern-string or a list of pattern-strings.
|
||||
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(cpl, timeout)
|
||||
...
|
||||
'''
|
||||
|
||||
if patterns is None:
|
||||
return []
|
||||
if not isinstance(patterns, list):
|
||||
patterns = [patterns]
|
||||
|
||||
# Allow dot to match \n
|
||||
compile_flags = re.DOTALL
|
||||
if self.ignorecase:
|
||||
compile_flags = compile_flags | re.IGNORECASE
|
||||
compiled_pattern_list = []
|
||||
for idx, p in enumerate(patterns):
|
||||
if isinstance(p, self.allowed_string_types):
|
||||
p = self._coerce_expect_string(p)
|
||||
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)
|
||||
elif isinstance(p, type(re.compile(''))):
|
||||
compiled_pattern_list.append(p)
|
||||
else:
|
||||
self._pattern_type_err(p)
|
||||
return compiled_pattern_list
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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::
|
||||
|
||||
# the input is 'foobar'
|
||||
index = p.expect(['bar', 'foo', 'foobar'])
|
||||
# returns 1('foo') even though 'foobar' is a "better" match
|
||||
|
||||
Please note, however, that buffering can affect this behavior, since
|
||||
input arrives in unpredictable chunks. For example::
|
||||
|
||||
# the input is 'foobar'
|
||||
index = p.expect(['foobar', 'foo'])
|
||||
# returns 0('foobar') if all input is available at once,
|
||||
# but returns 1('foo') if parts of the final 'bar' arrive late
|
||||
|
||||
When a match is found for the given pattern, the class instance
|
||||
attribute *match* becomes an re.MatchObject result. Should an EOF
|
||||
or TIMEOUT pattern match, then the match attribute will be an instance
|
||||
of that exception class. The pairing before and after class
|
||||
instance attributes are views of the data preceding and following
|
||||
the matching pattern. On general exception, class attribute
|
||||
*before* is all data received up to the exception, while *match* and
|
||||
*after* attributes are value None.
|
||||
|
||||
When the keyword argument timeout is -1 (default), then TIMEOUT will
|
||||
raise after the default value specified by the class timeout
|
||||
attribute. When None, TIMEOUT will not be raised and may block
|
||||
indefinitely until match.
|
||||
|
||||
When the keyword argument searchwindowsize is -1 (default), then the
|
||||
value specified by the class maxread attribute is used.
|
||||
|
||||
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::
|
||||
|
||||
index = p.expect(['good', 'bad', pexpect.EOF, pexpect.TIMEOUT])
|
||||
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:
|
||||
index = p.expect(['good', 'bad'])
|
||||
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')
|
||||
p.expect(pexpect.EOF)
|
||||
print p.before
|
||||
|
||||
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,
|
||||
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::
|
||||
|
||||
index = p.expect(patterns)
|
||||
|
||||
With this non-blocking form::
|
||||
|
||||
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_)
|
||||
|
||||
def expect_list(self, pattern_list, timeout=-1, searchwindowsize=-1,
|
||||
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
|
||||
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().
|
||||
|
||||
|
||||
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
|
||||
return expect_async(exp, timeout)
|
||||
else:
|
||||
return exp.expect_loop(timeout)
|
||||
|
||||
def expect_exact(self, pattern_list, timeout=-1, searchwindowsize=-1,
|
||||
async_=False, **kw):
|
||||
|
||||
'''This is similar to expect(), but uses plain string matching instead
|
||||
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
|
||||
escaping regular expression characters that you want to match.
|
||||
|
||||
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)):
|
||||
pattern_list = [pattern_list]
|
||||
|
||||
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)
|
||||
|
||||
try:
|
||||
pattern_list = iter(pattern_list)
|
||||
except TypeError:
|
||||
self._pattern_type_err(pattern_list)
|
||||
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
|
||||
return expect_async(exp, timeout)
|
||||
else:
|
||||
return exp.expect_loop(timeout)
|
||||
|
||||
def expect_loop(self, searcher, timeout=-1, searchwindowsize=-1):
|
||||
'''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. '''
|
||||
|
||||
exp = Expecter(self, searcher, searchwindowsize)
|
||||
return exp.expect_loop(timeout)
|
||||
|
||||
def read(self, size=-1):
|
||||
'''This reads at most "size" bytes from the file (less if the read hits
|
||||
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
|
||||
immediately. '''
|
||||
|
||||
if size == 0:
|
||||
return self.string_type()
|
||||
if size < 0:
|
||||
# delimiter default is EOF
|
||||
self.expect(self.delimiter)
|
||||
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 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
|
||||
# will never match anything in which case we stop only on EOF.
|
||||
cre = re.compile(self._coerce_expect_string('.{%d}' % size), re.DOTALL)
|
||||
# delimiter default is EOF
|
||||
index = self.expect([cre, self.delimiter])
|
||||
if index == 0:
|
||||
### FIXME self.before should be ''. Should I assert this?
|
||||
return self.after
|
||||
return self.before
|
||||
|
||||
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. '''
|
||||
|
||||
if size == 0:
|
||||
return self.string_type()
|
||||
# delimiter default is EOF
|
||||
index = self.expect([self.crlf, self.delimiter])
|
||||
if index == 0:
|
||||
return self.before + self.crlf
|
||||
else:
|
||||
return self.before
|
||||
|
||||
def __iter__(self):
|
||||
'''This is to support iterators over a file-like object.
|
||||
'''
|
||||
return iter(self.readline, self.string_type())
|
||||
|
||||
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.'''
|
||||
|
||||
lines = []
|
||||
while True:
|
||||
line = self.readline()
|
||||
if not line:
|
||||
break
|
||||
lines.append(line)
|
||||
return lines
|
||||
|
||||
def fileno(self):
|
||||
'''Expose file descriptor for a file-like interface
|
||||
'''
|
||||
return self.child_fd
|
||||
|
||||
def flush(self):
|
||||
'''This does nothing. It is here to support the interface for a
|
||||
File-like object. '''
|
||||
pass
|
||||
|
||||
def isatty(self):
|
||||
"""Overridden in subclass using tty"""
|
||||
return False
|
||||
|
||||
# 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.
|
||||
self.close()
|
|
@ -0,0 +1,187 @@
|
|||
import os
|
||||
import sys
|
||||
import stat
|
||||
import select
|
||||
import time
|
||||
import errno
|
||||
|
||||
try:
|
||||
InterruptedError
|
||||
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.
|
||||
|
||||
This is roughly ``os.path isfile(path) and os.access(path, os.X_OK)``.
|
||||
"""
|
||||
# follow symlinks,
|
||||
fpath = os.path.realpath(path)
|
||||
|
||||
if not os.path.isfile(fpath):
|
||||
# non-files (directories, fifo, etc.)
|
||||
return False
|
||||
|
||||
mode = os.stat(fpath).st_mode
|
||||
|
||||
if (sys.platform.startswith('sunos')
|
||||
and os.getuid() == 0):
|
||||
# When root on Solaris, os.X_OK is True for *all* files, irregardless
|
||||
# of their executability -- instead, any permission bit of any user,
|
||||
# group, or other is fine enough.
|
||||
#
|
||||
# (This may be true for other "Unix98" OS's such as HP-UX and AIX)
|
||||
return bool(mode & (stat.S_IXUSR |
|
||||
stat.S_IXGRP |
|
||||
stat.S_IXOTH))
|
||||
|
||||
return os.access(fpath, os.X_OK)
|
||||
|
||||
|
||||
def which(filename, env=None):
|
||||
'''This takes a given filename; tries to find it in the environment path;
|
||||
then checks if it is executable. This returns the full path to the filename
|
||||
if found and executable. Otherwise this returns None.'''
|
||||
|
||||
# Special case where filename contains an explicit path.
|
||||
if os.path.dirname(filename) != '' and is_executable_file(filename):
|
||||
return filename
|
||||
if env is None:
|
||||
env = os.environ
|
||||
p = env.get('PATH')
|
||||
if not p:
|
||||
p = os.defpath
|
||||
pathlist = p.split(os.pathsep)
|
||||
for path in pathlist:
|
||||
ff = os.path.join(path, filename)
|
||||
if is_executable_file(ff):
|
||||
return ff
|
||||
return None
|
||||
|
||||
|
||||
def split_command_line(command_line):
|
||||
|
||||
'''This splits a command line into a list of arguments. It splits arguments
|
||||
on spaces, but handles embedded quotes, doublequotes, and escaped
|
||||
characters. It's impossible to do this with a regular expression, so I
|
||||
wrote a little state machine to parse the command line. '''
|
||||
|
||||
arg_list = []
|
||||
arg = ''
|
||||
|
||||
# Constants to name the states we can be in.
|
||||
state_basic = 0
|
||||
state_esc = 1
|
||||
state_singlequote = 2
|
||||
state_doublequote = 3
|
||||
# The state when consuming whitespace between commands.
|
||||
state_whitespace = 4
|
||||
state = state_basic
|
||||
|
||||
for c in command_line:
|
||||
if state == state_basic or state == state_whitespace:
|
||||
if c == '\\':
|
||||
# Escape the next character
|
||||
state = state_esc
|
||||
elif c == r"'":
|
||||
# Handle single quote
|
||||
state = state_singlequote
|
||||
elif c == r'"':
|
||||
# Handle double quote
|
||||
state = state_doublequote
|
||||
elif c.isspace():
|
||||
# Add arg to arg_list if we aren't in the middle of whitespace.
|
||||
if state == state_whitespace:
|
||||
# Do nothing.
|
||||
None
|
||||
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
|
||||
|
||||
|
||||
def select_ignore_interrupts(iwtd, owtd, ewtd, timeout=None):
|
||||
|
||||
'''This is a wrapper around select.select() that ignores signals. If
|
||||
select.select raises a select.error exception and errno is an EINTR
|
||||
error then it is ignored. Mainly this is used to ignore sigwinch
|
||||
(terminal resize). '''
|
||||
|
||||
# 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:
|
||||
return select.select(iwtd, owtd, ewtd, timeout)
|
||||
except InterruptedError:
|
||||
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.
|
||||
if timeout is not None:
|
||||
timeout = end_time - time.time()
|
||||
if timeout < 0:
|
||||
return([], [], [])
|
||||
else:
|
||||
# something else caused the select.error, so
|
||||
# this actually is an exception.
|
||||
raise
|
||||
|
||||
|
||||
def poll_ignore_interrupts(fds, timeout=None):
|
||||
'''Simple wrapper around poll to register file descriptors and
|
||||
ignore signals.'''
|
||||
|
||||
if timeout is not None:
|
||||
end_time = time.time() + timeout
|
||||
|
||||
poller = select.poll()
|
||||
for fd in fds:
|
||||
poller.register(fd, select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR)
|
||||
|
||||
while True:
|
||||
try:
|
||||
timeout_ms = None if timeout is None else timeout * 1000
|
||||
results = poller.poll(timeout_ms)
|
||||
return [afd for afd, _ in results]
|
||||
except InterruptedError:
|
||||
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.
|
||||
if timeout is not None:
|
||||
timeout = end_time - time.time()
|
||||
if timeout < 0:
|
||||
return []
|
||||
else:
|
||||
# something else caused the select.error, so
|
||||
# this actually is an exception.
|
||||
raise
|
|
@ -0,0 +1,5 @@
|
|||
pytest
|
||||
pytest-cov
|
||||
coverage
|
||||
coveralls
|
||||
pytest-capturelog
|
|
@ -0,0 +1,5 @@
|
|||
[tool:pytest]
|
||||
norecursedirs = .git
|
||||
|
||||
[bdist_wheel]
|
||||
universal=1
|
|
@ -0,0 +1,71 @@
|
|||
# encoding: utf-8
|
||||
from distutils.core import setup
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
if any(a == 'bdist_wheel' for a in sys.argv):
|
||||
from setuptools import setup
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), 'pexpect', '__init__.py'), 'r') as f:
|
||||
for line in f:
|
||||
version_match = re.search(r"__version__ = ['\"]([^'\"]*)['\"]", line)
|
||||
if version_match:
|
||||
version = version_match.group(1)
|
||||
break
|
||||
else:
|
||||
raise Exception("couldn't find version number")
|
||||
|
||||
long_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.
|
||||
"""
|
||||
|
||||
setup(name='pexpect',
|
||||
version=version,
|
||||
packages=['pexpect'],
|
||||
package_data={'pexpect': ['bashrc.sh']},
|
||||
description='Pexpect allows easy control of interactive console applications.',
|
||||
long_description=long_description,
|
||||
author='Noah Spurrier; Thomas Kluyver; Jeff Quast',
|
||||
author_email='noah@noah.org, thomas@kluyver.me.uk, contact@jeffquast.com',
|
||||
url='https://pexpect.readthedocs.io/',
|
||||
license='ISC license',
|
||||
platforms='UNIX',
|
||||
classifiers = [
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Console',
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: System Administrators',
|
||||
'License :: OSI Approved :: ISC License (ISCL)',
|
||||
'Operating System :: POSIX',
|
||||
'Operating System :: MacOS :: MacOS X',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Topic :: Software Development',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
'Topic :: Software Development :: Quality Assurance',
|
||||
'Topic :: Software Development :: Testing',
|
||||
'Topic :: System',
|
||||
'Topic :: System :: Archiving :: Packaging',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
'Topic :: System :: Shells',
|
||||
'Topic :: System :: Software Distribution',
|
||||
'Topic :: Terminals',
|
||||
],
|
||||
install_requires=['ptyprocess>=0.5'],
|
||||
)
|
|
@ -0,0 +1,108 @@
|
|||
|
||||
'''
|
||||
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.
|
||||
|
||||
'''
|
||||
from __future__ import print_function
|
||||
|
||||
import contextlib
|
||||
import unittest
|
||||
import signal
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
class PexpectTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.PYTHONBIN = sys.executable
|
||||
self.original_path = os.getcwd()
|
||||
tests_dir = os.path.dirname(__file__)
|
||||
self.project_dir = project_dir = os.path.dirname(tests_dir)
|
||||
|
||||
# all tests are executed in this folder; there are many auxiliary
|
||||
# programs in this folder executed by spawn().
|
||||
os.chdir(tests_dir)
|
||||
|
||||
# If the pexpect raises an exception after fork(), but before
|
||||
# exec(), our test runner *also* forks. We prevent this by
|
||||
# storing our pid and asserting equality on tearDown.
|
||||
self.pid = os.getpid()
|
||||
|
||||
coverage_rc = os.path.join(project_dir, '.coveragerc')
|
||||
os.environ['COVERAGE_PROCESS_START'] = coverage_rc
|
||||
os.environ['COVERAGE_FILE'] = os.path.join(project_dir, '.coverage')
|
||||
print('\n', self.id(), end=' ')
|
||||
sys.stdout.flush()
|
||||
|
||||
# some build agents will ignore SIGHUP and SIGINT, which python
|
||||
# inherits. This causes some of the tests related to terminate()
|
||||
# to fail. We set them to the default handlers that they should
|
||||
# be, and restore them back to their SIG_IGN value on tearDown.
|
||||
#
|
||||
# I'm not entirely convinced they need to be restored, only our
|
||||
# test runner is affected.
|
||||
self.restore_ignored_signals = [
|
||||
value for value in (signal.SIGHUP, signal.SIGINT,)
|
||||
if signal.getsignal(value) == signal.SIG_IGN]
|
||||
if signal.SIGHUP in self.restore_ignored_signals:
|
||||
# sighup should be set to default handler
|
||||
signal.signal(signal.SIGHUP, signal.SIG_DFL)
|
||||
if signal.SIGINT in self.restore_ignored_signals:
|
||||
# SIGINT should be set to signal.default_int_handler
|
||||
signal.signal(signal.SIGINT, signal.default_int_handler)
|
||||
unittest.TestCase.setUp(self)
|
||||
|
||||
def tearDown(self):
|
||||
# restore original working folder
|
||||
os.chdir(self.original_path)
|
||||
|
||||
if self.pid != os.getpid():
|
||||
# The build server pattern-matches phrase 'Test runner has forked!'
|
||||
print("Test runner has forked! This means a child process raised "
|
||||
"an exception before exec() in a test case, the error is "
|
||||
"more than likely found above this line in stderr.",
|
||||
file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
# restore signal handlers
|
||||
for signal_value in self.restore_ignored_signals:
|
||||
signal.signal(signal_value, signal.SIG_IGN)
|
||||
|
||||
if sys.version_info < (2, 7):
|
||||
# We want to use these methods, which are new/improved in 2.7, but
|
||||
# we are still supporting 2.6 for the moment. This section can be
|
||||
# removed when we drop Python 2.6 support.
|
||||
@contextlib.contextmanager
|
||||
def assertRaises(self, excClass):
|
||||
try:
|
||||
yield
|
||||
except Exception as e:
|
||||
assert isinstance(e, excClass)
|
||||
else:
|
||||
raise AssertionError("%s was not raised" % excClass)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def assertRaisesRegexp(self, excClass, pattern):
|
||||
import re
|
||||
try:
|
||||
yield
|
||||
except Exception as e:
|
||||
assert isinstance(e, excClass)
|
||||
assert re.match(pattern, str(e))
|
||||
else:
|
||||
raise AssertionError("%s was not raised" % excClass)
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
The best way to run these tests is from the directory above this one. Run:
|
||||
|
||||
py.test
|
||||
|
||||
To run a specific test file:
|
||||
|
||||
py.test tests/test_constructor.py
|
|
@ -0,0 +1,8 @@
|
|||
This is test data.
|
||||
One
|
||||
2
|
||||
THREE
|
||||
IV
|
||||
.....
|
||||
110
|
||||
This is the end of test data: END
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
'''
|
||||
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.
|
||||
|
||||
'''
|
||||
|
||||
# __init__.py
|
||||
# The mere presence of this file makes the dir a package.
|
||||
pass
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue