paramikopython
*paramiko須要PyCrypto模塊的支持shell
paramiko支持經過SSH協議進行一些操做,好比遠程執行命令,上下傳文件等等vim
用法:服務器
① 遠程命令session
ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) #指定當對方主機沒有本機公鑰的狀況時應該怎麼辦,AutoAddPolicy表示自動在對方主機保存下本機的祕鑰 ssh.connect('ip',22,'user','passwd') #SSH端口默認22,可改 stdin,stdout,stderr = ssh.exec_command("命令內容") #這三個獲得的都是類文件對象 outmsg,errmsg = stdout.read(),stderr.read() #讀一次以後,stdout和stderr裏就沒有內容了,因此必定要用變量把它們帶的信息給保存下來,不然read一次以後就沒有了 if errmsg == "": print outmsg ssh.close()
② 文件交流ssh
tra = paramiko.Transport(('ip',22)) #參數是一個tuple tra.connect(username='...',password='...') #必定要指明參數名的username和password。不然會報錯str has no attribute 'get_name' sftp = paramiko.SFTPClient.from_transport(tra) sftp.put('本地路徑','遠程路徑') #上傳文件 sftp.get('遠程路徑','本地路徑') #下載文件 tra.close()
須要注意的是在put和get方法中,兩個路徑都是須要完整的(要帶文件名!)異步
sftp的put和get方法還有callback這個參數。這個參數指定一個函數對象,這個函數應該接受兩個int型參數,分別表明了已上傳/已下載的字節數;總的要上傳/下載的字節數。利用callback的指定能夠作出一個相似進度條的功能。另外,還須要注意的是callback會在傳輸完成以前不斷地被調用可是具體是怎麼樣的時機下調用我不是很清楚,須要研究下paramiko的源碼。可是能夠肯定的一點是這個callback函數也是在主線程中調用的,因此最好不要在裏面寫什麼sleep,這樣會致使整個傳輸過程變慢的。函數
■ Transport的更多擴展spa
今天想經過python作一個SSH模擬終端。想法很是簡單,就是經過SSHClient類創建鏈接而後進行命令和返回的交互嘛。不過發生了不少問題,去網上一找才發現,原來SSHClient類的exec_command方法是個單session包裝的方法。即調用這個方法只能執行一趟命令,執行完成以後就斷開了鏈接,再次執行時又是新的session。好比:命令行
ssh.exec_command('cd /tmp;pwd')的返回是/tmp,但若是把cd和pwd分紅兩個exec_command寫的話,pwd最終返回的是HOME目錄,這代表了exec_command的單會話特性。那麼怎麼樣才能從更底層開始創建命令交互的通訊? 網上小查了一種方法仍是須要Transport這個咱們以前在SFTP時候用的類。
作法:
tran = paramiko.Transport(sock=(ip,22)) tran.connect(username='xxx',password='xxx') channel = tran.open_session() channel.get_pty() channel.invoke_shell() channel.send('ls\n') result = '' while True: time.sleep(0.5) res = channel.recv(65535).decode('utf8') result += res if result: sys.stdout.write(result.strip('\n')) if res.endswith('# ') or res.endswith('$ '): break
經過這樣的方式搭建出來的一個SSH命令通道是和Xshell這種軟件創建出來的終端差很少的,好比有終端命令行提示符,也支持cd等命令。
在獲取命令運行的返回(recv方法的返回)時,咱們用了一個while True的逐次取數據的方式。這麼作的一個好處就是當返回比較多比較大的時候能夠順利讀取徹底。其實這麼寫也是有其必要性在裏面的。若是直接在這個代碼的循環外面直接recv一下,返回獲得的會是'\r\n'而不是ls返回的文件信息。什麼原理不清楚可是既然while True這個方式有必要性又有優勢的話就能夠考慮用下。
跳出循環的方式是判斷返回的結尾是否是終端命令提示符的結尾#+"空格"或者$+"空格",這種判斷方法比較不健壯。網上也有用正則匹配或者其餘的一些方法來識別返回結果讀取到頭了,可參考。這種方法創建的SSH通道的話,是自帶命令行提示符的,並且每條命令的返回實質上是「真的返回」+"\n"+"命令提示符",因此能夠作到每個命令返回以後後面就有命令提示符。
另外一方面,這個通道也並非很萬能的,好比對經過stdin進行交互要怎麼作目前我尚未找到辦法、所以也就也意味着對vim,crontab -e之類的對交互有需求的軟件就不是支持很好了。
■ 更多擴展
● 在connect方法裏還有參數timeout = float設置鏈接超時時間
在connect方法中,還可用參數pkey指定本機私鑰文件用於身份驗證,內容能夠是個PKey類對象。也能夠用key_filename = '路徑' 這個參數來指定一個文件。而在SSHClient類還有方法load_system_host_keys用於指定對方主機存放本機公鑰的位置,默認不加參數的話是將這個位置設置爲~/.ssh/known_hosts。SSHClient類對象connect以前,先用load_system_host_keys指定本機公鑰存放位置,再在connect的時候指明本機私鑰的話,就不用password,只要username就能夠登陸了。
● 關於set_missing_host_key_policy
這個方法的參數有三種選擇,分別對應三種當對方主機沒有在相關文件中找到本機的公鑰時作的動做:
paramiko.AutoAddPolicy() 自動添加本機的公鑰和主機名進相關文件
paramiko.RejectPolicy() 自動拒絕未知的主機名和密鑰,「未知」指的是沒有在相關文件中有記錄的主機
paramiko.WarningPolicy() 和AutoAdd沒差,只不過在出現沒有記錄的狀況時警告一下
● SFTPClient類除了上面提到的那些方法之外,還有:
sftp.mkdir('路徑',mode) #mode不用加引號,直接寫755之類的便可,方法直接建立目錄並按mode設置其權限
sftp.remove('路徑') #刪除某目錄
sftp.stat("文件路徑") #獲取文件信息
sftp.listdir("路徑") #以列表方式返回目錄下的內容
以上這些方法和exec_command執行一些特定的命令是差很少的,實踐的時候也沒必要一棵樹吊死在exec_command上,也能夠適當考慮這些方法。
● ssh.exec_command的異步原理
據我估計ssh.exec_command應該是單獨開一條線程來遠程執行命令,而當咱們對命令的輸出不感興趣,在exec_command執行以後不調用read來收集遠程返回的信息的話,那麼這個線程是不阻塞的。這是個小坑的地方。好比下面兩個例子,ssh是一個已經配置好的SSHClient對象:
stdin,stdout,stderr = ssh.exec_command("sleep 5s;echo foo") stdin,stdout,stderr = ssh.exec_command("echo bar") out,err = stdout.read(),stderr.read() if err: print err else: print out #這種狀況下的輸出只有bar
######另外一種狀況###### stdin,stdout,stderr = ssh.exec_command("sleep 5s;echo foo") out,err = stdout.read(),stderr.read() stdin,stdout,stderr = ssh.exec_command("echo bar") out,err = stdout.read(),stderr.read() if err: print err else: print out #這種狀況下先等5秒而後輸出foo和bar
能夠看到第一種狀況中,沒有讀取stdout等的信息,那麼本地程序直接往下執行,無論以前的命令是否還未給出結果,而第二種狀況,因爲我要讀取內容,因此必須等到上一條命令執行完成給出結果本地才繼續跑下一條命令。
● stdin的用法
以前因爲基本上都是從服務器上面讀數據,對於stdin這個變量一直感受很雞肋。直到那天要批量改服務器密碼。。試了一下果真stdin是能夠拿來進行write的。記得在write的時候要適當插入回車\n來模擬敲回車的過程,不然極可能會出現SSH被堵塞的狀況。好比改密碼這個過程:
##前面配置過程省略## stdin,stdout,stderr = ssh.exec_command("passwd") stdin.write("old_password\nnew_password\nnew_password\n") #由於密碼要確認,因此要輸兩遍 out,err = stdout.read(),stderr.read() if err != '': print err else: print out