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

103

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

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

Алок
источник
1
нашел что-то похожее, можете ли вы посмотреть stackoverflow.com/questions/11009308/…
JJ84

Ответы:

55

Вы можете вызвать команду scpbash (она копирует файлы через 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, вам нужно вызвать subprocess.runкоманду вне withблока, который вы используете для открытия файла (или .close()сначала вызвать файл, если вы не используете withblock), чтобы вы знали, что он сброшен на диск из Python.

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

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

Чтобы сделать это в 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()

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

Тони Мейер
источник
14
paramiko также имеет красивую функцию sftp.put (self, localpath, remotepath, callback = None), поэтому вам не нужно открывать запись и закрывать каждый файл.
Джим Кэрролл,
7
Замечу, что SFTP - это не то же самое, что SCP.
Drahkar
4
@Drahkar вопрос просит пересылку файла по SSH. Вот что это значит.
Тони Мейер
8
Примечание: чтобы эта работа работала из коробки, добавьте ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())после создания экземпляра ssh.
doda
1
Ребята, безопасно ли использовать пароль сервера? есть ли возможность использовать закрытый ключ?
Aysennoussi
32

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

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

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

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

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

Блэр Конрад
источник
3
Использование subprocess.Popen - это правильная вещь. Передача ему строки, а не массива (и использование shell = True) является неправильным, поскольку это означает, что имена файлов с пробелами работают неправильно.
Чарльз Даффи,
2
вместо sts = os.waitpid (p.pid, 0) мы можем использовать sts = p.wait ().
db42
есть ли способ без выполнения с прямым API Python для этого протокола?
lpapp
1
subprocess.check_call(['scp', myfile, destination])можно было бы использовать вместо этого, начиная с Python 2.5 (2006)
jfs
@JFSebastian: да, проверял в декабре. По крайней мере, мои голоса подтверждают это. :) Спасибо за продолжение.
lpapp
12

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

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

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

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

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

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


источник
1
Любопытно: какова история rsync vs. scp?
Алок,
7

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

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

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')
Maviles
источник
1
Примечание. Это зависит от пакета scp. По состоянию на декабрь 2017 г. последнее обновление этого пакета было в 2015 г., а номер версии - 0.1.x. Не уверен, что хочу добавить эту зависимость.
Чак
2
Итак, сейчас 2018 год, и в мае они выпустили версию 0.11.0. Итак, кажется, SCPClient все-таки не мертв?
Adriano_Pinaffo 02
5

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

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()
jfs
источник
2

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

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

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

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

Шон
источник
1

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

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

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

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

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

Jonno_FTW
источник
1

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

Использование внешнего ресурса 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')
Майкл
источник
0

Вызов 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)

jfs
источник
0

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

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

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

Роберто Марзокки
источник
-3

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

import os
filePath = "/foo/bar/baz.py"
serverPath = "/blah/boo/boom.py"
os.system("scp "+filePath+" user@myserver.com:"+serverPath)
Дрю Олсон
источник