python3通過paramiko遠程交互式控制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')

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章