使用paramiko远程执行命令、下发文件的实例


Posted in Python onOctober 01, 2017

写部署脚本时,难免涉及到一些远程执行命令或者传输文件。

之前一直使用sh库,调用sh.ssh远程执行一些命令,sh.scp传输文件,但是实际使用中还是比较麻烦的,光是模拟用户登陆这一点,还需要单独定义方法模拟输入。

感受一下:

from sh import ssh
PASS = 'xxxx'
def ssh_interact(line, stdin):
  line = line.strip()
  print(line)
  if line.endswith('password:'):
    stdin.put(PASS)
ssh('x.x.x.x', _out=ssh_interact)

来自官方文档

后来发现paramiko库更加优雅、便捷,所以准备用pramiko替换掉sh。

之前通过同事了解到,paramiko在远程执行python脚本时,脚本中的输出内容可能会通过stderr这个管道输出出来,所以直接用paramiko的SSHClient类中的exec_command方法执行,通过读stderr管道中有无输出来判断命令是否成功执行的方式是行不通的。所以用更底层一些的Channel类的recv_exit_status方法判断执行退出码更好一些。

安装

可以通过使用pip install paramiko安装,细节这里不再赘述。

封装

首先定义几个异常

# coding: utf-8
import os.path

from paramiko import SSHClient, AutoAddPolicy, AuthenticationException


class ConnectError(Exception):
  """
  连接错误时抛出的异常
  """
  pass

class RemoteExecError(Exception):
  """
  远程执行命令,失败时抛出的异常
  """
  pass

class SCPError(Exception):
  """
  远程下发文件时抛出的异常
  """
  pass
...
class Remote(object):
  def __init__(self, host, username, password=None, port=22, key_filename=None):
    self.host = host
    self.username = username
    self.password = password
    self.port = port
    self.key_filename = key_filename
    self._ssh = None

  def _connect(self):
    self._ssh = SSHClient()
    self._ssh.set_missing_host_key_policy(AutoAddPolicy())
    try:
      if self.key_filename:
        self._ssh.connect(self.host, username=self.username, port=self.port, key_filename=self.key_filename)
      else:
        self._ssh.connect(self.host, username=self.username, password=self.password, port=self.port)
    except AuthenticationException: 
      self._ssh = None
      raise ConnectionError('连接失败,请确认用户名、密码、端口或密钥文件是否有效')
    except Exception as e:
      self._ssh = None
      raise ConnectionError('连接时出现意料外的错误:%s' % e)

  def get_ssh(self):
    if not self._ssh:
      self._connect()
    return self._ssh

实例化SSHClient类,通过它的connect()方法获取SSH连接。

需要注意的是,远程访问的主机若是第一次连接,属于未知设备需要认证,通过set_missing_host_key_policy()方法设置一种策略,这里使用的是AutoAddPolicy()。

这里的_connect支持两种方式登录,一种是提供主机的用户名密码,另一种是通过密钥文件。在连接时检查如果指定了密钥文件则使用这种方式登录,否则通过用户名密码登录。

_connect()虽然是实际的建立连接的方法,但实际对外接口是get_ssh(),如果已经有建立好的SSH连接直接返回,避免重复建立连接。

class Remote(object):
  ...
  def ssh(self, cmd, root_password=None, get_pty=False, super=False):
    cmd = self._prepare_cmd(cmd, root_password, super)
    stdout = self._exec(cmd, get_pty)
    return stdout

  def _prepare_cmd(self, cmd, root_password=None, super=False):
    if self.username != 'root' and super:
      if root_password:
        cmd = "echo '{}'|su - root -c '{}'".format(root_password, cmd)
      else:
        cmd = "echo '{}'|sudo -p '' -S su - root -c '{}'".format(self.password, cmd)
    return cmd

  def _exec(self, cmd, gty_pty=False):
    channel = self.get_ssh().get_transport().open_session()
    if get_pty:
      channel.get_pty()
    channel.exec_command(cmd)
    stdout = channel.makefile('r', -1).readlines()
    stderr = channel.makefile_stderr('r', -1).readlines()
    ret_code = channel.recv_exit_status()
    if ret_code:
      msg = ''.join(stderr) if stderr else ''.join(stdout)
      raise RemoteExecError(msg)
    return stdout

在远程执行某些命令时,可能需要管理员权限,这种时候需要做一些判断,首先判断登录提供的用户名如果不是root,则需要对命令做一些修改。这里的修改有两种情况,一是,该普通用户本身就有sudo权限,只需要把执行的命令加到sudo之后执行就可以,还有一种是普通用户没有sudo权限,需要通过su先切换到root身份之后再执行,这种情况下需要提供root密码。

还有一点要注意的是get_pty这个参数,实际在远程执行sudo命令时,一般主机都会需要通过tty才能执行,通过把get_pty值设置为True,可以模拟tty,但是随之而来也会有一个问题,如果是远程执行一个需要长期运行的进程,例如启动nginx服务,当远程命令执行后SSH退出之后,此次运行的所有程序也会随之结束,所以在需要通过远程命令运行某些服务或程序时,是不能指定get_pty参数的;但同时,如果是普通用户远程登录,是没有权限执行service命令的。建议的一种方式是修改/etc/sudoers配置文件,注释掉Defaults requiretty这行。

class Remote(object):
  ...

  def scp(self, local_file, remote_path):
    if not os.path.exists(local_file):
      raise SCPError("Local %s isn't exists" % local_file)
    if not os.path.isfile(local_file):
      raise SCPError("%s is not a File" % local_file)
    sftp = self.get_ssh().open_sftp()
    try:
      sftp.put(local_file, remote_path)
    except Exception as e:
      raise SCPError(e)

先确认要下发的文件存在,并且是文件不是目录,如果不是则抛出异常。同时,remote_path需要是远程主机的文件绝对目录,例如/tmp/xxx.xxx,而不能是/tmp。

使用

# coding: utf-8
from remote_client import RemoteClient

rc = RemoteClient('10.1.100.1', 'test', 'test_pass')
rc.ssh('whoami')  # [u'test\n']
rc.scp('/tmp/test.out', '/tmp/test.out')

总结

相较于sh,paramiko好用的不是一星半点,这里只是提供了一个简单的封装,paramiko本身还有很多其他用法,欢迎大家积极讨论。

以上只是本人的一点理解,如果有错误之处,欢迎指正。

以上这篇使用paramiko远程执行命令、下发文件的实例就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
使用Python实现一个简单的项目监控
Mar 31 Python
讲解Python中if语句的嵌套用法
May 14 Python
python3.5 tkinter实现页面跳转
Jan 30 Python
python将.ppm格式图片转换成.jpg格式文件的方法
Oct 27 Python
解决Shell执行python文件,传参空格引起的问题
Oct 30 Python
解决新版Pycharm中Matplotlib图像不在弹出独立的显示窗口问题
Jan 15 Python
Python实现删除排序数组中重复项的两种方法示例
Jan 31 Python
Python3模拟curl发送post请求操作示例
May 03 Python
python flask web服务实现更换默认端口和IP的方法
Jul 26 Python
numpy求平均值的维度设定的例子
Aug 24 Python
Python API 操作Hadoop hdfs详解
Jun 06 Python
Python正则re模块使用步骤及原理解析
Aug 18 Python
解决Scrapy安装错误:Microsoft Visual C++ 14.0 is required...
Oct 01 #Python
win10下Python3.6安装、配置以及pip安装包教程
Oct 01 #Python
Python实现字符串反转的常用方法分析【4种方法】
Sep 30 #Python
Python实现利用最大公约数求三个正整数的最小公倍数示例
Sep 30 #Python
Python基于pygame模块播放MP3的方法示例
Sep 30 #Python
Python实现自动为照片添加日期并分类的方法
Sep 30 #Python
Python实现获取照片拍摄日期并重命名的方法
Sep 30 #Python
You might like
析构函数与php的垃圾回收机制详解
2013/10/28 PHP
输入值/表单提交参数过滤有效防止sql注入的方法
2013/12/25 PHP
ThinkPHP中U方法的使用浅析
2014/06/13 PHP
destoon调用自定义模板及样式的公告栏
2014/06/21 PHP
PHP的mysqli_stmt_init()函数讲解
2019/01/24 PHP
分享10篇优秀的jQuery幻灯片制作教程及应用案例
2011/04/16 Javascript
js关闭浏览器窗口及检查浏览器关闭事件
2013/09/03 Javascript
JavaScript实现的日期控件具体代码
2013/11/18 Javascript
JS控件ASP.NET的treeview控件全选或者取消(示例代码)
2013/12/16 Javascript
jQuery实现的漂亮表单效果代码
2015/08/18 Javascript
js判断空对象的实例(超简单)
2016/07/26 Javascript
node.js利用redis数据库缓存数据的方法
2017/03/01 Javascript
JAVA中截取字符串substring用法详解
2017/04/14 Javascript
webpack打包后直接访问页面图片路径错误的解决方法
2017/06/17 Javascript
Vue自定义弹窗指令的实现代码
2018/08/13 Javascript
[05:49]DOTA2-DPC中国联赛 正赛 Elephant vs LBZS 选手采访
2021/03/11 DOTA
[46:20]DOTA2-DPC中国联赛 正赛 PSG.LGD vs LBZS BO3 第二场 1月22日
2021/03/11 DOTA
浅谈python 四种数值类型(int,long,float,complex)
2016/06/08 Python
python cx_Oracle的基础使用方法(连接和增删改查)
2017/11/19 Python
python dataframe向下向上填充,fillna和ffill的方法
2018/11/28 Python
python机器人运动范围问题的解答
2019/04/29 Python
numpy.linalg.eig() 计算矩阵特征向量方式
2019/11/29 Python
win10下python2和python3共存问题解决方法
2019/12/23 Python
使用pandas库对csv文件进行筛选保存
2020/05/25 Python
Django中Aggregation聚合的基本使用方法
2020/07/09 Python
英国领先的狗和宠物美容专家:Christies Direct
2017/04/03 全球购物
统计学专业毕业生的自我评价分享
2013/11/28 职场文书
我未来的职业规划范文
2014/01/11 职场文书
高中生职业规划范文
2014/03/09 职场文书
三方合作协议书范本
2014/04/18 职场文书
2014年世界艾滋病日宣传活动总结
2014/11/18 职场文书
财务会计岗位职责
2015/02/03 职场文书
肖申克的救赎观后感
2015/06/02 职场文书
推广普通话的宣传语
2015/07/13 职场文书
淡雅古典唯美少女娇媚宁静迷人写真
2022/03/21 杂记
mysql查看表结构的三种方法总结
2022/07/07 MySQL