Як скопіювати файл на віддалений сервер в 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()спочатку, якщо ви не використовуєте withблок), тож ви знаєте, що він завантажений на диск з Python.

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


3
@CharlesDuffy: subprocess.check_call(['scp', srcfile, dest])можна використовувати з Python 2.5 замістьrc = Popen(..).wait(); if rc != 0: raise ..
jfs

1
додавання ключа ssh не є хорошим рішенням, коли він не потрібен. Наприклад, якщо вам потрібно одноразове спілкування, ви не встановлюєте ssh ключ з міркувань безпеки.
орезвані

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), тому вам не потрібно відкривати запис та закривати кожен файл.
Джим Керролл

6
Я зауважу, що SFTP - це не те саме, що SCP.
Драхкар

4
@Drahkar питання запитує, щоб файл надсилався через SSH. Ось що це робить.
Тоні Мейер

8
Примітка: щоб зробити цю роботу поза полем, додайте ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())після інстанції ssh.
додано

1
Хлопці, чи безпечно використовувати пароль сервера? чи є можливість використовувати приватний ключ?
Айсенноуссі

32

Напевно, ви використовуєте модуль підпроцесу . Щось на зразок цього:

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

Де destination, мабуть, форма user@remotehost:remotepath. Завдяки @Charles Duffy за вказівку на слабкість моєї оригінальної відповіді, яка використовувала єдиний аргумент рядка для визначення операції scp shell=True- що не оброблятиме пробіли у шляхах.

Документація модуля містить приклади перевірки помилок, які ви, можливо, захочете виконати разом із цією операцією.

Переконайтесь, що ви налаштували належні облікові дані, щоб ви могли виконувати без нагляду безпардовий пошук між машинами . Для цього вже є питання про stackoverflow .


3
Використання підпроцесу. 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')

1
Примітка - це покладається на пакет scp. Станом на грудень 2017 року, останнє оновлення цього пакета було у 2015 році та номер версії - 0,1.x. Не впевнений, що хотів би додати цю залежність.
Чак

2
Отже, це вже 2018 рік, і в травні вони можуть випустити версію 0.11.0. Отже, здається, що SCPClient зрештою не мертвий?
Adriano_Pinaffo

5

Ви можете зробити щось подібне і для перевірки ключа хоста

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

4

fabric може використовуватися для завантаження файлів vis 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()

2

Можна скористатися васальним пакетом, який саме призначений для цього.

Все, що вам потрібно, це встановити васал і зробити

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

Потім у python:

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

Цей метод має перевагу в тому, що ви можете передавати дані, якщо ви генеруєте дані, а не кешуєте локально та надсилаєте один великий файл.


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

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)


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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.