python 第三方庫paramiko的常用方式
介紹
paramiko是什麼可以參考其他人的博客或文章,這裡不再贅述,直入正題。
本次測試的版本信息如下:
- python 3.9
- paramiko 2.7.2
- centos 8
三種常用方式
paramiko 的三種常用方式如下:
- 使用密碼進行登錄
- 使用密鑰免密碼登錄
- SFTP 傳輸文件
其中最割裂的就是SFTP 傳輸文件,很多文章登陸使用SSHClient類,傳輸文件使用Transport類,我也是這樣用瞭很長時間。
如果你也是這麼用的,你沒有啥想法嗎?用python就是節約心智,怎麼一個變形還能出來兩種東西呢,沒有辦法統一嗎?
網上的統一就是實例化Transport類然後實例化SSHClient類,再把實例化的Transport類添加到實例化SSHClient類。總是有一種別扭的感覺。
重點:查看源碼可以發現,SSHClient類直接提供瞭 SFTP 傳輸文件的實例化方法,直接用就行瞭,世界頓時清爽瞭很多
使用密碼進行登錄
import paramiko hostname = 'localhost' port = 22 username = 'aaron' # 看密碼就知道我是用的redhat系linux系統 password = 'redhat' # 實例化SSHClient類 ssh = paramiko.SSHClient() # 遠程主機沒有本地密鑰時的處理規則,主要有三個 # AutoAddPolicy:直接建立連接,不進行yes/no的確認 # WarningPolicy:直接建立連接,但是會提示是新連接 # RejectPolicy:拒絕未知的連接,依賴系統密鑰的信息。默認選項。 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 連接到服務器 ssh.connect(hostname, port, username, password) # 執行命令,獲取標準輸入、標準輸出、標準錯誤輸出,均為流式輸入輸出 # 函數原型為 exec_command(self, command, bufsize=-1, timeout=None, get_pty=False, environment=None, ) # 理論上可以通過標準輸入,也就是下面的額stdin變量完成連續輸入 # 同時參數中有佈爾型參數 get_pty 可以指定是否獲取 tty 通道,這樣阻塞輸入,比如sudo輸入密碼什麼的都能做。貌似就可以做成你想要的任何東西。 # 但是以上兩點沒有驗證,貌似比較麻煩,我太懶瞭-_-||| # # 另外,exec_command方法每次都是新開一個通道執行命令,執行完成後狀態消失。SSHClient類還提供一個invoke_shell方法,這個方法可以連續輸入命令。 # 這兩個的區別主要是 invoke_shell使用SSH shell通道,而exec_command使用SSH exec通道。 # shell通道就是常用的終端軟件登陸的通道,登陸變量都會進行加載比如 ~/bashrc 等 # 而 exec通道 則不進行加載登陸文件,相當於linux桌面系統上右鍵開terminal一樣。 # 如果你還是不懂,沒關系,invoke_shell nb就完事瞭 stdin, stdout, stderr = ssh.exec_command('df') # 打印輸出 print(stdout.read().decode()) # 不要忘記關閉連接 ssh.close()
使用密鑰免密碼登錄
這裡使用密鑰文件,但是為瞭一般情況,我給密鑰文件設置瞭密碼,如果你隻是想免密碼,不設置密碼即可.
在客戶機上生成密鑰對,將公鑰傳遞給服務器
ssh-keygen -t rsa # 這裡設置密碼為redhat_rsa,這裡是給密鑰設置密碼,如果想免密,不設置密碼即可 ssh-copy-id -i ~/.ssh/id_rsa.pub aaron@localhost
import paramiko hostname = 'localhost' port = 22 username = 'aaron' # 這裡是密鑰文件的密碼 password = 'redhat_rsa' # 密鑰文件的位置,可以是列表,paramiko會把列表裡文件順序嘗試,登陸上位置 private_key_path = '/home/aaron/.ssh/id_rsa' ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) # 這裡網上很多先設置pkey傳入,但是直接傳文件路徑也可以,還簡單。 # 我使用的和網上不同,另一個版本請自行搜索,資料n多 # 如果沒有密鑰,則不需添加password # look_for_keys默認為True,就是會找你 .ssh 目錄下有沒有合適的密鑰文件 # 也就是說如果密鑰文件存在,但是你傳 key_filename 時傳錯瞭,不影響,paramiko已經替你想好瞭,這才是正經 python 應有的待遇,舒服! ssh.connect(hostname, port, username=username, password=password, key_filename=private_key_path, look_for_keys=False) stdin, stdout, stderr = ssh.exec_command('ip a') print(stdout.read().decode()) ssh.close()
SFTP 傳輸文件
import paramiko hostname = 'localhost' port = 22 username = 'aaron' password = 'redhat' # 還是SSHClient登陸,以上兩種方式都可以。 ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(hostname, port, username, password) # 重頭戲,直接使用打開方法即可 sftp = ssh.open_sftp() # do something # 從這裡到下面的ssh.close()為止都是sftp能做的事情,具體能做啥,請看下一個代碼段,這裡隻列舉上傳(put) 下載(get) 文件,這兩個也比較重要 # 回調函數,沒想到吧,上傳下載還能有回調函數 # 參數一定,都是傳入的兩個size,int型數據 # size 已傳輸文件累計大小 # file_size 文件總大小 def callback(size, file_size): print(f"目前傳輸文件比例: {size} / {file_size}") # 上傳文件,參數都給你們瞭,看看啥意思就行瞭 # 主要就是這個confirm, 如果定義會檢測一下上傳到服務器文件大小和本地大小是否一致,默認False stat = sftp.put(localpath='/tmp/s.avi', remotepath='/tmp/a.avi', callback=callback, confirm=True) print(stat) # 下載文件,同樣參數都給你們瞭,看看啥意思就行瞭 sftp.get(localpath='/tmp/s.avi', remotepath='/tmp/a.avi', callback=callback) ssh.close()
stfp 能 do 的 something
# 列出當前路徑下有什麼文件,默認path="." print(sftp.listdir()) # 列出當前路徑下文件屬性,默認path="." attrs = sftp.listdir_attr() print(attrs) print("="*20) # listdir函數就是遍歷的每個屬性的filename print(attrs[0].filename) print(attrs[0].longname) print(attrs[0].st_atime) print(attrs[0].st_mtime) print(attrs[0].st_gid) print(attrs[0].st_uid) print(attrs[0].st_mode) # 就是 listdir_attr 的迭代器版本 attrs = sftp.listdir_iter() print("="*20) for i in attrs: print(i.filename) print(i.longname) print(i.st_atime) print(i.st_mtime) print(i.st_gid) print(i.st_uid) print(i.st_mode) # 和內置open用法基本相同,隻不過是打開外部文件 with sftp.open("a.txt", "w") as f: f.write("aaa") # 刪除文件,隻能刪除文件,刪除目錄使用rmdir函數。文件不存在則報錯。 sftp.remove("/home/aaron/a.txt") # 文件改名,類似於move sftp.rename("/home/aaron/as.txt", "/tmp/soon.txt",) # 符合posix標準的改名,沒有測試 sftp.posix_rename("/home/aaron/as.txt", "/tmp/soon.txt",) # 新建目錄 sftp.mkdir("/home/aaron/as") # 刪除目錄,類似rmdir,刪除的必須為空目錄 sftp.rmdir("/home/aaron/as") # 返回單個文件的attr信息,如果是軟連接則直接返回真實文件信息 stat = sftp.stat("/tmp/soon.txt") print(stat) # 測試和stat差不多,如果是軟鏈接則返回軟鏈接文件信息 stat = sftp.lstat("/tmp/soon.txt") print(stat) # 修改權限,權限為八進制數,需要把權限換算為十進制數。比如下面的例子就是權限333 sftp.chmod("/tmp/soon.txt", 219) # 修改屬主和屬組,屬主和屬組為gid和uid表示。需要有權限。 sftp.chown("/tmp/soon.txt", 0, 0) # 設置atime和mtime,如果傳入None,則設置為當前時間。否則必須傳入兩個元素的元組或數組,分別為 (atime, mtime) sftp.utime("/tmp/soon.txt", None) import time sftp.utime("/tmp/soon.txt", (time.time(), time.time())) # 讀取軟鏈接指定的目標 print(sftp.readlink("/etc/rc.local")) # 讀取軟連接制定目標的絕對路徑 print(sftp.normalize("/etc/rc.local")) # 切換工作路徑。SFTP沒有工作路徑的概念,但是paramiko進行瞭模擬。如果設置瞭路徑,所有的相對路徑都是根據這個路徑來的。如果想要切換回去傳入None即可。 sftp.chdir("/tmp") # 獲取當前的工作路徑。如果沒有使用chdir切換過,則會返回None print(sftp.getcwd())
terminal demo
自己一直想做一個類似xshell的東西,尤其是mac本的iterm或者iterm2是啥垃圾,還被吹的不行不行的,是沒用過好東西嗎。
但是每次執行exec_command都會從傢目錄開始,無法切換目錄,十分不方便。一直沒有啥進展,知道遇到瞭 invoke_shell ,一切看起來都有瞭些可能。
import time from threading import Thread import paramiko # 接收消息並打印的函數 # 返回的消息會分成好幾段,如果隻是發送命令後直接打印是打印不全的,這裡直接循環檢測緩沖區,有結果就打印。 def recv_and_print(channel): # 定義全局變量,recv_func_flag 此接收函數退出標志,cmd 當前執行命令 global recv_func_flag, cmd while recv_func_flag: # 如果此次命令是exit並且退出完成,則設置退出標志 if cmd == "exit" and channel.exit_status_ready(): # 打印退出狀態,為int型數字 print(f"此次退出狀態:{channel.recv_exit_status()}") # 退出標志置為假 recv_func_flag = False # 吐過緩沖區有數據 if channel.recv_ready(): # 接收數據 response = channel.recv(1024).decode().strip() # 需要註意的是接收的數據會把傳入的命令也返回一遍,這裡我們隻保留自己打在屏幕上的,不要傳回的,所以傳回的數據如果和命令相同則不打印,略過 if response != cmd: print(response, end="") print("接收函數退出......") # 定義全局變量 recv_func_flag = True cmd = "" # ssh登陸,老一套東西 hostname = 'localhost' port = 22 username = 'aaron' password = 'redhat010;' ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(hostname, port, username, password) # 獲取invoke_shell invoke_shell = ssh.invoke_shell() # 接收函數使用另一個線程運行,因為和當前主線程一同退出,所以不需要join方法 t = Thread(target=recv_and_print, args=(invoke_shell,)) t.start() # 主線程退出標志 flag = True while flag: # 輸入命令 cmd = input() # 輸入命令必須有回車才會執行,這裡我發送的是linux命令,\r之後能執行命令,如果系統不同,需要測試\r\n等回車字符 invoke_shell.send(f"{cmd}\r") # 如果命令是exit則退出循環 if cmd == "exit": flag = False # 檢測接收函數已經退出,這裡停止0.5s是因為退出命令發送給服務器,服務器會返回註銷的信息,之後檢測接收函數才會完全退出,認為設置瞭一個等待時間,這個時間因為是本機,設置的相對不長,如果是其他主機,需要根據網絡以及超時情況進行設置 while recv_func_flag: time.sleep(0.5) # 別忘瞭關閉ssh ssh.close()
這個demo直接運行然後輸入命令即可,就像是使用terminal直接登陸一樣。
ll等定義的alias也是能使用的。
但是雙擊tab ctrl+c 等沒有實現,留待諸君完善吧
這個demo目前還有一些問題。時間原因也懶得解決瞭,以後用到的時候再深入探究吧。
- 換行總是有問題,時好時壞,感覺每次發送數據有時命令、結果、信息提示符有時合並發送,有時分開發送,沒有啥必然規律。也許和linux發行版有關?和tcp通信有關?目前原因不明(具體現象請自行測試)
- 顯示信息使用瞭 print(response, end=””) 退出時也使用相同的顯示命令,到時系統註銷的顯示信息和函數的提示信息”接收函數退出……”拼接在一起瞭(對啊,提示信息之前我可以加個回車啊,哎呀,不再測試瞭,太累瞭)
- su命令也可以正常執行,輸入密碼啥的不影響,但是密碼會明文顯示。。。。。
- 接上條,su退出成問題,su退出使用exit,整個函數就退出瞭。算瞭,不修復瞭/-_-\
到此這篇關於python 第三方庫paramiko的文章就介紹到這瞭,更多相關python 第三方庫paramiko內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 使paramiko庫執行命令時在給定的時間強制退出功能的實現
- python基於paramiko庫遠程執行 SSH 命令,實現 sftp 下載文件
- Python實現網絡自動化eNSP
- Python使用paramiko連接遠程服務器執行Shell命令的實現
- python 遠程執行命令的詳細代碼