Expect是一个用来处理交互的命令。借助Expect,我们可以将交互过程写在一个脚本上,使之自动化完成。形象的说,ssh登录,
ftp登录等都符合交互的定义。
Expect中最关键的四个命令是send,expect,spawn,interact。
send:用于向进程发送字符串
expect:从进程接收字符串
spawn:启动新的进程
interact:允许用户交互
================================================================
2.7版本以上的Python:
pip install pyexpect
装好pyexpect之后:
In [1]: import pexpect
In [2]: output,exitstatus = pexpect.run('ls -l',withexitstatus=1)
In [3]: print output,exitstatus
解释:
以上的output和exitstatus这两个变量名字是自己定义的
'ls -l'的执行结果赋值给前面的变量output
withexitstatus=1是开启返回状态,然后把这个返回状态赋值给前面的变量exitstatus
================================================================
接下来更深入的学习一下spawn,它能帮我们解决更加复杂的交互问题
先概念性的记住以下方法:
1、产生子进程执行相关命令pexpect.spawn()返回一个子进程实例:
child = pexpect.spawn(‘command’)
2、该实例产生的日志信息可以通过child.logfile进行配置:(可选项)
f = file(’/tmp/plog.out’, ‘w’)
child.logfile = f
3、使用expect进行交互
index = child.expect([“命令执行成功输出的内容”,”命令执行失败输出的内容”pexpect.EOF,pexpect.TIMEOUT])
pexpect.EOF 子进程结束
pexpect.TIMEOUT 超时,默认30s
然后通过if判断index的值,当index为0代表出现了“命令执行成功输出的内容”,当命令为1时代表”命令执行失败输出的内容”
4、通过sendline给子进程发送信息,自动带一个回车:
child.sendline(‘command’)
#甚至可以通过sendcontrol(‘c’)来发送ctrl+c
5、使用try,except来进行异常处理
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()
6、可以使用before来打印命令执行结果:
p = pexpect.spawn('/bin/ls')
p.expect(pexpect.EOF)
print p.before
以上6点我觉得可以让你游刃有余的解决日常90%进程交互的问题。
这里share一个经验就是写程序之前,先把这6个问题思考好,定义好,再去写程序的主题就ok了!
================================================================
例1:
现在有一台ftp服务器,地址为192.168.100.194,我要连上去,并且下载一个名为passwd的文件,然后退出。这个操作可以手动操作,
但如果工作量大了,我们就可以写个脚本来操作:
#!/usr/bin/env python
import pexpect
import sys
child = pexpect.spawn ('ftp 192.168.100.194')
child.expect ('Name .*: ')
child.sendline (sys.argv[1]) #这里是传的的一个参数,即下文的hello
child.expect ('Password:')
child.sendline (sys.argv[2]) #这里是第二个参数,即下文的123
child.expect ('ftp> ')
child.sendline ('get passwd') #下载passwd这个文件
child.expect('ftp> ')
child.sendline ('bye') #退出
执行结果:
[root@localhost test]# python a.py hello 123
[root@localhost test]# ls
a.py passwd
例1修改版:
import pexpect
username = 'ptest'
key = '123456'
f = file('/tmp/plog.out', 'w')
if __name__ == "__main__":
child = pexpect.spawn('ftp localhost') #产生新的子进程对象
child.logfile = f #指定日志文件为上面的变量f
try:
index = child.expect(['Name']) #期待命令执行成功并输出Name
if index == 0: #如果是列表里的第0个,即Name,则往下走
child.sendline(username) #通过sendline给子进程发送信息,自带回车
index = child.expect(['Password']) #期待命令执行成功并输出Password
if index == 0:
child.sendline(key) #如果命中上面的Pwassword,则发送key
index = child.expect(['Login successful.*ftp>'])
if index == 0:
child.sendline('ls')
index = child.expect(['test.mp3'])
if index == 0:
child.sendline('bin') #改为二进制,防止下面get的时候乱码
child.sendline('get test.mp3') #下载test.mp3文件
index = child.expect(['Transfer complete.*ftp>'])
if index == 0:
print 'download complete!'
child.sendline('bye') #退出
except:
print child.before
print 'see /tmp/plog.out can find more info!'
执行一下这段代码,然后去查看一下/tmp/plog.out文件的内容
这段代码虽然非常丑陋,但还是能起到练习的作用,稍加变化就能满足您生产需求!
================================================================
例2:线上实例,可拿去用。生成公钥并拷贝到指定主机,支持多台。
#!/usr/bin/env python
#coding=utf8
import pexpect
#指定本地公钥所在的家目录
home = '/root'
#指定远程主机的用户、IP和密码,多台用逗号隔开
info = {'[email protected]':'123456'
}
f = file('/tmp/plog.out', 'w')
def genkey():
child = pexpect.spawn("ssh-keygen -t rsa")
child.logfile = f
try:
index = child.expect(['save the key'])
if index == 0:
child.sendline()
index = child.expect(['Enter passphrase', 'Overwrite'])
if index == 0:
child.sendline()
index = child.expect(['Enter same passphrase'])
if index == 0:
child.sendline()
else:
child.sendline('n')
except:
print child.before
def copykey():
try:
for k,v in info.items():
child = pexpect.spawn("ssh-copy-id -i %s/.ssh/id_rsa.pub %s"%(home,k))
child.logfile = f
index = child.expect(['continue connecting','password'])
if index == 0:
child.sendline('yes')
else:
child.sendline(v)
child.expect(pexpect.EOF)
except:
print child.before
if __name__ == "__main__":
genkey()
copykey()
================================================================
例3:这里是ssh登录的过程,有超时,ssh_newkey,让你输入password三种情况:
#!/usr/bin/env python
#encoding=utf8
import pexpect
import getpass, os
#user: ssh 主机的用户名
#host:ssh 主机的域名
#password:ssh 主机的密码
#command:即将在远端 ssh 主机上运行的命令
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'
# 为 ssh 命令生成一个 spawn 类的子程序对象.
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
# 如果 ssh 没有 public key,接受它.
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 ():
# 获得用户指定 ssh 主机域名.
host = raw_input('Hostname: ')
# 获得用户指定 ssh 主机用户名.
user = raw_input('User: ')
# 获得用户指定 ssh 主机密码.
password = getpass.getpass()
# 获得用户指定 ssh 主机上即将运行的命令.
command = raw_input('Enter the command: ')
child = ssh_command (user, host, password, command)
# 匹配 pexpect.EOF
child.expect(pexpect.EOF)
# 输出命令结果.
print child.before
if __name__ == '__main__':
try:
main()
except Exception, e:
print str(e)
os._exit(1)
1.密钥
用shell下的except,这个不好用,ssh可以
#!/usr/bin/env python
# encoding: UTF-8
"""
ssh-copy-id -i /root/.ssh/id_rsa.pub root@localhost
"""
import pexpect
keyfile='/root/.ssh/id_rsa.pub'
target_user='root'
target_host='localhost'
command = 'ssh-copy-id -i ' + keyfile + ' ' + target_user + '@' + target_host
password = '123456'
child = pexpect.spawn(command)
try:
index = child.expect(['yes/no', 'password'])
if index == 0:
child.sendline('yes')
index = child.expect(['password'])
if index == 0:
child.sendline(password)
else:
child.sendline(password)
except pexpect.EOF:
print 'now to try U sshkey'
2、
#!/usr/bin/env python
#coding:utf-8
import pexpect,sys
child = pexpect.spawn("ssh [email protected]")
# pexpect.spawn(command, args=[], timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None,
env=None, ignore_sighup=True)
# 其中command参数可以是任意已知的系统命令 参数timeout为等待结果的超时时间;参数maxread为pex-pect从终端控制台一次读取的最大字节数,
searchwindowsize参数为匹配缓冲区字符串的位置,默认是从开始位置匹配。
#需要注意的是,pexpect不会解析shell命令当中的元字符,包括重定向“>”、管道“|”或通配符“*”,当然,我们可以通过一个技巧来解决这个问题,
将存在这三个特殊元字符的命令作为/bin/bash的参数进行调用,
#例如:child = pexpect.spawn('/bin/bash -c "ls -l | grep LOG > logs.txt"')
# child.expect(pexpect.EOF)
#我们可以通过将命令的参数以Python列表的形式进行替换,从而使我们的语法变成更加清晰,下面的代码等价于上面的。
# shell_cmd = 'ls -l | grep LOG > logs.txt'
# child = pexpect.spawn('/bin/bash', ['-c', shell_cmd])
# child.expect(pexpect.EOF)
fout = file("mylog.txt",'w')
#child.logfile = fout #输出到文件
child.logfile = sys.stdout #标准输出,当前终端
passwd = "1"
comm = "free -m"
try:
index = child.expect(["yes/no","password:"]) #匹配,返回索引值
if index == 0:
child.sendline("yes")
child.expect("password:")
child.sendline(passwd)
child.expect('#')
child.sendline(comm)
child.expect('#')
child.sendline("exit")
elif index == 1:
child.sendline(passwd)
child.expect('#')
child.sendline(comm)
child.expect('#')
child.sendline("exit")
else:
print "Error"
except Exception,e:
print str(e)