Jak skopiować plik na zdalny serwer w Pythonie za pomocą SCP lub SSH?

103

Mam plik tekstowy na mojej maszynie lokalnej, który jest generowany przez codzienny skrypt Pythona uruchamiany w cronie.

Chciałbym dodać trochę kodu, aby ten plik był bezpiecznie przesyłany na mój serwer przez SSH.

Alok
źródło
1
znalazłem coś podobnego, czy możesz wyglądać stackoverflow.com/questions/11009308/ ...
JJ84

Odpowiedzi:

55

Możesz wywołać scppolecenie bash (kopiuje pliki przez SSH ) za pomocą subprocess.run:

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

Jeśli tworzysz plik, który chcesz wysłać w tym samym programie w języku Python, będziesz chciał wywołać subprocess.runpolecenie poza withblokiem, którego używasz do otwarcia pliku (lub .close()najpierw wywołaj plik, jeśli nie używasz withblock), więc wiesz, że jest on opróżniany na dysk z Pythona.

Musisz wcześniej wygenerować (na maszynie źródłowej) i zainstalować (na maszynie docelowej) klucz ssh, aby scp został automatycznie uwierzytelniony za pomocą twojego publicznego klucza ssh (innymi słowy, aby twój skrypt nie pytał o hasło) .

pdq
źródło
3
@CharlesDuffy: subprocess.check_call(['scp', srcfile, dest])może być używany od Pythona 2.5 zamiastrc = Popen(..).wait(); if rc != 0: raise ..
jfs
1
dodanie klucza ssh nie jest dobrym rozwiązaniem, gdy nie jest potrzebny. Na przykład, jeśli potrzebujesz jednorazowej komunikacji, nie konfigurujesz klucza ssh ze względów bezpieczeństwa.
orezvani
150

Aby to zrobić w Pythonie (tj. Nie owijając scp przez subprocess.Popen lub podobną) z biblioteką Paramiko , zrobiłbyś coś takiego:

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

(Prawdopodobnie chciałbyś poradzić sobie z nieznanymi hostami, błędami, utworzeniem wszelkich niezbędnych katalogów i tak dalej).

Tony Meyer
źródło
14
paramiko ma również fajną funkcję sftp.put (self, localpath, remotepath, callback = None), więc nie musisz otwierać i zamykać każdego pliku.
Jim Carroll
6
Chciałbym zauważyć, że SFTP to nie to samo, co SCP.
Drahkar
4
@Drahkar pytanie prosi o przesłanie pliku przez SSH. To właśnie to robi.
Tony Meyer,
8
Uwaga: aby to działało po wyjęciu z pudełka, dodaj ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())po utworzeniu wystąpienia ssh.
doda
1
Chłopaki, czy używanie hasła serwera jest bezpieczne? czy istnieje możliwość wykorzystania klucza prywatnego?
Aysennoussi
32

Prawdopodobnie użyłbyś modułu podprocesu . Coś takiego:

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

Gdzie destinationprawdopodobnie jest forma user@remotehost:remotepath. Podziękowania dla @Charles Duffy za wskazanie słabości mojej pierwotnej odpowiedzi, która użyła pojedynczego argumentu ciągu do określenia operacji scp shell=True- która nie obsługuje białych znaków w ścieżkach.

Dokumentacja modułu zawiera przykłady sprawdzania błędów, które możesz chcieć wykonać w połączeniu z tą operacją.

Upewnij się, że skonfigurowano odpowiednie poświadczenia, aby można było wykonać nienadzorowany, bezhasłowy scp między komputerami . Jest już pytanie o przepełnienie stosu .

Blair Conrad
źródło
3
Korzystanie z subprocess.Popen jest właściwą rzeczą. Przekazanie mu ciągu znaków zamiast tablicy (i użycie powłoki = True) jest złą rzeczą, ponieważ oznacza, że ​​nazwy plików ze spacjami nie działają poprawnie.
Charles Duffy,
2
zamiast „sts = os.waitpid (p.pid, 0)” możemy użyć „sts = p.wait ()”.
db42
czy istnieje sposób na to bez wykonywania z bezpośrednim interfejsem API języka Python dla tego protokołu?
lpapp
1
subprocess.check_call(['scp', myfile, destination])może być używany zamiast tego od Pythona 2.5 (2006)
jfs
@JFSebastian: tak, sprawdziłem to w grudniu. Moje głosy poparcia przynajmniej to potwierdzają. :) Dziękuję jednak za kontynuację.
lpapp
12

Istnieje kilka różnych sposobów podejścia do problemu:

  1. Pakuj programy wiersza poleceń
  2. użyj biblioteki Python, która zapewnia możliwości SSH (np. - Paramiko lub Twisted Conch )

Każde podejście ma swoje dziwactwa. Będziesz musiał skonfigurować klucze SSH, aby włączyć logowanie bez hasła, jeśli pakujesz polecenia systemowe, takie jak „ssh”, „scp” lub „rsync”. Możesz osadzić hasło w skrypcie za pomocą Paramiko lub innej biblioteki, ale brak dokumentacji może być frustrujący, szczególnie jeśli nie znasz podstaw połączenia SSH (np. - wymiana kluczy, agenci itp.). Prawdopodobnie jest oczywiste, że klucze SSH są prawie zawsze lepszym pomysłem niż hasła do tego rodzaju rzeczy.

UWAGA: trudno jest pokonać rsync, jeśli planujesz przesyłać pliki przez SSH, zwłaszcza jeśli alternatywą jest zwykły stary scp.

Użyłem Paramiko z zamiarem zastąpienia wywołań systemowych, ale wróciłem do opakowanych poleceń ze względu na ich łatwość użycia i natychmiastową znajomość. Możesz być inny. Dałem Conch raz jeszcze jakiś czas temu, ale nie przemówiło to do mnie.

Jeśli zdecydujesz się na ścieżkę wywołań systemowych, Python oferuje tablicę opcji, takich jak os.system lub moduły commands / subprocess. Wybrałbym moduł podprocesu, jeśli używasz wersji 2.4+.


źródło
1
ciekawy: jaka jest historia o rsync vs. scp?
Alok
7

Napotkano ten sam problem, ale zamiast „hakowania” lub emulacji wiersza poleceń:

Znalazłem tę odpowiedź tutaj .

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
źródło
1
Uwaga - to zależy od pakietu scp. Od grudnia 2017 r. Najnowsza aktualizacja tego pakietu miała miejsce w 2015 r., A numer wersji to 0.1.x. Nie jestem pewien, czy chciałbym dodać tę zależność.
Chuck
2
więc teraz jest rok 2018, aw maju wydali wersję 0.11.0. Więc wygląda na to, że SCPClient wcale nie jest martwy?
Adriano_Pinaffo
5

Możesz zrobić coś takiego, aby obsłużyć również sprawdzanie klucza hosta

import os
os.system("sshpass -p password scp -o StrictHostKeyChecking=no local_file_path username@hostname:remote_path")
Pradeep Pathak
źródło
4

fabric może zostać użyty do przesłania plików 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()
jfs
źródło
2

Możesz użyć pakietu wasala, który jest dokładnie do tego przeznaczony.

Wszystko, czego potrzebujesz, to zainstalować wasala i zrobić

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

Ponadto zapisze ci dane uwierzytelniające i nie musisz ich wpisywać wielokrotnie.

Shawn
źródło
1

Użyłem sshfs do zamontowania zdalnego katalogu przez ssh i shutil do skopiowania plików:

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

Następnie w Pythonie:

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

Ta metoda ma tę zaletę, że możesz przesyłać strumieniowo dane, jeśli generujesz dane zamiast buforować lokalnie i wysyłać pojedynczy duży plik.

Jonno_FTW
źródło
1

Spróbuj tego, jeśli nie chcesz używać certyfikatów 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
źródło
1

Korzystanie z zewnętrznego zasobu 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')
Michael
źródło
0

Wywołanie scppolecenia za pośrednictwem podprocesu nie pozwala na otrzymanie raportu postępu wewnątrz skryptu. pexpectmożna użyć do wyodrębnienia tych informacji:

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

Zobacz plik kopii Pythona w sieci lokalnej (linux -> linux)

jfs
źródło
0

Bardzo proste podejście jest następujące:

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

Żadna biblioteka Pythona nie jest wymagana (tylko system operacyjny) i działa, jednak użycie tej metody wymaga zainstalowania innego klienta ssh. Może to spowodować niepożądane zachowanie, jeśli zostanie uruchomione w innym systemie.

Roberto Marzocchi
źródło
-3

Trochę hacky, ale poniższe powinny działać :)

import os
filePath = "/foo/bar/baz.py"
serverPath = "/blah/boo/boom.py"
os.system("scp "+filePath+" [email protected]:"+serverPath)
Drew Olson
źródło