最近在跟一个自动化发布平台的建设事项,其中 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 @ zhang.ge 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。
2017年05月08日 am10:44
没有看懂,但是还是要支持一下!
2017年05月17日 pm7:12
虽然看不懂,但是还要支持一下
2017年05月17日 pm7:13
签到成功!签到时间:下午7:13:10,每日签到,生活更精彩哦~
2017年05月08日 pm12:46
虽然看不明白,但是还是得赞一个,这个打字晃动,眼睛都花了
2017年05月09日 pm9:54
评论工具条的最右边,红色字体按钮【关闭震动】,点击即可关闭震动效果,同一个浏览器一直有效关闭。
2017年05月09日 pm10:13
好吧,一直没有注意这个问题,是我的错
2017年05月10日 pm3:51
2017年05月08日 pm9:09
沙发
,没Linux基础表示看不懂!感谢张戈上次帮忙和鸟哥打招呼买主题,另外请教下文章标题浅灰色背景是怎么弄的,站内搜索没找到,鸟哥主题默认是无背景!
2017年05月09日 pm9:49
这个你要懂点css,自己加的css。
2017年05月09日 pm9:55
方便有空的时候写篇教程吗?张兄!只知道类似background: #EEE之类,但不知道如何用DIV来实现,没代码基础,蛋蛋忧伤!
2017年05月09日 pm9:57
将如下css代码加入到主题设置的自定义css中即可:
2017年05月09日 pm10:02
大神,请收下我的膝盖!立竿见影,马上生效!32个赞!
2017年05月12日 pm9:28
博主百度权重5了,牛X啊!
2017年05月16日 pm12:10
666666666666666666666牛逼牛逼
2017年05月17日 pm7:11
签到成功!签到时间:下午7:11:20,每日签到,生活更精彩哦~
2017年05月17日 pm7:11
6666666啊
2017年05月24日 am11:32
要不要这么牛,代码收藏了,现在很多博客都是写一些抄袭的东西,还是你这里有原创的东西。顶你张老板
2017年05月25日 pm4:17
虽然看不懂,但是还要支持一下
2017年05月26日 pm6:58
这语言完全看不懂感觉,世界要有一种统一的语言多好
2017年05月27日 pm1:04
世界都是中文就皆大欢喜了吧。。。
2017年05月27日 pm4:01
看不太懂,但还是一篇用心的文章
2017年05月27日 pm5:03
嘿嘿,终于到你这里来了
2017年06月07日 pm12:05
签到成功!签到时间:11:52:34,每日签到,生活更精彩哦~
2017年06月07日 pm3:02
IE5下,只显示文字。
2017年06月08日 pm4:36
第一次评论,博主辛苦了
2017年06月08日 pm4:37
怎么看不见评论内容呢
2017年06月13日 am10:21
看不懂,但是应该很有用,保存下来,张大神的文章篇篇经典。。。。
2017年06月15日 am8:56
你这全能啊,好多语言不在话下呀
2017年06月15日 pm10:17
晕死了,我的主题也是自适应,为什么360和搜狗把我的站转码了,百度好着呢。为什么呢,我看你的都好着呢
2017年06月19日 pm9:10
值得学习
2017年06月28日 pm4:22
感谢分享!
2017年06月30日 pm2:35
代码写的很简洁,学习了
2017年07月15日 pm3:34
2017年07月16日 am10:07
好像留言不了呀,博主!
2017年07月20日 pm6:14
难道不能用ansible实现么,搞的这么费劲,不过你这博客做的不错?
2017年07月22日 pm7:49
其实都对比过,最终发现越是底层的技术,适应性更好,更好DIY。
方便的话希望可以QQ交流下。