如何使用SCP或SSH在Python中将文件复制到远程服务器?

144
我有一个文本文件存在本地机器上,该文件是由每日运行在cron中的Python脚本生成的。
我想添加一些代码,将该文件通过SSH安全地发送到我的服务器上。
14个回答

183

使用Paramiko库在Python中实现此功能(即不通过subprocess.Popen或类似方式包装scp),您可以像这样操作:

import os
import paramiko

ssh = paramiko.SSHClient() 
ssh.load_host_keys(os.path.expanduser(os.path.join("~", ".ssh", "known_hosts")))
ssh.connect(server, username=username, password=password)
sftp = ssh.open_sftp()
sftp.put(localpath, remotepath)
sftp.close()
ssh.close()

(您可能需要处理未知主机、错误、创建必要的任何目录等问题。)


16
Paramiko库也提供了一个很好的sftp.put(self, localpath, remotepath, callback=None)函数,这样你就不需要打开、写入和关闭每个文件了。 - Jim Carroll
7
请注意,SFTP不同于SCP。 - Drahkar
5
问题要求通过SSH发送文件,这就是这个操作所做的。 - Tony Meyer
10
注意:为了让这个程序可以直接使用,请在实例化“ssh”之后添加“ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())”代码。 - doda
2
大家好,使用服务器密码安全吗?有可能使用私钥吗? - Aysennoussi
显示剩余7条评论

75

您可以使用scp bash命令(它在SSH下复制文件)与subprocess.run一起调用:

import subprocess
subprocess.run(["scp", FILE, "USER@SERVER:PATH"])
#e.g. subprocess.run(["scp", "foo.bar", "joe@srvr.net:/path/to/foo.bar"])

如果您正在创建想要在同一Python程序中发送的文件,则需要在打开该文件的with块之外调用subprocess.run命令(或者如果您没有使用with块,则先调用.close()关闭文件),以便您知道它已从Python刷新到磁盘。

您需要事先在源计算机上生成并安装一个ssh密钥(在目标计算机上),以便scp自动使用您的公共ssh密钥进行身份验证(换句话说,让您的脚本不会要求密码)。


3
@CharlesDuffy:自Python 2.5起,可以使用subprocess.check_call(['scp', srcfile, dest])代替rc = Popen(..).wait(); if rc != 0: raise .. - jfs
1
添加SSH密钥并不是一个好的解决方案,当它不需要时。例如,如果您只需要一次性通信,出于安全原因,您不会设置SSH密钥。 - orezvani

34

你可能会使用 subprocess模块。类似这样:

import subprocess
p = subprocess.Popen(["scp", myfile, destination])
sts = os.waitpid(p.pid, 0)

其中destination可能是形如user@remotehost:remotepath的格式。感谢@Charles Duffy指出我原来的答案存在的漏洞,该答案使用了一个单字符串参数来指定scp操作shell=True - 无法处理路径中的空格。

此模块文档中提供了与此操作一起执行可能需要的错误检查示例

确保您已经设置了适当的凭据,以便在机器之间执行无人值守、无需密码的scp。关于此问题,已经有一个stackoverflow问题了。


3
使用subprocess.Popen是正确的方法。如果传递一个字符串而不是一个数组(并且使用shell=True),那么这是错误的做法,因为带有空格的文件名将无法正常工作。 - Charles Duffy
2
我们可以使用"sts = p.wait()"代替"sts = os.waitpid(p.pid, 0)"。 - db42
有没有一种不需要执行的方式,可以使用直接的Python API来实现这个协议? - László Papp
1
自从 Python 2.5(2006年)以来,可以使用 subprocess.check_call(['scp', myfile, destination]) 来代替。 - jfs
@J.F.Sebastian:没错,我在去年十二月份就已经检查过了。我的点赞证明了这一点。:) 不过还是谢谢你的跟进。 - László Papp

16

遇到了同样的问题,但没有使用“破解”或仿真命令行:

在这里找到了答案here

from paramiko import SSHClient
from scp import SCPClient

ssh = SSHClient()
ssh.load_system_host_keys()
ssh.connect('example.com')

with SCPClient(ssh.get_transport()) as scp:
    scp.put('test.txt', 'test2.txt')
    scp.get('test2.txt')

1
请注意 - 这取决于scp软件包。截至2017年12月,该软件包的最新更新日期为2015年,版本号为0.1.x。我不确定是否想要添加此依赖项。 - Chuck
2
所以,现在是2018年,在五月份他们发布了0.11.0版本。看来SCPClient并没有死吧? - Adriano_Pinaffo
SCP模块定期更新(最近一次更新是在2021年6月),在Github上获得了380颗星。这是一个合法的解决方案。 - volkit

15

有几种不同的方法可以解决这个问题:

  1. 包装命令行程序
  2. 使用提供SSH功能的Python库(例如- ParamikoTwisted Conch

每种方法都有其自己的特点。如果您正在包装系统命令(如“ssh”,“scp”或“rsync”),则需要设置SSH密钥以启用无密码登录。您可以使用Paramiko或其他库在脚本中嵌入密码,但是如果您不熟悉SSH连接的基础知识(例如-密钥交换、代理等),您可能会发现缺乏文档非常令人沮丧。 SSH密钥几乎总是比密码更好用于此类事情。

注意:如果您计划通过SSH传输文件,特别是如果替代方案是普通的scp,则很难击败rsync。

我曾经试用过Paramiko,希望能够取代系统调用,但由于其易用性和熟悉度,我最终还是回到了封装命令。也许你会有不同的选择。我之前也尝试过Conch,但并不适合我。

如果选择使用系统调用路径,Python提供了多种选项,例如os.system或commands/subprocess模块。如果使用2.4+版本,我会选择subprocess模块。


1
好奇:rsync和scp之间的故事是什么? - Alok

6
你可以像这样做,来处理主机密钥检查。
import os
os.system("sshpass -p password scp -o StrictHostKeyChecking=no local_file_path username@hostname:remote_path")

4

Fabric可以通过ssh上传文件:

#!/usr/bin/env python
from fabric.api import execute, put
from fabric.network import disconnect_all

if __name__=="__main__":
    import sys
    # specify hostname to connect to and the remote/local paths
    srcdir, remote_dirname, hostname = sys.argv[1:]
    try:
        s = execute(put, srcdir, remote_dirname, host=hostname)
        print(repr(s))
    finally:
        disconnect_all()

3
一个非常简单的方法如下:
import os
os.system('sshpass -p "password" scp user@host:/path/to/file ./')

没有需要的Python库(只需要os),它可以工作,但是使用此方法依赖于另一个已安装的SSH客户端。如果在另一个系统上运行,这可能会导致不良行为。

2
您可以使用专门为此设计的vassal包。
您只需要安装vassal并执行以下操作:
from vassal.terminal import Terminal
shell = Terminal(["scp username@host:/home/foo.txt foo_local.txt"])
shell.run()

此外,它将保存您的身份验证凭据,无需再次输入。

2

使用外部资源 paramiko;

    from paramiko import SSHClient
    from scp import SCPClient
    import os

    ssh = SSHClient() 
    ssh.load_host_keys(os.path.expanduser(os.path.join("~", ".ssh", "known_hosts")))
    ssh.connect(server, username='username', password='password')
    with SCPClient(ssh.get_transport()) as scp:
            scp.put('test.txt', 'test2.txt')

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接