脚本编程

分享一个自写的Python远程命令和文件(夹)传输类

Jager · 5月7日 · 2017年 7916次已读

最近在跟一个自动化发布平台的建设事项,其中 Linux 系统的远程控制通道则由我独立开发完成,其中涉及到了 Linux 系统远程命令和文件传输操作。

因为之前写 Linux 系统密码管理系统的时候,用的是 Paramiko 的 SSHClient。所以,我这次依然采用 Paramiko 来做实现,代码虽短,说起其中的坑,我也是一把辛酸一把泪的填上了。

先上完整代码:

# -*- 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

简单说下用法:

# 先在 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

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。

37 条回应
  1. 明月登楼 2017-5-8 · 10:44

    没有看懂,但是还是要支持一下!

    • 励志语录 2017-5-17 · 19:12

      虽然看不懂,但是还要支持一下

    • 励志语录 2017-5-17 · 19:13

      签到成功!签到时间:下午7:13:10,每日签到,生活更精彩哦~

  2. BanYuner 2017-5-8 · 12:46

    虽然看不明白,但是还是得赞一个,这个打字晃动,眼睛都花了

    • avatar
      Jager 2017-5-9 · 21:54

      评论工具条的最右边,红色字体按钮【关闭震动】,点击即可关闭震动效果,同一个浏览器一直有效关闭。

      • BanYuner 2017-5-9 · 22:13

        好吧,一直没有注意这个问题,是我的错

        • avatar
          Jager 2017-5-10 · 15:51

          :grin:

  3. 不懂夜的黑 2017-5-8 · 21:09

    沙发 :mrgreen: ,没Linux基础表示看不懂!感谢Jager上次帮忙和鸟哥打招呼买主题,另外请教下文章标题浅灰色背景是怎么弄的,站内搜索没找到,鸟哥主题默认是无背景!

    • avatar
      Jager 2017-5-9 · 21:49

      这个你要懂点css,自己加的css。

      • 不懂夜的黑 2017-5-9 · 21:55

        方便有空的时候写篇教程吗?张兄!只知道类似background: #EEE之类,但不知道如何用DIV来实现,没代码基础,蛋蛋忧伤!

        • avatar
          Jager 2017-5-9 · 21:57

          将如下css代码加入到主题设置的自定义css中即可:

          .entry-header h1 {
              background: #ebebeb;
          }
          • 不懂夜的黑 2017-5-9 · 22:02

            大神,请收下我的膝盖!立竿见影,马上生效!32个赞!

  4. 发那科 2017-5-12 · 21:28

    博主百度权重5了,牛X啊!

  5. 堆爱博客 2017-5-16 · 12:10

    [color=deeppink]666666666666666666666牛逼牛逼[/color]

  6. 励志语录 2017-5-17 · 19:11

    签到成功!签到时间:下午7:11:20,每日签到,生活更精彩哦~

  7. 励志语录 2017-5-17 · 19:11

    6666666啊

  8. 邻水房产网 2017-5-24 · 11:32

    要不要这么牛,代码收藏了,现在很多博客都是写一些抄袭的东西,还是你这里有原创的东西。顶你张老板

  9. 国际化妆品加盟 2017-5-25 · 16:17

    虽然看不懂,但是还要支持一下

  10. 爱CSS 2017-5-26 · 18:58

    这语言完全看不懂感觉,世界要有一种统一的语言多好

    • avatar
      Jager 2017-5-27 · 13:04

      世界都是中文就皆大欢喜了吧。。。

  11. 香港服务器 2017-5-27 · 16:01

    看不太懂,但还是一篇用心的文章

  12. 网赚教程大全 2017-5-27 · 17:03

    嘿嘿,终于到你这里来了

  13. php网站源码 2017-6-7 · 12:05

    签到成功!签到时间:11:52:34,每日签到,生活更精彩哦~

  14. Forex 2017-6-7 · 15:02

    IE5下,只显示文字。

  15. 花卉说 2017-6-8 · 16:36

    第一次评论,博主辛苦了

  16. 花卉说 2017-6-8 · 16:37

    怎么看不见评论内容呢

  17. 互访互推导航 2017-6-13 · 10:21

    看不懂,但是应该很有用,保存下来,张大神的文章篇篇经典。。。。

  18. 冷知识 2017-6-15 · 8:56

    你这全能啊,好多语言不在话下呀

  19. 花卉说 2017-6-15 · 22:17

    晕死了,我的主题也是自适应,为什么360和搜狗把我的站转码了,百度好着呢。为什么呢,我看你的都好着呢

  20. 牛仔库 2017-6-19 · 21:10

    值得学习

  21. 赵亮 2017-6-28 · 16:22

    感谢分享!

  22. 面试技巧 2017-6-30 · 14:35

    代码写的很简洁,学习了

  23. 111 2017-7-15 · 15:34
    <strong>[color=blue][color=red]签到成功!签到时间:下午3:31:01,每日签到,生活更精彩哦~[/color][/color]</strong>
  24. 途航建材 2017-7-16 · 10:07

    好像留言不了呀,博主!

  25. qw 2017-7-20 · 18:14

    难道不能用ansible实现么,搞的这么费劲,不过你这博客做的不错?

    • avatar
      Jager 2017-7-22 · 19:49

      其实都对比过,最终发现越是底层的技术,适应性更好,更好DIY。
      方便的话希望可以QQ交流下。

  26. 王耍耍 2019-12-10 · 23:25

    感谢分享,刚好有需要