说明:本文档中的示例代码都以Python3.7.3编写。
A. pexpect模块
pexpect模块是一个比较灵活的与命令行进行交互的模块。
pexpect 是 Don Libes 的 Expect 语言的一个 Python 实现,是一个用来启动子程序,并使用正则表达式对程序输出做出特定响应,以此实现与其自动交互的 Python 模块。 Pexpect 的使用范围很广,可以用来实现与 ssh、ftp 、telnet 等程序的自动交互;可以用来自动复制软件安装包并在不同机器自动安装;还可以用来实现软件测试中与命令行交互的自动化。
Pexpect4.0以上的版本可以在Windows平台和POSIX平台上使用了,但pexpect.spawn和pexpect.run()只能在POSIX平台上用,windows平台可以用pexpect.popen_spawn.PopenSpawn,操作文件时可用pexpect.fdpexpect.fdspawn。
child = pexpect.spawn() #启动一个子进程;
child.send() #发送命令
child.sendline() #发送命令,且有一个回车符
child.sendcontrol() #发送控制符
child.expect() #从缓冲区匹配指定正则表达式的内容。
expect()方法中的正则表达式,使用的时候还是要注意:
- 不能用”$”匹配行尾(因为pexpect每次都只读取一个字符,每个字符看起来都是一行的最后一个字符),可以用“\r\n”匹配行尾;
- 在正则表达式的末尾,尽量少使用模糊匹配“+”“*”,建议使用精确匹配。因为pexpect中是最少匹配,而不是贪婪匹配。比如child.expect ('.+')只会匹配到一个字符,而child.expect ('.*')一个字符都匹配不到。
- 特殊模式EOF和pexpect.TIMEOUT。
当没有匹配到任何内容时,pexpect会抛出pexpect.EOF的异常,可以把pexpect.EOF当成一个正则表达式传入expect(),这样就不会抛出异常,EOF之前所有的输出都会记录在before成员中;pexpect.TIMEOUT同理;pexpect的默认超时时间是30s,可以在expect方法中重新设置超时时间timeout=120.
child.before #正则表达式匹配成功后前面的内容
child.after #正则表达式匹配到的内容
child.interact() #控制权交给用户
pexpect.run() #直接运行shell命令,类似于os.system()功能,与 os.system() 不同的是,使用 run() 可以方便地同时获得命令的输出结果与命令的退出状态。
代码示例:设置ssh免密
def ssh_authentication(server_ip, user, passwd):
"""进行ssh的免密码认证"""
if not os.path.exists(os.path.expanduser("~/.ssh/id_rsa")) or not os.path.exists(
os.path.expanduser("~/.ssh/id_rsa.pub")):
if os.system("ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa"):
return False
#检查是否已经免密
try:
#在本机进行登录
cmd="ssh {}@{}".format(user,server_ip)
child = pexpect.spawn(cmd)
index = child.expect(["(?i)Last login:", "(?i)yes/no", "(?i)password",pexpect.EOF, pexpect.TIMEOUT])
if index == 0:
logger.info("ssh key is installed:{}".format(server_ip))
child.close()
return True
elif index == 1:
child.sendline("yes")
index1 = child.expect(["(?i)Last login:", pexpect.EOF, pexpect.TIMEOUT])
if index1 == 0:
logger.info("ssh key is installed:{}".format(server_ip))
child.close()
return True
elif index ==2:
logger.error("ssh key is not installed:{}".format(server_ip))
else:
output = child.before.decode()
logger.error("error occured:{}".format(output))
#设置免密
logger.info("set ssh key for:{}".format(server_ip))
cmd = "ssh-copy-id {}@{}".format(user, server_ip)
child = pexpect.spawn(cmd)
child.timeout=120
index = child.expect(["(?i)password:", "(?i)yes/no", pexpect.EOF, pexpect.TIMEOUT])
if index == 0:
child.sendline(passwd)
child.expect(pexpect.EOF)
stdout = str(child.before)
logger.info(stdout)
if "you wanted were added." in stdout:
logger.info("ssh key is installed:{}".format(server_ip))
child.close()
return True
elif index == 1:
child.sendline("yes")
index1 = child.expect(["(?i)password:", pexpect.EOF, pexpect.TIMEOUT])
if index1 == 0:
child.sendline(passwd)
child.expect(pexpect.EOF)
stdout = str(child.before)
logger.info(stdout)
if "you wanted were added." in stdout:
logger.info("ssh key is installed:{}".format(server_ip))
child.close()
return True
logger.error("ssh key install failed:{} {}".format(server_ip,child.before.decode()))
child.close()
except Exception as e:
logger.error("{}:{}".format(server_ip, e))
logger.info("ssh-copy-id failed:{}".format(server_ip))
return False
B. pty.spawn()
pty是一个伪终端处理模块:启动另外一个进程并以程序的方式从其控制终端中进行读写。pty.spawn(argv[, master_read[, stdin_read]])方法生成一个进程,并将其控制终端连接到当前进程的标准io,master_read,stdin_read是两个函数,pty.spawn()调用时向这两个函数传入一个文件描述符,这两个函数会返回一个字符串,默认字符串长度是1024bytes, master_read的回调函数从子进程中读取输出,stdin_read从父进程的标准输入中读取数据,实现人机交互。
示例代码:打开bash,记录输入输出到文件中
该代码中,pty.spawn(shell,read)运行一个子进程,子进程中打开/bin/bash窗口,在该创建中的输入和屏幕输出,都会记录到temp.txt中,在窗口中输入exit,则退出子进程。
import pty,os,time
shell = os.environ.get('SHELL', 'sh')
filename = "./temp.txt"
mode = 'wb'
with open(filename, mode) as script:
def read(fd):
data = os.read(fd, 1024)
script.write(data)
return data
print('Script started, file is', filename)
script.write(('Script started on %s\n' % time.asctime()).encode())
pty.spawn(shell,read)
script.write(('Script done on %s\n' % time.asctime()).encode())
print('Script done, file is', filename)
C. subprocess
subprocess 模块允许我们启动一个新进程,并连接到它们的输入/输出/错误管道,从而获取返回值。pty.spawn()也是生成子进程,但子进程的输入输出会连接到父进程的输入输出,subprocess的实现与此是不同的,subprocess提供了更丰富的子进程管理方法。
subprocess.Popen类是该模块的核心,对进程进行创建和管理。unix平台下用os.execvp()执行子程序,windows下调用CreateProcess()方法生成子进程。
subprocess.Popen的构造函数如下:
class subprocess.Popen(args, bufsize=0, executable=None, stdin=None, stdout=None, stderr=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0)
| 参数 | 描述 |
| args | 要执行的shell命令,可以是字符串或者列表,如果是字符串的话,是平台相关的,不同的平台处理方法不同,当shell=True时,建议将args参数作为字符串传递; |
| bufsize | 指定缓存策略,0表示不缓冲,1表示行缓冲,其他大于1的数字表示缓冲区大小,负数 表示使用系统默认缓冲策略。 |
| stdin, stdout, stderr | 分别表示程序标准输入、输出、错误句柄。 |
| preexec_fn | 用于指定一个将在子进程运行之前被调用的可执行对象,只在Unix平台下有效。 |
| close_fds | 如果该参数的值为True,则除了0,1和2之外的所有文件描述符都将会在子进程执行之前被关闭。 |
| shell | 该参数用于标识是否使用shell作为要执行的程序,如果shell值为True,则建议将args参数作为一个字符串传递而不要作为一个序列传递。 |
| cwd | 如果该参数值不是None,则该函数将会在执行这个子进程之前改变当前工作目录。 |
| env | 用于指定子进程的环境变量,如果env=None,那么子进程的环境变量将从父进程中继承。如果env!=None,它的值必须是一个映射对象。 |
| universal_newlines | 如果该参数值为True,则该文件对象的stdin,stdout和stderr将会作为文本流被打开,否则他们将会被作为二进制流被打开。 |
| startupinfo和creationflags | 这两个参数只在Windows下有效,它们将被传递给底层的CreateProcess()函数,用于设置子进程的一些属性,如主窗口的外观,进程优先级等。 |
Popen抛出的异常:OSError、ValueError、CalledProcessError。
Popen实例的一些函数:
| 方法 | 描述 |
| Popen.poll() | 用于检查子进程(命令)是否已经执行结束,没结束返回None,结束后返回状态码。 |
| Popen.wait(timeout=None) | 等待子进程结束,并返回状态码;如果在timeout指定的秒数之后进程还没有结束,将会抛出一个TimeoutExpired异常。 |
| Popen.communicate(input=None) | 该方法可用来与进程进行交互,比如发送数据到stdin,从stdout和stderr读取数据,直到到达文件末尾。返回(stdoutdata, stderrdata)。如果要发送数据到子进程的stdin,并要子进程返回结果到stdoutdata、stderrdata中,在用Popen创建子进程时需要设置参数stdin=PIPE,stdout=PIPE及stderr=PIPE。如果返回的数据量过大,不建议使用该方法。 |
| Popen.send_signal(signal) | 发送指定的信号给这个子进程。 |
| Popen.terminate() | 停止该子进程。 |
| Popen.kill() | 杀死该子进程。 |
subprocess中的常用方法都是基于subprocess.Popen类实现的。
subprocess的常用方法:
| 方法 | 描述 |
| subprocess.getstatusoutput(cmd) | #执行string类型的shell命令,返回执行状态和执行结果两个值:status,output; subprocess.getoutput(cmd) #执行string类型的shell命令,返回执行结果stdout or stderr; |
| subprocess.run() | #执行列表类型的shell命令(shell命令以列表形式提供,如’ls -l’ 要写成[‘ls’,’-l’]),返回CompletedProcess实例,如果传入参数同时传入shell=True,则传入一个字符串args,shell命令而不是待执行的shell命令序列。 |
| subprocess.call() | 执行列表类型的shell命令(shell命令以列表形式提供,如’ls -l’ 要写成[‘ls’,’-l’]),返回returncode ,如果传入参数同时传入shell=True,则传入一个字符串args,shell命令而不是待执行的shell命令序列。 这个函数不能在参数中使用stdout=PIPE或stderr=PIPE,这样会导致子进程死锁,如果要用pipe的的话可以用Popen的communicate()。 |
| subprocess.check_call() | #参数同subprocess.call(),如果执行结果是0就返回,否则抛异常 CalledProcessError。 |
| subprocess.check_output() | #参数形式是列表,shell=True时可以是字符串, 执行shell命令,返回bytes类型的output,执行状态不为0时,抛异常 CalledProcessError; subprocess.PIPE #特殊常量,在Popen的stdin, stdout or stderr 参数中使用,表示打开标准输入输出流管道。 |
| subprocess.STDOUT | Sepcial value,在Popen的stderr 参数中使用,表示将标准错误输出到标准输出中; |
| subprocess.CalledProcessError | subproecess的异常 |
| subprocess.STARTUPINFO类 | 只在windows平台上使用的Popen |
示例代码:执行shell命令,获取磁盘wwid
import subprocess
def execute_cmd(args,timeout=60):
shell = True
if isinstance(args,list):
shell=False
try:
proc = subprocess.Popen(args, shell=shell, close_fds=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = proc.communicate(input=None)
if proc.returncode == None:
raise Exception("Timeout %s" % timeout)
return proc.returncode, stdout.decode(), stderr.decode()
except Exception as e:
if proc:
proc.kill()
proc.wait()
raise
cmd="lsscsi"
code,stdout,stderr = execute_cmd(cmd)
if code!=0:
print(code,stdout,stderr)
else:
stdout=stdout.split("\n")
disks=[item.split()[-1] for item in stdout if item and "disk" in item]
print(disks)
for disk in disks:
cmd=["/usr/lib/udev/scsi_id","-g", "-u", disk]
code, stdout, stderr = execute_cmd(cmd)
if code==0:
print("{} wwid is :{}".format(disk,stdout.strip("\n")))
else:
print("get {} wwid failed :{}".format(disk, stderr))
D. paramiko
paramiko是基于Python实现的SSH2远程安全连接,支持认证及密钥方式。可以实现远程命令执行、文件传输、中间SSH代理等功能,相对于Pexpect,封装的层次更高,更贴近SSH协议的功能。
fabric和ansible内部的远程管理就是使用的paramiko来现实的。
paramiko包含两个核心组件,一个为SSHClient类,另一个为SFTPClient类。
SSHClient类封装了Transport, Channel, 和SFTPClient三个类,可以完成建立连接,鉴权,用于在远程服务器上执行shell命令。
示例代码一:基于用户名密码连接到远程服务器并执行shell
import paramiko
# 创建SSH对象
ssh = paramiko.SSHClient()
# 允许连接不在know_hosts文件中的主机
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接服务器
ssh.connect(hostname='20.20.6.138', port=22, username='root', password='sandstone')
# 执行命令
stdin, stdout, stderr = ssh.exec_command('df')
# 获取命令结果
rc = stdout.channel.recv_exit_status()
print(rc)
print(stdout.read().decode())
print(stderr.read().decode())
# 关闭连接
ssh.close()
示例代码二: 基于公钥私钥连接到远程服务器并执行shell
(先要用ssh-key-gen创建私钥和公钥,再用ssh-copy-id将公钥设置到远程服务器上)
import paramiko
private_key = paramiko.RSAKey.from_private_key_file('/root/.ssh/id_rsa')
# 创建SSH对象
ssh = paramiko.SSHClient()
# 允许连接不在know_hosts文件中的主机
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接服务器
ssh.connect(hostname='20.20.6.130', port=22, username='root', pkey=private_key)
# 执行命令
stdin, stdout, stderr = ssh.exec_command('ls -l')
# 获取命令结果
rc = stdout.channel.recv_exit_status()
print(rc)
print(stdout.read().decode())
print(stderr.read().decode())
# 关闭连接
ssh.close()
E. pxssh
pxssh 类是pexpect的派生类,用来建立ssh连接。pxssh不如paramiko功能强大,建议使用paramiko进行ssh连接。
该模块已提供了login()/logout()函数用于登陆登出ssh,sendline()等函数用于发送命令。
示例代码:登陆远程主机,执行shell命令
from pexpect import pxssh
#创建子进程
connect = pxssh.pxssh()
#登录
connect.login("20.20.6.130","root","sandstone")
#发送命令
connect.sendline("df")
#匹配下一个shell提示符
connect.prompt()
#获取结果
print(connect.before.decode())
#登出
connect.logout()
F. RedExpect
RedExpect模块也是提供ssh远程执行shell的模块,类似pexpect,模拟人机交互,通过关键字匹配输出。
使用这个模块的人不多,文档链接比较难打开,想学习的可以安装模块来练习。
2202

被折叠的 条评论
为什么被折叠?



