Как скопировать файл на удаленный сервер на Python с помощью SCP или SSH?

У меня есть текстовый файл на моем локальном компьютере, который создается ежедневным сценарием Python, запускаемым в cron.

Я хотел бы добавить немного кода, чтобы этот файл безопасно отправлялся на мой сервер по SSH.


person Alok    schedule 16.09.2008    source источник


Ответы (14)


Вы можете вызвать команду bash scp (она копирует файлы поверх SSH) с _ 2_:

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

Если вы создаете файл, который хотите отправить, в той же программе Python, вам нужно вызвать команду subprocess.run за пределами блока with, который вы используете для открытия файла (или сначала вызовите .close() в файле, если вы без блока with), поэтому вы знаете, что он сбрасывается на диск из Python.

Вам необходимо заранее сгенерировать (на исходном компьютере) и установить (на конечном компьютере) ключ ssh, чтобы scp автоматически аутентифицировался с вашим открытым ключом ssh (другими словами, чтобы ваш скрипт не запрашивал пароль) .

person pdq    schedule 16.09.2008
comment
@CharlesDuffy: subprocess.check_call(['scp', srcfile, dest]) можно использовать с Python 2.5 вместо rc = Popen(..).wait(); if rc != 0: raise .. - person jfs; 28.03.2014
comment
Добавление ключа ssh не является хорошим решением, когда он не нужен. Например, если вам нужно одноразовое общение, вы не устанавливаете ключ ssh из соображений безопасности. - person orezvani; 16.11.2014

Чтобы сделать это в Python (т.е. не оборачивать scp через subprocess.Popen или подобное) с библиотекой Paramiko, вы сделал бы что-то вроде этого:

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

(Вы, вероятно, захотите иметь дело с неизвестными хостами, ошибками, созданием любых необходимых каталогов и т. Д.).

person Tony Meyer    schedule 16.09.2008
comment
paramiko также имеет красивую функцию sftp.put (self, localpath, remotepath, callback = None), поэтому вам не нужно открывать запись и закрывать каждый файл. - person Jim Carroll; 01.10.2009
comment
Замечу, что SFTP - это не то же самое, что SCP. - person Drahkar; 15.09.2012
comment
@Drahkar вопрос просит пересылку файла по SSH. Вот что это значит. - person Tony Meyer; 21.09.2012
comment
Примечание: чтобы сделать эту работу нестандартной, добавьте ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) после создания экземпляра ssh. - person doda; 09.10.2012
comment
@doda, только если вы не хотите использовать файл known_hosts - person Tom; 20.09.2013
comment
Есть идеи для решения на основе scp? SFTP не работает с нашим сервером. - person lpapp; 10.12.2013
comment
Ребята, безопасно ли использовать пароль сервера? есть ли возможность использовать закрытый ключ? - person Aysennoussi; 31.12.2014
comment
Конечно, можно использовать закрытый ключ: gist.github.com/batok/2352501 - person Joel B; 24.04.2015
comment
Интересно, что этот ответ набрал почти втрое больше голосов, чем принятый. - person WinEunuuchs2Unix; 10.08.2020

Вероятно, вы использовали бы модуль подпроцесса. Что-то вроде этого:

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

Где destination, вероятно, имеет форму user@remotehost:remotepath. Спасибо @Charles Duffy за указание на слабость в моем исходном ответе, в котором для указания операции scp shell=True использовался единственный строковый аргумент, который не обрабатывал пробелы в путях.

В документации модуля есть примеры проверки ошибок, которые вы, возможно, захотите выполнить вместе с этой операцией.

Убедитесь, что вы настроили правильные учетные данные, чтобы можно было выполнять автоматическую передачу данных без пароля между машинами. Для этого уже существует вопрос о stackoverflow.

person Blair Conrad    schedule 16.09.2008
comment
Использование subprocess.Popen - правильная вещь. Передача ему строки, а не массива (и использование shell = True) является неправильным, поскольку это означает, что имена файлов с пробелами работают неправильно. - person Charles Duffy; 15.10.2008
comment
вместо sts = os.waitpid (p.pid, 0) мы можем использовать sts = p.wait (). - person db42; 18.12.2011
comment
есть ли способ без выполнения с прямым API Python для этого протокола? - person lpapp; 10.12.2013
comment
subprocess.check_call(['scp', myfile, destination]) можно было бы использовать вместо этого, начиная с Python 2.5 (2006 г.) - person jfs; 28.03.2014
comment
@ J.F.Sebastian: да, я проверял это в декабре. По крайней мере, мои голоса подтверждают это. :) Спасибо за продолжение. - person lpapp; 28.03.2014

Есть несколько разных подходов к решению проблемы:

  1. Обернуть программы командной строки
  2. используйте библиотеку Python, которая предоставляет возможности SSH (например, Paramiko или Витая раковина)

У каждого подхода есть свои особенности. Вам нужно будет настроить ключи SSH, чтобы разрешить вход без пароля, если вы обертываете системные команды, такие как «ssh», «scp» или «rsync». Вы можете встроить пароль в скрипт с помощью Paramiko или какой-либо другой библиотеки, но отсутствие документации может вас расстроить, особенно если вы не знакомы с основами SSH-соединения (например, обмен ключами, агенты и т. Д.). Само собой разумеется, что ключи SSH почти всегда лучше, чем пароли для такого рода вещей.

ПРИМЕЧАНИЕ: его сложно превзойти rsync, если вы планируете передавать файлы через SSH, особенно если альтернативой является старый добрый scp.

Я использовал Paramiko с прицелом на замену системных вызовов, но обнаружил, что меня снова привлекают завернутые команды из-за их простоты использования и непосредственного знакомства. Ты мог бы быть другим. Некоторое время назад я осмотрел Конча, но мне это не понравилось.

Если вы выбираете путь системного вызова, Python предлагает массив параметров, таких как os.system или модули команд / подпроцесса. Я бы пошел с модулем подпроцесса, если использую версию 2.4+.

person Community    schedule 16.09.2008
comment
Любопытно: как обстоят дела с rsync и scp? - person Alok; 16.09.2008

Достигнута та же проблема, но вместо "взлома" или эмуляции командной строки:

Нашел этот ответ здесь.

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')
person Maviles    schedule 24.07.2016
comment
Примечание. Это зависит от пакета scp. По состоянию на декабрь 2017 г. последнее обновление этого пакета было в 2015 г., а номер версии - 0.1.x. Не уверен, что хочу добавить эту зависимость. - person Chuck; 10.12.2017
comment
Итак, сейчас 2018 год, и в мае была выпущена версия 0.11.0. Итак, кажется, что SCPClient все-таки не мертв? - person Adriano_Pinaffo; 02.08.2018
comment
модуль scp регулярно обновляется (последнее было в июне 2021 года) и имеет 380 звезд на github. это законное решение. - person volkit; 09.06.2021

Вы можете сделать что-то вроде этого, чтобы обрабатывать проверку ключа хоста

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

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()
person jfs    schedule 28.03.2014

Вы можете использовать пакет vassal, который как раз для этого предназначен.

Все, что вам нужно, это установить вассала и сделать

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

Кроме того, это избавит вас от аутентификации учетных данных, и вам не придется вводить их снова и снова.

person Shawn    schedule 16.11.2018

Я использовал sshfs для монтирования удаленного каталога через ssh и shutil, чтобы скопировать файлы:

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

Затем в питоне:

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

Этот метод имеет то преимущество, что вы можете передавать данные в потоке, если генерируете данные, а не кэшируете локально и отправляете один большой файл.

person Jonno_FTW    schedule 06.02.2018

Попробуйте это, если вы не хотите использовать сертификаты 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!')
person JavDomGom    schedule 27.11.2018

Использование внешнего ресурса 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')
person michael    schedule 12.07.2017

Вызов команды 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)

person jfs    schedule 28.03.2014

Очень простой подход заключается в следующем:

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

Библиотеки python не требуются (только os), и она работает, однако использование этого метода требует установки другого клиента ssh. Это может привести к нежелательному поведению при запуске в другой системе.

person Roberto Marzocchi    schedule 04.01.2018

Вроде хаки, но должно работать следующее :)

import os
filePath = "/foo/bar/baz.py"
serverPath = "/blah/boo/boom.py"
os.system("scp "+filePath+" [email protected]:"+serverPath)
person Drew Olson    schedule 16.09.2008