18.1 paramiko
paramiko模块是基于Python实现的SSH远程安全连接,用于SSH远程执行命令、文件传输等功能。
默认Python没有,需要手动安装:pip install paramiko
如安装失败,可以尝试yum安装:yum install python-paramiko
18.1.1 SSH密码认证远程执行命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
import
paramiko
import
sys
hostname
=
'192.168.1.215'
port
=
22
username
=
'root'
password
=
'123456'
client
=
paramiko.SSHClient()
# 绑定实例
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(hostname, port, username, password, timeout
=
5
)
stdin, stdout, stderr
=
client.exec_command(
'df -h'
)
# 执行bash命令
result
=
stdout.read()
error
=
stderr.read()
# 判断stderr输出是否为空,为空则打印执行结果,不为空打印报错信息
if
not
error:
print
result
else
:
print
error
client.close()
|
18.1.2 私钥认证远程执行命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
import
paramiko
import
sys
hostname
=
'192.168.1.215'
port
=
22
username
=
'root'
key_file
=
'/root/.ssh/id_rsa'
cmd
=
" "
.join(sys.argv[
1
:])
def
ssh_conn(command):
client
=
paramiko.SSHClient()
key
=
paramiko.RSAKey.from_private_key_file(key_file)
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(hostname, port, username, pkey
=
key)
stdin, stdout, stderr
=
client.exec_command(command)
# 标准输入,标准输出,错误输出
result
=
stdout.read()
error
=
stderr.read()
if
not
error:
print
result
else
:
print
error
client.close()
if
__name__
=
=
"__main__"
:
ssh_conn(cmd)
|
18.1.3 上传文件到远程服务器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
import
os, sys
import
paramiko
hostname
=
'192.168.1.215'
port
=
22
username
=
'root'
password
=
'123456'
local_path
=
'/root/test.txt'
remote_path
=
'/opt/test.txt'
if
not
os.path.isfile(local_path):
print
local_path
+
" file not exist!"
sys.exit(
1
)
try
:
s
=
paramiko.Transport((hostname, port))
s.connect(username
=
username, password
=
password)
except
Exception as e:
print
e
sys.exit(
1
)
sftp
=
paramiko.SFTPClient.from_transport(s)
# 使用put()方法把本地文件上传到远程服务器
sftp.put(local_path, remote_path)
# 简单测试是否上传成功
try
:
# 如果远程主机有这个文件则返回一个对象,否则抛出异常
sftp.
file
(remote_path)
print
"上传成功."
except
IOError:
print
"上传失败!"
finally
:
s.close()
|
18.1.4 从远程服务器下载文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
import
os, sys
import
paramiko
hostname
=
'192.168.1.215'
port
=
22
username
=
'root'
password
=
'123456'
local_path
=
'/root/test.txt'
remote_path
=
'/opt/test.txt'
try
:
s
=
paramiko.Transport((hostname, port))
s.connect(username
=
username, password
=
password)
sftp
=
paramiko.SFTPClient.from_transport(s)
except
Exception as e:
print
e
sys.exit(
1
)
try
:
# 判断远程服务器是否有这个文件
sftp.
file
(remote_path)
# 使用get()方法从远程服务器拉去文件
sftp.get(remote_path, local_path)
except
IOError as e:
print
remote_path
+
"remote file not exist!"
sys.exit(
1
)
finally
:
s.close()
# 测试是否下载成功
if
os.path.isfile(local_path):
print
"下载成功."
else
:
print
"下载失败!"
|
18.1.5 上传目录到远程服务器
paramiko模块并没有实现直接上传目录的类,已经知道了如何上传文件,再写一个上传目录的代码就简单了,利用os库的os.walk()方法遍历目录,再一个个上传:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
import
os, sys
import
paramiko
hostname
=
'192.168.1.215'
port
=
22
username
=
'root'
password
=
'123456'
local_path
=
'/root/abc'
remote_path
=
'/opt/abc'
# 去除路径后面正斜杠
if
local_path[
-
1
]
=
=
'/'
:
local_path
=
local_path[
0
:
-
1
]
if
remote_path[
-
1
]
=
=
'/'
:
remote_path
=
remote_path[
0
:
-
1
]
file_list
=
[]
if
os.path.isdir(local_path):
for
root, dirs, files
in
os.walk(local_path):
for
file
in
files:
# 获取文件绝对路径
file_path
=
os.path.join(root,
file
)
file_list.append(file_path)
else
:
print
path
+
"Directory not exist!"
sys.exit(
1
)
try
:
s
=
paramiko.Transport((hostname, port))
s.connect(username
=
username, password
=
password)
sftp
=
paramiko.SFTPClient.from_transport(s)
except
Exception as e:
print
e
for
local_file
in
file_list:
# 替换目标目录
remote_file
=
local_file.replace(local_path, remote_path)
remote_dir
=
os.path.dirname(remote_file)
# 如果远程服务器没目标目录则创建
try
:
sftp.stat(remote_dir)
except
IOError:
sftp.mkdir(remote_dir)
print
"%s -> %s"
%
(local_file, remote_file)
sftp.put(local_file, remote_file)
s.close()
|
sftp是安全文件传输协议,提供一种安全的加密方法,sftp是SSH的一部分,SFTPClient类实现了sftp客户端,通过已建立的SSH通道传输文件,与其他的操作,如下:
sftp.getcwd() | 返回当前工作目录 |
sftp.chdir(path) | 改变工作目录 |
sftp.chmod(path, mode) | 修改权限 |
sftp.chown(path, uid, gid) | 设置属主属组 |
sftp.close() | 关闭sftp |
sftp.file(filename, mode='r', bufsize=-1) | 读取文件 |
sftp.from_transport(s) | 创建SFTP客户端通道 |
sftp.listdir(path='.') | 列出目录,返回一个列表 |
sftp.listdir_attr(path='.') | 列出目录,返回一个SFTPAttributes列表 |
sftp.mkdir(path, mode=511) | 创建目录 |
sftp.normalize(path) | 返回规范化path |
sftp.open(filename, mode='r', bufsize=-1) | 在远程服务器打开文件 |
sftp.put(localpath, remotepath, callback=None) | localpath文件上传到远程服务器remotepath |
sftp.get(remotepath, localpath, callback=None) | 从远程服务器remotepath拉文件到本地localpath |
sftp.readlink(path) | 返回一个符号链接目标 |
sftp.remove(path) | 删除文件 |
sftp.rename(oldpath, newpath) | 重命名文件或目录 |
sftp.rmdir(path) | 删除目录 |
sftp.stat(path) | 返回远程服务器文件信息(返回一个对象的属性) |
sftp.truncate(path, size) | 截取文件大小 |
sftp.symlink(source, dest) | 创建一个软链接(快捷方式) |
sftp.unlink(path) | 删除软链接 |
博客地址:http://lizhenliang.blog.51cto.com
QQ群:323779636(Shell/Python运维开发群)
18.2 fabric
fabric模块是在paramiko基础上又做了一层封装,操作起来更方便。主要用于多台主机批量执行任务。
默认Python没有,需要手动安装:pip install fabric
如安装失败,可以尝试yum安装:yum install fabric
Fabric常用API:
API类 |
描述 |
示例 |
local | 执行本地命令 | local('uname -s') |
lcd | 切换本地目录 | lcd('/opt') |
run | 执行远程命令 | run('uname -s') |
cd | 切换远程目录 | cd('/opt') |
sudo | sudo方式执行远程命令 | sudo('/etc/init.d/httpd start') |
put | 上传本地文件或目录到远程主机 | put(remote_path, local_path) |
get | 从远程主机下载文件或目录到本地 | put(local_path, remote_path) |
open_shell | 打开一个shell,类似于SSH连接到了远程主机 | open_shell("ifconfig eth0") |
prompt | 获得用户输入信息 | prompt('Please input user password: ') |
confirm | 获得提示信息确认 | confirm('Continue[Y/N]?') |
reboot | 重启远程主机 | reboot() |
@task | 函数装饰器,引用说明函数可调用,否则不可见 | |
@runs_once | 函数装饰器,函数只会执行一次 |
当我们写好fabric脚本后,需要用fab命令调用执行任务。
命令格式:fab [options] <command>[:arg1,arg2=val2,host=foo,hosts='h1;h2',...] ...
fab命令有以下常用选项:
选项 |
描述 |
-l | 打印可用的命令(函数) |
--set=KEY=VALUE,... | 逗号分隔,设置环境变量 |
--shortlist | 简短打印可用命令 |
-c PATH | 指定本地配置文件 |
-D | 不加载用户known_hosts文件 |
-f PATH | 指定fabfile文件 |
-g HOST | 逗号分隔要操作的主机 |
-i PATH | 指定私钥文件 |
-k | 不加载来自~/.ssh下的私钥文件 |
-p PASSWORD | 使用密码认证and/or sudo |
-P | 默认为并行执行方法 |
--port=PORT | 指定SSH连接端口 |
-R ROLES | 根据角色操作,逗号分隔 |
-s SHELL | 指定新shell,默认是'/bin/bash -l -c' |
--show=LEVELS | 以逗号分隔的输出 |
--ssh-config-path=PATH | SSH配置文件路径 |
-t N | 设置连接超时时间,单位秒 |
-T N | 设置远程命令超时时间,单位秒 |
-u USER | 连接远程主机用户名 |
-x HOSTS | 以逗号分隔排除主机 |
-z INT | 并发进程数 |
18.2.1 本地执行命令
1
2
3
4
5
6
7
|
from
fabric.api
import
local
def
command():
local(
'ls'
)
# fab command
[localhost] local: ls
fabfile.py fabfile.pyc tab.py tab.pyc
Done.
|
使用fab命令调用,默认寻找当前目录的fabfile.py文件。
18.2.2 远程执行命令
1
2
3
4
5
6
7
8
9
10
11
12
|
from
fabric.api
import
run
def
command():
run(
'ls'
)
# fab -H 192.168.1.120 -u user command
[
192.168
.
1.120
] Executing task
'command'
[
192.168
.
1.120
] run: ls
[
192.168
.
1.120
] Login password
for
'user'
:
[
192.168
.
1.120
] out: access.log a.py
[
192.168
.
1.120
] out:
Done.
Disconnecting
from
192.168
.
1.120
... done.
|
如果在多台主机执行,只需要-H后面的IP以逗号分隔即可。
18.2.3 给脚本函数传入位置参数
1
2
3
4
5
6
7
8
9
10
11
|
from
fabric.api
import
run
def
hello(name
=
"world"
):
print
(
"Hello %s!"
%
name)
# fab -H localhost hello
[localhost] Executing task
'hello'
Hello world!
Done.
# fab -H localhost hello:name=Python
[localhost] Executing task
'hello'
Hello Python!
Done.
|
18.2.4 主机列表组
1
2
3
4
5
6
|
from
fabric.api
import
run, env
env.hosts
=
[
'root@192.168.1.120:22'
,
'root@192.168.1.130:22'
]
env.password
=
'123.com'
env.exclude_hosts
=
[
'root@192.168.1.120:22'
]
# 排除主机
def
command():
run(
'ls'
)
|
env作用是定义fabfile全局设定,类似于变量。还有一些常用的属性:
env属性 |
描述 |
示例 |
env.hosts | 定义目标主机 | env.hosts = ['192.168.1.120:22'] |
env.exclude_hosts | 排除指定主机 | env.exclude_hosts = '[192.168.1.1]' |
env.user | 定义用户名 | env.user='root' |
env.port | 定义端口 | env.port='22' |
env.password | 定义密码 | env.password='123' |
env.passwords | 定义多个密码,不同主机对应不同密码 | env.passwords = {'root@192.168.1.120:22': '123'} |
env.gateway | 定义网关 | env.gateway='192.168.1.2' |
env.roledefs | 定义角色分组 | env.roledef = {'web':['192.168.1.11'], 'db':['192.168.1.12']} |
env.deploy_release_dir | 自定义全局变量,格式:env.+ '变量名' | env.var |
18.2.5 定义角色分组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# vi install.py
from
fabric.api
import
run, env
env.roledefs
=
{
'web'
: [
'192.168.1.10'
,
'192.168.1.20'
],
'db'
: [
'192.168.1.30'
,
'192.168.1.40'
]
}
env.password
=
'123'
@roles
(
'web'
)
def
task1():
run(
'yum install httpd -y'
)
@roles
(
'db'
)
def
task2():
run(
'yum install mysql-server -y'
)
def
deploy():
execute(task1)
execute(task2)
# fab -f install.py deploy
|
18.2.6 上传目录到远程主机
1
2
3
4
5
6
7
8
|
from
fabric.api
import
*
env.hosts
=
[
'192.168.1.120'
]
env.user
=
'user'
env.password
=
'123.com'
def
task():
put(
'/root/abc'
,
'/home/user'
)
run(
'ls -l /home/user'
)
# fab task
|
18.2.7 从远程主机下载目录
1
2
3
4
5
6
7
8
|
from
fabric.api
import
*
env.hosts
=
[
'192.168.1.120'
]
env.user
=
'user'
env.password
=
'123.com'
def
task():
get(
'/home/user/b'
,
'/opt'
)
local(
'ls -l /opt'
)
# fab task
|
18.2.8 打印颜色,有助于关键地方醒目
1
2
3
4
5
6
|
from
fabric.colors
import
*
def
show():
print
green(
'Successful.'
)
print
red(
'Failure!'
)
print
yellow(
'Warning.'
)
# fab show
|
经过上面演示fabric主要相关功能,是不是觉得很适合批量自动部署呢!没错,通过编写简单的脚本,即可完成复杂的操作。
博客地址:http://lizhenliang.blog.51cto.com
QQ群:323779636(Shell/Python运维开发群)
18.3 pexpect
pexpect是一个用来启动子程序,并使用正则表达式对程序输出做出特定响应,以此实现与其自动交互的Python模块。暂不支持Windows下的Python环境执行。
这里主要讲解run()函数和spawn()类,能完成自动交互,下面简单了解下它们使用。
18.3.1 run()
run()函数用来运行bash命令,类似于os模块中的system()函数。
参数:run(command, timeout=-1, withexitstatus=False, events=None, extra_args=None, logfile=None, cwd=None, env=None)
1
2
3
4
5
|
例
1
:执行ls命令
>>>
import
pexpect
>>> pexpect.run(
"ls"
)
例
2
:获得命令状态返回值
>>> command_output, exitstatus
=
pexpect.run(
"ls"
, withexitstatus
=
1
)
|
command_outout是执行结果,exitstatus是退出状态值。
18.3.2 spawn()
spawn()是pexpect模块主要的类,实现启动子程序,使用pty.fork()生成子进程,并调用exec()系列函数执行命令。
参数:spawn(command, args=[], timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None)
spawn()类几个常用函数:
expect(pattern, timeout=-1, searchwindowsize=None) | 匹配正则表达式,pattern可以是正则表达式。 |
send(s) | 给子进程发送一个字符串 |
sendline(s='') | 就像send(),但添加了一个换行符(os.lineseq) |
sendcontrol(char) | 发送一个控制符,比如ctrl-c、ctrl-d |
例子:ftp交互
用ftp命令登录是这样的,需要手动输入用户名和密码,才能登录进去。
1
2
3
4
5
6
7
8
9
10
11
|
# ftp 192.168.1.10
Connected to
192.168
.
1.10
(
192.168
.
1.10
).
220
-
FileZilla Server version
0.9
.
46
beta
220
-
written by Tim Kosse (tim.kosse@filezilla
-
project.org)
220
Please visit http:
/
/
sourceforge.net
/
projects
/
filezilla
/
Name (
192.168
.
1.10
:root): yunwei
331
Password required
for
yunwei
Password:
230
Logged on
Remote system
type
is
UNIX.
ftp>
|
下面我们用pexpect帮我们完成输入用户名和密码:
1
2
3
4
5
6
7
8
9
10
11
|
import
pexpect
child
=
pexpect.spawn(
'ftp 192.168.1.10'
)
child.expect(
'Name .*: '
)
child.sendline(
'yunwei'
)
child.expect(
'Password:'
)
child.sendline(
'yunweipass'
)
child.expect(
'ftp> '
)
child.sendline(
'ls'
)
child.sendline(
'bye'
)
child.expect(pexpect.EOF)
# pexpect.EOF程序打印提示信息
print
child.before
# 保存命令执行结果
|
手动输入时,是来自键盘的标准输入,而pexpect是先匹配到关键字,再向子进程发送字符串。
pexpect.EOF打印提示信息,child.before保存的是命令执行结果。
通过上面的例子想必你已经知道pexpect主要功能了,在交互场景下很有用,这里就讲解这么多了,目的是给大家提供一个自动交互实现思路。
小结:
通过对Python下paramiko、fabric和pexpect模块使用,它们各有自己擅长的一面。
paramiko:方便嵌套系统平台中,擅长远程执行命令,文件传输。
fabric:方便与shell脚本结合,擅长批量部署,任务管理。
pexpect:擅长自动交互,比如ssh、ftp、telnet。