paramiko模块exec_command()函数是将服务器执行完的结果一次性返回给你;

invoke_shell()函数类似shell终端,可以将执行结果分批次返回,看到任务的执行情况,不会因为执行一个很长的脚本而不知道是否执行成功

exec_command():

invoke_shell()

python 操作ssh--有more用invoke_shell循环获取数据

# 实例化SSHClient
client = paramiko.SSHClient()
# 自动添加策略,保存服务器的主机名和密钥信息
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
loger.debug("hostname=%s username=%s password=%s" %(equ_ip_s,username_s,passwd_s))
# 连接SSH服务端,以用户名和密码进行认证
client.connect(hostname=equ_ip_s, username=username_s, password=passwd_s)
chan = client.invoke_shell()
chan.settimeout(9000)
#  设置结束条件
p = re.compile(r'xxx')
loger.debug("client is %s",client)
loger.debug("cmd is '%s'",cmd)
chan.send(cmd+'\n')
# chan.send(cmd+'\n')
result = ''
result = chan.recv(4096)
#  循环获取数据
while True:
    chan.send(" ")
    results = chan.recv(1024000)
    result += results
    if p.search(results):
        print len(result)
        print type(result)
        chan.send('q')
        break
# print result
# 拆分获取每行的数据
result2 = re.split(r'\r\n', result)

参考:第二篇:ssh.invoke_shell() 切换root出现的新问题 - FelixApff - 博客园

SSHClient类打包了.Transport,.Channel,.SFTPClient来满足多方面的认证和传输的要求,下面来看看部分用法。

一、类实例化

ssh_client = paramiko.SSHClient()

实例化一个SSHClient类的对象。

实例化的时候做了些什么呢?查看一下paramiko版本2.8.1中的源码:

def __init__(self):
    """
    Create a new SSHClient.
    """
    self._system_host_keys = HostKeys()
    self._host_keys = HostKeys()
    self._host_keys_filename = None
    self._log_channel = None
    self._policy = RejectPolicy()
    self._transport = None
    self._agent = None

可以看到构造函数中,对新对象的部分属性进行了初始化,如主机公钥、日志、拒绝策略、传输方式和代理这些。

其中关于公钥,根据在RFC4251中的定义:

   Each server host SHOULD have a host key.  Hosts MAY have multiple
   host keys using multiple different algorithms.  Multiple hosts MAY
   share the same host key.  If a host has keys at all, it MUST have at
   least one key that uses each REQUIRED public key algorithm (DSS
   [FIPS-186-2]).

   The server host key is used during key exchange to verify that the
   client is really talking to the correct server.  For this to be
   possible, the client must have a priori knowledge of the server's
   public host key.


每个服务器都有公钥,在编写链接功能时,在和服务器建立链接之前,要进行准备公钥的操作。

二、自动添加公钥的方法

当链接新的服务器时,我们不一定会准备好了公钥。这时如何处理:

ssh_client.set_missing_host_key_policy()

这个函数可以用来处理针对未知主机时,使用什么样的策略。阅读源码中的注释:

* A **policy** is a "policy class" (or instance thereof), namely some
  subclass of `.MissingHostKeyPolicy` such as `.RejectPolicy` (the
  default), `.AutoAddPolicy`, `.WarningPolicy`, or a user-created
  subclass.

可以看出如果不设定的话,默认是拒绝建立链接的。所以我们可以加入以下语句:

ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

这条语句就是当链接到一个未知的主机时设置策略为,自动添加公钥策略。

如果不添加这条语句,则需要注意:链接新的服务器由于没有添加公钥,会产生报错。

三、建立链接

进行建立链接的操作:

ssh_client.connect(hostname=server_ip, port=server_port, username=server_username,password=server_password)

通过用户名和密码的方式登录主机,这种方法比较常用。不过用秘钥登录,则更方便和安全

paramiko是怎样建立链接的呢?如下:

for af, addr in to_try:
    try:
        sock = socket.socket(af, socket.SOCK_STREAM)
        if timeout is not None:
            try:
                sock.settimeout(timeout)
            except:
                pass
        retry_on_signal(lambda: sock.connect(addr))
        # Break out of the loop on success
        break
    except socket.error as e:
        # Raise anything that isn't a straight up connection error
        # (such as a resolution error)
        if e.errno not in (ECONNREFUSED, EHOSTUNREACH):
            raise
        # Capture anything else so we know how the run looks once
        # iteration is complete. Retain info about which attempt
        # this was.
        errors[addr] = e

paramiko通过socket库,将重复尝试对主机进行建立链接的操作。

根据retry_on_signal()函数的源码:

def retry_on_signal(function):
    """Retries function until it doesn't raise an EINTR error"""
    while True:
        try:
            return function()
        except EnvironmentError as e:
            if e.errno != errno.EINTR:
                raise

当出现除了系统中断以外的异常时,会让程序停止尝试。

四、执行命令

可以选择.exec_command或者.invoke_shell函数执行命令。

def resource_query_get(connection, query_command, pty_status=False):
    standard_in, standard_out, standard_err = connection.exec_command(query_command, get_pty=pty_status)
    return standard_out

可以将需要执行的多条命令,作为一个数组循环调用函数,以自动完成预定的任务。

另外,需要注意如sar,top等非即时返回结果的命令,需要加上pty_status=True的参数。pty_status为True时,程序向服务器请求了一个伪终端。例如下面这种情况:

    server_pty_command_list = ["sar -u 2 5 | sed -n '9p' | awk '{print $3}'"]
    for query_command in server_pty_command_list:
        record_file(resource_query_get(connection, query_command, True))

我们需要取回CPU使用率的5秒内的平均取样,这条命令需要等待5秒后才会有结果,如果没有把pty_status设置为Ture,就无法取回值。

看下关于pty_status的注释:

Request a pseudo-terminal from the server.  This is usually used right
after creating a client channel, to ask the server to provide some
basic terminal semantics for a shell invoked with `invoke_shell`.
It isn't necessary (or desirable) to call this method if you're going
to execute a single command with `exec_command`.

设计者建议也可以使用invoke_shell函数来完成具有交互需求的功能。所以可以根据实际需要来选择用法,需要即时执行命令返回结果的建议使用exec_command,更复杂的交互需求则建议使用invoke_shell函数完成。

原文链接:https://blog.csdn.net/m0_66158540/article/details/123261304

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐