功能:
- 连接远程服务器
- 上传本地文件到服务器中,下载服务器中文件到本地
- 运行服务器中的程序/执行command
使用paramiko
库完成上述功能。paramiko
提供了SSHv2协议的一个抽象,可以完成客户端和服务器端的功能。在我要完成的功能中,主要是客户端的程序。
函数的逻辑:
开ssh连接–>上传文件–>执行服务器中程序–>取文件
开ssh连接
一个错误示范下面:
import paramiko
ssh_client=paramiko.SSHClient()
ssh_client.connect(hostname=’hostname’,username=’mokgadi’,password=’mypassword’)
如果思路非常直接的实例化一个paramiko.SSHClient()
对象,然后就进行连接,会出现一个error:
missing_host_key raise SSHException(‘Server %r not found in known_hosts’ % hostname) paramiko.ssh_exception.SSHException: Server ‘hostname’ not found in known_hosts
这是由于没有告诉本机可以“信任”待访问的远程服务器。如果以命令行执行的话,会有如下信息:
The authenticity of host ‘hostname’ can’t be established.RSA key fingerprint is ‘key’. Are you sure you want to continue connecting (yes/no)?
这涉及到网络安全中公私钥的问题,比较复杂网络安全课没好好上 不做讨论。总之需要告诉本机,要访问的服务器是可以信任的,Paramiko提供了这样的机制,那么开启ssh连接的正确示范:
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(hostname = hostname, port = port, username = username, password=password)
需要提前定义好 hostname,port,username,password。
上传文件
上传文件使用sftp协议,sftp是一个基于ssh的协议,所以要在ssh连接后开启sftp连接。可以使用paramiko提供的open_sftp()
函数,我也看到了很多使用transform对象的方法,但是个人认为基于ssh后开启sftp最直观。
逻辑就是开启连接–>上传–>关闭连接。
ftp_client_up = ssh_client.open_sftp()
ftp_client_up.put(local_file,remote_file)
ftp_client_up.close()
其中需要提前定义好local_file
和remote_file
。需要注意的是,必须写明文件名且两种必须一样,如下所示:
local_file = '/Users/xxx/xxx/1.txt'
remote_file = '/home/username/xxx/1.txt'
执行服务器中程序/执行command
执行服务器中的程序是通过命令行完成的,cd到对应目录 python xxx完成的,所以先介绍执行command的方式。通过库中exec_command
函数完成
stdin,stdout,stderr=ssh_client.exec_command("ls")
需要注意的是,exec_command
每次通过开一个新的Channel来执行传输的命令,并通过stdin、stdout和stderr的这些类似于Python文件对象的返回,理论上只能执行一条命令。也就是第一行exec_command("cd xxx")
第二行执行xxx文件中的某个程序python xxx.py
这种方式是不行的,此时会回到根目录。我大概是利用了一种漏洞,在exec_command
中一次执行多个,那就是把要执行的命令之间用\n
分开,且最后一个不要有。
stdin,stdout,stderr=ssh_client.exec_command("xxx\n xxx\n xxx")
但我需要执行的程序需要开启虚拟环境且需要使用GPU。怎么做呢?
由于每次exec_command
它并不像bash一样,一启动就会通过source ~/.bashrv
执行所有的系统命令,此后所有运行的command都相当于这个bash的子进程,因此需要用绝对路径开启虚拟环境source /home/xxx/anaconda3/bin/activate venv
。
对于开启GPU,它是通过环境变量来配置运行的,还是上述的问题,所以就作为临时的变量,使用export AAA=bbb
来配置。
【这部分对于bash的理解纯属个人观点,可能并不正确,欢迎讨论。】
取文件
由于我运行的程序会将结果写到文件中,所以类似put,再get回一个文件,逻辑和上传是一样的
ftp_client_down_re = ssh_client.open_sftp()
ftp_client_down_re.get(remote_path,local_path)
ftp_client_down_re.close()
也需要提前定义好local_path
和remote_path
。需要注意的是,必须写明文件名且两种必须一样,如下所示:
local_path = '/Users/xxx/xxx/1.txt'
remote_path = '/home/username/xxx/1.txt'
逻辑是开启连接–>下载–>关闭连接
最后在所有完成以后关闭ssh连接
ssh_client.close()
个人建议将这段代码写成一个函数进行调用。虽然不写大概率能运行成功,但是会出现它自己释放时候出问题,报错如下:
Exception ignored in: <object repr() failed>
Traceback (most recent call last):
File “/Users/xxx/xxx/lib/python3.5/site-packages/paramiko/file.py”, line 66, in del
File “/Users/xxx/xxx/lib/python3.5/site-packages/paramiko/file.py”, line 84, in close
File “/Users/xxx/xxx/lib/python3.5/site-packages/paramiko/file.py”, line 93, in flush
TypeError: ‘NoneType’ object is not callable
可能是因为sys.exit()的问题,把这个交给调用函数之后来完成应该不会出这个问题。以及如果多个command去exec,最后一个带着\n
也可能会出现这个问题。
完整代码
import paramiko
def fun():
hostname = 'xxx'
port = xxx
username='xxx'
password = 'xxxx'
#文件名务必一致
local_file = '/Users/xxx/xxxx/1.txt'
remote_file = '/home/xxx/xxx/1.txt'
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(hostname = hostname, port = port, username = username, password=password)
ftp_client_up = ssh_client.open_sftp()
ftp_client_up.put(local_file,remote_file)
ftp_client_up.close()
stdin,stdout,stderr=ssh_client.exec_command("xxx\n xxx\n xxxx")
# print(stderr.readlines())
print(stdout.readlines())
local_path = '/Users/xxx/xxx/result.txt'
remote_path = '/home/xxx/xxx/result.txt'
ftp_client_down_re = ssh_client.open_sftp()
ftp_client_down_re.get(remote_path,local_path)
ftp_client_down_re.close()
ssh_client.close()
fun()