Come copiare un file su un server remoto in Python usando SCP o SSH?


103

Ho un file di testo sulla mia macchina locale che viene generato da uno script Python giornaliero eseguito in cron.

Vorrei aggiungere un po 'di codice per inviare il file in modo sicuro al mio server tramite SSH.


1
trovato qualcosa di simile, u può avere uno sguardo stackoverflow.com/questions/11009308/...
JJ84

Risposte:


55

Puoi chiamare il scpcomando bash (copia i file su SSH ) con 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"])

Se stai creando il file che desideri inviare nello stesso programma Python, ti consigliamo di chiamare il subprocess.runcomando al di fuori del withblocco che stai utilizzando per aprire il file (o chiamare .close()prima il file se non stai utilizzando un withblock), quindi sai che è scaricato su disco da Python.

Devi generare (sulla macchina di origine) e installare (sulla macchina di destinazione) una chiave ssh in anticipo in modo che scp venga automaticamente autenticato con la tua chiave ssh pubblica (in altre parole, in modo che il tuo script non richieda una password) .


3
@CharlesDuffy: subprocess.check_call(['scp', srcfile, dest])potrebbe essere utilizzato da Python 2.5 invece dirc = Popen(..).wait(); if rc != 0: raise ..
jfs

1
l'aggiunta della chiave ssh non è una buona soluzione quando non è necessaria. Ad esempio, se è necessaria una comunicazione una tantum, non si configura la chiave ssh per motivi di sicurezza.
orezvani

150

Per fare questo in Python (cioè non avvolgere scp attraverso subprocess.Popen o simili) con la libreria Paramiko , dovresti fare qualcosa del genere:

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

(Probabilmente vorresti occuparti di host sconosciuti, errori, creare le directory necessarie e così via).


14
paramiko ha anche una bella funzione sftp.put (self, localpath, remotepath, callback = None), quindi non devi aprire la scrittura e chiudere ogni file.
Jim Carroll

6
Vorrei notare che SFTP non è la stessa cosa di SCP.
Drahkar

4
@Drahkar la domanda chiede che il file venga inviato tramite SSH. Questo è ciò che fa.
Tony Meyer

8
Nota: per fare in modo che funzioni subito, aggiungi ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())dopo l'istanza ssh.
doda

1
Ragazzi, è sicuro usare la password del server? c'è la possibilità di utilizzare la chiave privata?
Aysennoussi

32

Probabilmente useresti il ​​modulo subprocess . Qualcosa come questo:

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

Dov'è destinationprobabilmente della forma user@remotehost:remotepath. Grazie a @Charles Duffy per aver sottolineato la debolezza nella mia risposta originale, che utilizzava un singolo argomento di stringa per specificare l'operazione scpshell=True che non avrebbe gestito gli spazi bianchi nei percorsi.

La documentazione del modulo ha esempi di controllo degli errori che potresti voler eseguire insieme a questa operazione.

Assicurati di aver impostato le credenziali appropriate in modo da poter eseguire uno scp non assistito e senza password tra le macchine . C'è già una domanda di stackoverflow per questo .


3
Usare subprocess.Popen è la cosa giusta. Passare una stringa anziché un array (e usare shell = True) è la cosa sbagliata, poiché significa che i nomi di file con spazi non funzionano correttamente.
Charles Duffy

2
invece di "sts = os.waitpid (p.pid, 0)", possiamo usare "sts = p.wait ()".
db42

esiste un modo libero di esecuzione con API python diretta per questo protocollo?
lpapp

1
subprocess.check_call(['scp', myfile, destination])potrebbe essere usato invece da Python 2.5 (2006)
jfs

@ JFSebastian: sì, l'ho verificato a dicembre. I miei voti positivi lo dimostrano, almeno. :) Grazie per il follow-up però.
lpapp

12

Ci sono un paio di modi diversi per affrontare il problema:

  1. Eseguire il wrapping dei programmi della riga di comando
  2. utilizzare una libreria Python che fornisce funzionalità SSH (ad esempio - Paramiko o Twisted Conch )

Ogni approccio ha le sue peculiarità. Dovrai configurare le chiavi SSH per abilitare gli accessi senza password se stai eseguendo il wrapping di comandi di sistema come "ssh", "scp" o "rsync". Puoi incorporare una password in uno script usando Paramiko o qualche altra libreria, ma potresti trovare frustrante la mancanza di documentazione, specialmente se non hai familiarità con le basi della connessione SSH (es. Scambi di chiavi, agenti, ecc.). Probabilmente è ovvio che le chiavi SSH sono quasi sempre un'idea migliore delle password per questo genere di cose.

NOTA: è difficile battere rsync se prevedi di trasferire file tramite SSH, specialmente se l'alternativa è semplicemente il vecchio scp.

Ho usato Paramiko con l'obiettivo di sostituire le chiamate di sistema, ma mi sono ritrovato attratto dai comandi impacchettati grazie alla loro facilità d'uso e familiarità immediata. Potresti essere diverso. Ho dato a Conch una riprova qualche tempo fa, ma non mi piaceva.

Se si opta per il percorso della chiamata di sistema, Python offre una serie di opzioni come os.system oi moduli di comandi / sottoprocesso. Vorrei andare con il modulo sottoprocesso se si utilizza la versione 2.4+.


1
curioso: qual è la storia su rsync e scp?
Alok

7

Hai raggiunto lo stesso problema, ma invece di "hackerare" o emulare la riga di comando:

Ho trovato questa risposta qui .

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
Nota: questo si basa sul pacchetto scp. A partire da dicembre 2017, l'ultimo aggiornamento di quel pacchetto era nel 2015 e il numero di versione è 0.1.x. Non sono sicuro di voler aggiungere questa dipendenza.
Chuck

2
quindi, è il 2018 ora ea maggio è stata rilasciata la versione 0.11.0. Quindi, sembra che SCPClient non sia morto dopo tutto?
Adriano_Pinaffo

5

Puoi fare qualcosa del genere, per gestire anche il controllo della chiave host

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

4

fabric potrebbe essere utilizzato per caricare file 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

Puoi usare il pacchetto vassallo, che è esattamente progettato per questo.

Tutto ciò di cui hai bisogno è installare vassallo e farlo

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

Inoltre, ti salverà le credenziali di autenticazione e non sarà necessario digitarle ancora e ancora.


1

Ho usato sshfs per montare la directory remota tramite ssh e shutil per copiare i file:

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

Quindi in Python:

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

Questo metodo ha il vantaggio di poter trasmettere dati in streaming se si generano dati anziché memorizzarli nella cache locale e inviare un singolo file di grandi dimensioni.


1

Prova questo se non vuoi utilizzare i certificati 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

Utilizzando la risorsa esterna 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

La chiamata al scpcomando tramite sottoprocesso non consente di ricevere il rapporto sullo stato di avanzamento all'interno dello script. pexpectpotrebbe essere utilizzato per estrarre tali informazioni:

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

Vedi il file di copia python nella rete locale (linux -> linux)


0

Un approccio molto semplice è il seguente:

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

Non è richiesta alcuna libreria python (solo os) e funziona, tuttavia l'utilizzo di questo metodo si basa su un altro client ssh da installare. Ciò potrebbe causare un comportamento indesiderato se eseguito su un altro sistema.


-3

Un po 'hacky, ma quanto segue dovrebbe funzionare :)

import os
filePath = "/foo/bar/baz.py"
serverPath = "/blah/boo/boom.py"
os.system("scp "+filePath+" user@myserver.com:"+serverPath)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.