這幾天一直在想辦法通過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')