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

我的本地机器上有一个文本文件,它是由运行在 cron 中的每日 Python 脚本生成的。

我想添加一点代码,让该文件通过 SSH 安全地发送到我的服务器。

354718 次浏览

您可以使用 subprocess.run调用 scp bash 命令(它通过 SSH复制文件) :

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 密钥进行身份验证(换句话说,这样您的脚本就不会询问密码)。

有点粗俗,不过下面这些应该行得通:)

import os
filePath = "/foo/bar/baz.py"
serverPath = "/blah/boo/boom.py"
os.system("scp "+filePath+" user@myserver.com:"+serverPath)

你可能会使用 子进程模块子进程模块,比如:

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

其中 destination的形式可能是 user@remotehost:remotepath @ Charles Duffy 指出了我最初答案中的弱点,它使用了一个字符串参数来指定 scp 操作 shell=True-这不能处理路径中的空格。

模块文档有 您可能希望与此操作一起执行的错误检查的示例。

确保您已经设置了正确的凭据,以便您可以执行 机器之间无人值守、无密码的 scp

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

  1. 包装命令行程序
  2. 使用提供 SSH 功能的 Python 库(如 帕拉米科螺旋海螺)

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

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

我曾经使用 Paramiko 来替换系统调用,但是发现自己又回到了包装好的命令,因为它们易于使用,而且非常熟悉。你可能不一样。前段时间我给海螺看了一遍,但它对我没有吸引力。

如果选择系统调用路径,Python 提供了一系列选项,比如 os.system 或者命令/子进程模块。如果使用版本2.4 + ,我会使用子流程模块。

在 Python 中执行此操作(即不通过 subprocess 包装 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()

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

可以使用 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()

通过子进程调用 scp命令不允许在脚本中接收进度报告。pexpect可以用来提取信息:

import pipes
import re
import pexpect # $ pip install pexpect


def progress(locals):
# extract percents
print(int(re.search(br'(\d+)%$', locals['child'].after).group(1)))


command = "scp %s %s" % tuple(map(pipes.quote, [srcfile, destination]))
pexpect.run(command, events={r'\d+%': progress})

参见 本地网络中的 python 复制文件(linux-> linux)

达到了同样的问题,但是没有“黑客攻击”或者模拟命令行:

找到了这个答案。

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')

利用外部资源;

    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')

一个非常简单的方法如下:

import os
os.system('sshpass -p "password" scp user@host:/path/to/file ./')

不需要任何 python 库(仅操作系统) ,并且它可以工作,但是使用此方法需要安装另一个 ssh 客户机。如果在另一个系统上运行,这可能导致不希望出现的行为。

我使用 Sshfs通过 ssh 挂载远程目录,使用 Shutil复制文件:

$ mkdir ~/sshmount
$ sshfs user@remotehost:/path/to/remote/dst ~/sshmount

然后是巨蟒:

import shutil
shutil.copy('a.txt', '~/sshmount')

这种方法的优点是,如果生成的是数据,而不是本地缓存和发送单个大文件,则可以通过该方法传输数据。

你可以使用附庸包,这正是为此而设计的。

你所需要的只是安装附庸和做

from vassal.terminal import Terminal
shell = Terminal(["scp username@host:/home/foo.txt foo_local.txt"])
shell.run()

此外,它将保存您的身份验证凭证,不需要键入他们一遍又一遍。

如果您不想使用 SSL 证书,请尝试这样做:

import subprocess


try:
# Set scp and ssh data.
connUser = 'john'
connHost = 'my.host.com'
connPath = '/home/john/'
connPrivateKey = '/home/user/myKey.pem'


# Use scp to send file from local to host.
scp = subprocess.Popen(['scp', '-i', connPrivateKey, 'myFile.txt', '{}@{}:{}'.format(connUser, connHost, connPath)])


except CalledProcessError:
print('ERROR: Connection to host failed!')

您也可以这样做,以处理主机密钥检查

import os
os.system("sshpass -p password scp -o StrictHostKeyChecking=no local_file_path username@hostname:remote_path")