python3通过paramiko远程交互式控制Linux服务器

  • Post author:
  • Post category:linux


这几天一直在想办法通过ssh方法通过自己工作的windows端去访问控制远程的Linux服务端,为后期的服务器自动化做准备。这几天几乎把能想到的知识点全都百度了一遍,不会google(比较菜),但没有找到自己想要的方式,有的代码也无法正常运行。经过自己的研究测试,发现:

(1)如果想实现真正的交互式,在paramiko中必须使用

invoke_shell()

的方式。

(2)网上大部分都是使用的非交互式方法

exec_command()

,每次调用该方法就相当于重新开启了一个command窗口结束后就关闭了该窗口,所以无法连续进行操作,对我而言最致命的是该方法远程无法操作类似python或者hbase这样的shell窗口,一旦在exec_command()中输入类似’python’的指令,就直接卡死(我猜测是该方法无法正确分析出linux服务器是否返回信息结束导致)。

下面分别介绍这两种模式:

exec_command()



invoke_shell()

非交互式exec_command

使用该方法需要注意下,在我使用过程中我遇到了无法加载环境变量情况,比如我安装了hbase,我想通过指令”hbase version”查看下版本,但返回给我结果是没有hbase这个指令,我在网上查了很久发现需要使用 bash -lc ‘hbase version’ 这样的方法包装下指令才能正常执行。另外如果要一次性执行多条指令,需要用 “;” 将指令隔开。

import paramiko

hostname = '192.16.21.12'
port = 22
username = 'hadoop'
password = 'hadoop'
timeout = 10

if __name__ == "__main__":
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    try:
        ssh.connect(hostname=hostname, port=port, username=username,password=password, timeout=timeout)
    except Exception as e:
        print(e)
        exit(1)
    command = 'ls -l;ls -la'  # 多个指令间用";"隔开
    command = "bash -lc '{}'".format(command)  # 需要用bash -lc包装一下命令,否则环境变量有问题
    stdin, stdout, stderr = ssh.exec_command(command)
    result = stdout.read().decode('utf-8')
    err = stderr.read().decode('utf-8')
    if len(err) != 0:
        print(err)
    else:
        print(result)
    ssh.close()


注意:linux的标准输出中会有很多文件带有颜色信息,如果不知道该怎么处理颜色信息可以使用–color=never屏蔽掉

,例:

ls --color=never

交互式invoke_shell(推荐使用)

在使用invoke_shell时遇到的问题是,判断服务器返回信息何时结束,这里通过我自己的观察(知识储备不够只能凭直觉了),一般当返回信息结束时,最后都是返回linux的SP1输入提示符(’hadoop@server:~$ ‘)那么我们可以通过返回信息的最后两个字符是否是’$ ‘来判断是否返回信息结束(注意$符号后是有个空格的)。通过这种方式,能够完成些基本的简单操作,但是像使用python或者hbase这种进入自己shell环境的情况,很明显结束符就不是’$ ‘了,但我发现一般这些shell环境的结束提示符都是以’> ‘或者’* ‘结束的,那么我们可以将这些都作为结束符,只要返回信息的最后两个字符是其中一种我们就认为返回结束。当然还有很多情况没有考虑到,这里只是做个简单的事例。在下面还有提供了另外一个版本,交互起来更加方便,不用去判断系统返回信息是否结束。

import paramiko
import time

hostname = '192.16.21.12'
port = 22
username = 'hadoop'
password = 'hadoop'
timeout = 10


def runCommand(chanT, command, endSymbol):
    chanT.send(command + '\n')  # 指令后加 '\n' 表示换行
    results = ''
    while True: 
        result = chanT.recv(1024).decode('utf-8')
        results += result
        if results[-2:] in endSymbol:  # 判断最后两个字符是否是我们定义的结束符
            break
    re = results.split('\n')[1:]  # 第一行是我们输入的指令,没用丢弃
    print('\n'.join(re), end='')
    return re[:-1]  # 最后一行是linux的SP1输入提示符,没用丢弃


if __name__ == "__main__":
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(hostname, port, username, password)
    chan = ssh.invoke_shell()  # 创建一个交互式的shell窗口
    chan.settimeout(1000)
    time.sleep(3)  # 刚进入linux服务器等待一会,否则直接通过chan.recv获取的信息不完整
    loginInfo = chan.recv(1024).decode('utf-8')  # Welcome to Ubuntu 16.04.6 LTS..等登录信息
    print(loginInfo, end='')
    endSymbol = ['$ ', '> ', '* ']  # 设置我们定义的结束符
    while True:
        command = input():  # 等待用户输入指令
        if command == 'quitshell'  # 当用户输入quitshell指令时退出程序
            print('Bye Bye!')
            exit(0)
        result = runCommand(chan, command, endSymbol)  

复制该代码,修改一下服务器对应的host,username以及password就可以直接使用了,使用过程中我发现在pycharm中使用没有任何问题,但在window的cmd窗口中显示带颜色的字体会出现问题(linux中很多文字信息都是通过不同颜色来区分的)。

测试效果如下,因为使用的机器无法联网,也不能传文件,所以展示的信息都是手敲的:

Welcome to Ubuntu16.04.6 LTS (GNU/Liunx 4.4.0-165.generic x86_64)
.................(信息太多不敲了)
hadoop@server:~$ ls
software zookeeper.out
hadoop@server:~$ python3
Python 3.5.2 (Default, Oct 8 2019, 13:06:37)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>print('hello')
hello
>>>exit()
hadoop@server:~$ 

交互式invoke_shell()的另一种使用方法:

这里使用了python的多线程,开了一个新的线程专门去负责读取系统返回的信息,有信息就打印出来,不用管结束符的问题。因为在人机交互过程中,人们通过返回打印的信息就知道是否该进行输入了。该方法也有一个问题,因为在主线程中,设置的每隔0.5s去读取用户的输入,如果在0.5s内linux服务器上的信息没有返回完,就会被input给堵塞,我暂时也没有想到什么好点的办法,但是如果你是在linux上可以直接使用select.select函数去监控stdin中有没有输入,这样就不会被堵塞,但window下select无法对stdin监控。

import paramiko
import time
import _thread

hostname = '192.16.21.12'
port = 22
username = 'hadoop'
password = 'hadoop'
timeout = 10


def recvThread():  # 开启一个多线程负责读取返回信息
    global chan
    while True:
        while chan.recv_ready():
            info = chan.recv(1024).decode('utf-8')
            print(info, end='')
            time.sleep(0.1)


if __name__ == "__main__":
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(hostname, port, username, password)
    chan = ssh.invoke_shell()  # 创建一个交互式的shell窗口
    chan.settimeout(1000)
    _thread.start_new_thread(recvThread, ())

    while True:
        time.sleep(0.5)
        command = input()
        if command == 'quitshell':
            print('Bye Bye!')
            exit(0)
        chan.send(command + '\n')



版权声明:本文为qq_37541097原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。