最近在跟一个自动化发布平台的建设事项,其中 Linux 系统的远程控制通道则由我独立开发完成,其中涉及到了 Linux 系统远程命令和文件传输操作。
因为之前写 Linux 系统密码管理系统的时候,用的是 Paramiko 的 SSHClient。所以,我这次依然采用 Paramiko 来做实现,代码虽短,说起其中的坑,我也是一把辛酸一把泪的填上了。
先上完整代码:
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
|
# -*- coding: utf-8 -*-
import os
import socket
import paramiko
import pysftp
'''
Name: remoteCtrl
Author: Jager @ zhangge.net
Description: remote command and file transfer API Base on paramiko and pysftp
Date: 2017-3-9 16:25:24
'''
class remoteCtrl(object):
# Description : remote command.
def command(self, ip, passwd, cmd, port=22, user='root', timeout=60):
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
client.connect(hostname=ip, port=int(port), username=user, password=passwd, timeout=timeout,allow_agent=False,look_for_keys=False)
# 连接超时
except socket.timeout as e:
return 502, e
# 密码错误
except paramiko.ssh_exception.AuthenticationException:
print "Password [%s] error" % passwd
client.close()
return 403, "Password [%s] error" % passwd
# 其他错误
except Exception as e:
print e
# 系统重装后会出现hostkey验证失败问题,需要先删除known_hosts中记录,用法:若返回503则重新下发即可
if "Host key for server" in str(e):
os.system('sed -i "/^\[%s].*/d" ~/.ssh/known_hosts' % ip)
client.close()
return 503,e
else:
client.close()
return 1,e
# 执行命令之前设置为utf8环境,其中 1>&2 尤为重要,可以解决paramiko远程执行后台脚本僵死的问题
stdin,stdout,stderr=client.exec_command("export LANG=en_US.UTF-8;export LC_ALL=en_US.UTF-8;%s 1>&2" % cmd)
result_info = ""
for line in stderr.readlines(): #因为有了 1>&2,所以读的是stderr
result_info += line
# 返回状态码和打屏信息
return stderr.channel.recv_exit_status(), result_info
# Description : paramiko & pysftp & sftp transfer.
def transfer(self,ip, passwd, src, dst, action='push', user = 'root' , port = 36000, timeout=60):
# 忽略hostkeys错误
cnopts = pysftp.CnOpts()
cnopts.hostkeys = None
# 若src以斜杠结尾,则去掉这个斜杠,是否是目录后面会有判断逻辑
if src[-1] == '/':
src = src[0:-1]
try:
with pysftp.Connection(ip, username=user, password=passwd, port=int(port), cnopts=cnopts) as sftp:
# 拉取文件或目录
if action == 'pull':
try:
# 判断远程来源是目录还文件
if sftp.isdir(src):
# 判断本地目录是否存在,若不存在则创建
if not os.path.exists(dst):
try:
os.makedirs(dst)
except Exception as e:
print e
pass
# 若为目录则分别取得父目录和需要操作的目录路径,进入父目录,然后执行sftp
parent_dir = src.rsplit('/',1)[0]
opt_dir = src.rsplit('/',1)[1]
sftp.chdir(parent_dir)
sftp.get_r(opt_dir,dst, preserve_mtime=True)
else:
# 拉取src远程文件到dst本地文件夹
if dst[-1] == '/':
# 判断本地目录是否存在,若不存在则创建
if not os.path.exists(dst):
try:
os.makedirs(dst)
except Exception as e:
print e
pass
os.chdir(dst)
sftp.get(src, preserve_mtime=True)
# 拉取src远程文件到dst本地文件
else:
file_dir = dst.rsplit('/',1)[0]
dst_file = dst.rsplit('/',1)[1] # 取得目标文件名称
# 判断本地目录是否存在,若不存在则创建
if not os.path.exists(file_dir):
try:
os.makedirs(file_dir)
except Exception as e:
print e
pass
os.chdir(file_dir)
sftp.get(src,dst_file, preserve_mtime=True)
except Exception as e:
return 1,e
else:
try:
# 判断本地文件是目录还是文件,若是目录则使用put_r 递归推送
if os.path.isdir(src):
# 判断目的目录是否存在,若不存在则创建
if not sftp.exists(dst):
try:
sftp.makedirs(dst)
except Exception as e:
print e
pass
sftp.put_r(src,dst,preserve_mtime=True)
# 否则先进入目标目录,然后使用put单文件推送
else:
# 推送src源文件到dst目的文件夹
if dst[-1] == '/':
# 判断目的目录是否存在,若不存在则创建
if not sftp.exists(dst):
try:
sftp.makedirs(dst)
except Exception as e:
print e
pass
sftp.chdir(dst)
sftp.put(src,preserve_mtime=True)
# 推送src源文件到dst目的文件
else:
file_dir = dst.rsplit('/',1)[0]
# 判断目的目录是否存在,若不存在则创建
if not sftp.exists(file_dir):
try:
sftp.makedirs(file_dir)
except Exception as e:
print e
pass
sftp.chdir(file_dir)
sftp.put(src,dst,preserve_mtime=True)
except Exception as e:
return 1,e
return 0, 'success'
except socket.timeout as e:
return 502,e
except paramiko.ssh_exception.AuthenticationException:
print "Password [%s] error" % passwd
client.close()
return 403,"Password [%s] error" % passwd
except Exception as e:
print e
# 系统重装后会出现hostkey验证失败问题,需要先删除known_hosts中记录
if "Host key for server" in str(e):
os.system('sed -i "/^\[%s].*/d" ~/.ssh/known_hosts' % ip)
client.close()
return 503,'Hostkeys Error'
else:
client.close()
return 1, e
|
简单说下用法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
# 先在Python脚本中载入,需要提前安装paramiko和pysftp插件(推荐pip命令安装)
from xxxx import remoteCtrl
# 执行远程命令,需要传入远程服务器ip地址、密码、命令、远程ssh端口,用户名和超时时间
myHandler = remoteCtrl()
ret, ret_info = myHandler.command(ip, password, cmd, port, user, timeout )
#### ret 表示最后一个命令的退出状态,ret_info 则是远程命令的打屏信息(含报错)
# 进行文件传输,需要传入远程服务器ip地址、密码、源文件路径、目标文件路径、传输动作(pull/push)、用户名、端口和超时时间
myHandler = remoteCtrl()
ret, ret_info = myHandler.transfer(ip, password, src, dst , action, user, port, timeout )
#### ret 表示传输结果,ret_info 是返回信息
|
代码很简单,不清楚的请注意代码中的注释,下面啰嗦下文件传输的说明:
①、规定目标文件夹(dst)必须以斜杠 / 结尾,否则识别为文件,而 src 因是实体存在,所以程序会自动判断是文件还是文件夹。
②、当执行本地文件夹推送至远程文件夹时,将不会保留本地文件夹名称,而是将本地文件夹内的所有文件推送到远程文件夹内,比如:
/data/srcdir/ 传送到 /data/dstdir/ ,结果是 srcdir 下的所有文件会存储在 dstdir
若想保留文件夹名称,请保证两端文件夹名称一致即可,比如:
/data/srcdir/ 推送到 /data/srcdir/
③、文件传输 demo:
将本地的/data/src.tar.gz 推送到 192.168.0.10 服务器的/data/files/dst.tar.gz
1
2
|
myHandler = remoteCtrl()
ret, ret_info = myHandler.transfer('192.168.0.10','123456','/data/src.tar.gz','/data/files/dst.tar.gz', 'push' )
|
Ps:若 action='pull'则表示将 src 拉取到本地的 dst。