Comment copier un fichier sur un serveur distant en Python en utilisant SCP ou SSH?

103

J'ai un fichier texte sur ma machine locale qui est généré par un script Python quotidien exécuté dans cron.

Je voudrais ajouter un peu de code pour que ce fichier soit envoyé en toute sécurité à mon serveur via SSH.

Alok
la source
1
trouvé quelque chose de similaire, pouvez-vous jeter un oeil à stackoverflow.com/questions/11009308/…
JJ84

Réponses:

55

Vous pouvez appeler la scpcommande bash (elle copie les fichiers via SSH ) avec subprocess.run:

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

Si vous créez le fichier que vous souhaitez envoyer dans le même programme Python, vous voudrez appeler la subprocess.runcommande en dehors du withbloc que vous utilisez pour ouvrir le fichier (ou appeler .close()d'abord le fichier si vous n'utilisez pas de withblock), vous savez donc qu'il est vidé sur le disque à partir de Python.

Vous devez générer (sur la machine source) et installer (sur la machine de destination) une clé ssh au préalable afin que le scp soit automatiquement authentifié avec votre clé publique ssh (en d'autres termes, votre script ne demande pas de mot de passe) .

pdq
la source
3
@CharlesDuffy: subprocess.check_call(['scp', srcfile, dest])pourrait être utilisé depuis Python 2.5 au lieu derc = Popen(..).wait(); if rc != 0: raise ..
jfs
1
l'ajout d'une clé ssh n'est pas une bonne solution lorsqu'elle n'est pas nécessaire. Par exemple, si vous avez besoin d'une communication ponctuelle, vous ne configurez pas la clé ssh pour des raisons de sécurité.
orezvani
150

Pour faire cela en Python (c'est-à-dire ne pas encapsuler scp via subprocess.Popen ou similaire) avec la bibliothèque Paramiko , vous feriez quelque chose comme ceci:

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

(Vous voudrez probablement traiter des hôtes inconnus, des erreurs, la création de tous les répertoires nécessaires, etc.).

Tony Meyer
la source
14
paramiko a également une fonction sftp.put (self, localpath, remotepath, callback = None), vous n'avez donc pas besoin d'ouvrir en écriture et de fermer chaque fichier.
Jim Carroll
6
Je noterais que SFTP n'est pas la même chose que SCP.
Drahkar le
4
@Drahkar la question demande que le fichier soit envoyé via SSH. Voilà ce que cela fait.
Tony Meyer
8
Remarque: pour que cela fonctionne dès le départ, ajoutez ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())après l'instanciation ssh.
doda
1
Les gars, est-il sûr d'utiliser le mot de passe du serveur? est-il possible d'utiliser la clé privée?
Aysennoussi
32

Vous utiliseriez probablement le module de sous - processus . Quelque chose comme ça:

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

destinationest probablement de la forme user@remotehost:remotepath. Merci à @Charles Duffy d'avoir souligné la faiblesse de ma réponse originale, qui utilisait un seul argument de chaîne pour spécifier l'opération scp shell=True- qui ne gérerait pas les espaces dans les chemins.

La documentation du module a exemples de vérification des erreurs que vous souhaiterez peut-être effectuer conjointement avec cette opération.

Assurez-vous que vous avez configuré les informations d'identification appropriées afin de pouvoir effectuer un scp sans surveillance et sans mot de passe entre les machines . Il y a déjà une question de stackoverflow pour cela .

Blair Conrad
la source
3
Utiliser subprocess.Popen est la bonne chose. Lui passer une chaîne plutôt qu'un tableau (et utiliser shell = True) est une mauvaise chose, car cela signifie que les noms de fichiers avec des espaces ne fonctionnent pas correctement.
Charles Duffy
2
au lieu de "sts = os.waitpid (p.pid, 0)", nous pouvons utiliser "sts = p.wait ()".
db42
existe-t-il un moyen sans exécution avec l'API python directe pour ce protocole?
lpapp
1
subprocess.check_call(['scp', myfile, destination])pourrait être utilisé à la place depuis Python 2.5 (2006)
jfs
@JFSebastian: oui, je l'ai vérifié en décembre. Mes votes positifs le prouvent au moins. :) Merci pour le suivi.
lpapp
12

Il existe plusieurs façons d'aborder le problème:

  1. Wrap programmes de ligne de commande
  2. utiliser une bibliothèque Python qui fournit des capacités SSH (par exemple - Paramiko ou Twisted Conch )

Chaque approche a ses propres bizarreries. Vous devrez configurer des clés SSH pour activer les connexions sans mot de passe si vous encapsulez des commandes système telles que «ssh», «scp» ou «rsync». Vous pouvez intégrer un mot de passe dans un script en utilisant Paramiko ou une autre bibliothèque, mais vous pourriez trouver le manque de documentation frustrant, surtout si vous n'êtes pas familier avec les bases de la connexion SSH (par exemple - échanges de clés, agents, etc.). Il va probablement sans dire que les clés SSH sont presque toujours une meilleure idée que les mots de passe pour ce genre de choses.

REMARQUE: il est difficile de battre rsync si vous prévoyez de transférer des fichiers via SSH, surtout si l'alternative est un ancien scp.

J'ai utilisé Paramiko dans le but de remplacer les appels système, mais je me suis retrouvé attiré par les commandes encapsulées en raison de leur facilité d'utilisation et de leur familiarité immédiate. Vous pourriez être différent. J'ai donné une fois à Conch il y a quelque temps, mais cela ne m'a pas plu.

Si vous optez pour le chemin de l'appel système, Python propose un tableau d'options telles que os.system ou les commandes / modules de sous-processus. J'irais avec le module de sous-processus si vous utilisez la version 2.4+.


la source
1
curieux: quelle est l'histoire de rsync vs scp?
Alok
7

Atteint le même problème, mais au lieu de "pirater" ou d'émuler la ligne de commande:

J'ai trouvé cette réponse ici .

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
la source
1
Remarque - cela repose sur le package scp. En décembre 2017, la dernière mise à jour de ce package date de 2015 et le numéro de version est 0.1.x. Pas sûr que je voudrais ajouter cette dépendance.
Chuck
2
donc, c'est 2018 maintenant et en mai ils ont publié la version 0.11.0. Donc, il semble que SCPClient n'est pas mort après tout?
Adriano_Pinaffo
5

Vous pouvez faire quelque chose comme ça, pour gérer également la vérification de la clé hôte

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

fabric pourrait être utilisé pour télécharger des fichiers 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
la source
2

Vous pouvez utiliser le package vassal, qui est exactement conçu pour cela.

Tout ce dont vous avez besoin est d'installer vassal et de faire

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

En outre, cela vous évitera de vous authentifier et vous n'aurez pas besoin de les saisir encore et encore.

Shawn
la source
1

J'ai utilisé sshfs pour monter le répertoire distant via ssh et shutil pour copier les fichiers:

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

Puis en python:

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

Cette méthode présente l'avantage que vous pouvez diffuser des données si vous générez des données plutôt que de les mettre en cache localement et d'envoyer un seul fichier volumineux.

Jonno_FTW
la source
1

Essayez ceci si vous ne souhaitez pas utiliser de certificats 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
la source
1

Utilisation de la ressource externe 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
la source
0

L'appel de scpcommande via un sous-processus ne permet pas de recevoir le rapport de progression à l'intérieur du script. pexpectpourrait être utilisé pour extraire cette information:

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

Voir le fichier de copie python dans le réseau local (linux -> linux)

jfs
la source
0

Une approche très simple est la suivante:

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

Aucune bibliothèque python n'est requise (seulement os), et cela fonctionne, cependant l'utilisation de cette méthode dépend d'un autre client ssh à installer. Cela peut entraîner un comportement indésirable s'il est exécuté sur un autre système.

Roberto Marzocchi
la source
-3

Un peu hacky, mais ce qui suit devrait fonctionner :)

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