Wie kopiere ich eine Datei mit SCP oder SSH auf einen Remote-Server in Python?

103

Ich habe eine Textdatei auf meinem lokalen Computer, die von einem täglichen Python-Skript generiert wird, das in cron ausgeführt wird.

Ich möchte ein bisschen Code hinzufügen, damit diese Datei sicher über SSH an meinen Server gesendet wird.

Alok
quelle
1
fand etwas ähnliches, können Sie einen Blick auf stackoverflow.com/questions/11009308/…
JJ84

Antworten:

55

Sie können den scpBefehl bash (er kopiert Dateien über SSH ) aufrufen mit subprocess.run:

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

Wenn Sie die Datei erstellen, die Sie im selben Python-Programm senden möchten, möchten Sie den subprocess.runBefehl außerhalb des withBlocks aufrufen, den Sie zum Öffnen der Datei verwenden (oder .close()die Datei zuerst aufrufen, wenn Sie keine verwenden withblock), damit Sie wissen, dass es von Python auf die Festplatte geschrieben wurde.

Sie müssen zuvor (auf dem Quellcomputer) einen SSH-Schlüssel generieren und (auf dem Zielcomputer) installieren, damit der SCP automatisch mit Ihrem öffentlichen SSH-Schlüssel authentifiziert wird (mit anderen Worten, Ihr Skript fragt nicht nach einem Kennwort). .

pdq
quelle
3
@ CharlesDuffy: subprocess.check_call(['scp', srcfile, dest])könnte seit Python 2.5 anstelle vonrc = Popen(..).wait(); if rc != 0: raise ..
jfs
1
Das Hinzufügen eines SSH-Schlüssels ist keine gute Lösung, wenn er nicht benötigt wird. Wenn Sie beispielsweise eine einmalige Kommunikation benötigen, richten Sie den SSH-Schlüssel aus Sicherheitsgründen nicht ein.
Orezvani
150

Um dies in Python (dh scp nicht durch subprocess.Popen oder ähnliches zu verpacken ) mit der Paramiko- Bibliothek zu tun, würden Sie Folgendes tun:

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

(Sie möchten wahrscheinlich unbekannte Hosts, Fehler, das Erstellen von erforderlichen Verzeichnissen usw. behandeln.)

Tony Meyer
quelle
14
paramiko hat auch eine nette sftp.put-Funktion (self, localpath, remotepath, callback = None), sodass Sie nicht jede Datei öffnen und schließen müssen.
Jim Carroll
6
Ich würde bemerken, dass SFTP nicht dasselbe ist wie SCP.
Drahkar
4
@Drahkar die Frage fragt nach der Datei über SSH gesendet werden. Das macht das.
Tony Meyer
8
Hinweis: Damit dies ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())sofort funktioniert, fügen Sie nach dem Instanziieren hinzu ssh.
Doda
1
Leute, ist es sicher, das Serverpasswort zu verwenden? Gibt es eine Möglichkeit, den privaten Schlüssel zu verwenden?
Aysennoussi
32

Sie würden wahrscheinlich das Unterprozessmodul verwenden . Etwas wie das:

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

Wo destinationist wohl von der Form user@remotehost:remotepath. Vielen Dank an @Charles Duffy für den Hinweis auf die Schwachstelle in meiner ursprünglichen Antwort, die ein einziges Zeichenfolgenargument zur Angabe der scp-Operation verwendete shell=True- das würde Leerzeichen in Pfaden nicht behandeln.

Die Moduldokumentation enthält Beispiele für die Fehlerprüfung, die Sie möglicherweise in Verbindung mit diesem Vorgang ausführen möchten.

Stellen Sie sicher, dass Sie die richtigen Anmeldeinformationen eingerichtet haben, damit Sie eine unbeaufsichtigte, kennwortlose Überprüfung zwischen den Computern durchführen können . Hierfür gibt es bereits eine Stackoverflow-Frage .

Blair Conrad
quelle
3
Unterprozess verwenden. Öffnen ist das Richtige. Das Übergeben einer Zeichenfolge anstelle eines Arrays (und die Verwendung von shell = True) ist das Falsche, da Dateinamen mit Leerzeichen nicht richtig funktionieren.
Charles Duffy
2
Anstelle von "sts = os.waitpid (p.pid, 0)" können wir "sts = p.wait ()" verwenden.
db42
Gibt es eine ausführungsfreie Möglichkeit mit direkter Python-API für dieses Protokoll?
lpapp
1
subprocess.check_call(['scp', myfile, destination])könnte stattdessen seit Python 2.5 (2006)
jfs
@JFSebastian: Ja, ich habe es im Dezember ausgecheckt. Meine Upvotes beweisen das zumindest. :) Danke für das Follow-up.
lpapp
12

Es gibt verschiedene Möglichkeiten, um das Problem anzugehen:

  1. Befehlszeilenprogramme umbrechen
  2. Verwenden Sie eine Python-Bibliothek, die SSH-Funktionen bietet (z. B. Paramiko oder Twisted Conch ).

Jeder Ansatz hat seine eigenen Macken. Sie müssen SSH-Schlüssel einrichten, um kennwortlose Anmeldungen zu aktivieren, wenn Sie Systembefehle wie "ssh", "scp" oder "rsync" umschließen. Sie können ein Kennwort mithilfe von Paramiko oder einer anderen Bibliothek in ein Skript einbetten. Der Mangel an Dokumentation ist jedoch möglicherweise frustrierend, insbesondere wenn Sie mit den Grundlagen der SSH-Verbindung nicht vertraut sind (z. B. Schlüsselaustausch, Agenten usw.). Es versteht sich wahrscheinlich von selbst, dass SSH-Schlüssel für solche Dinge fast immer eine bessere Idee sind als Passwörter.

HINWEIS: Es ist schwer, rsync zu schlagen, wenn Sie Dateien über SSH übertragen möchten, insbesondere wenn die Alternative einfach altes scp ist.

Ich habe Paramiko verwendet, um Systemaufrufe zu ersetzen, fand mich jedoch aufgrund ihrer Benutzerfreundlichkeit und sofortigen Vertrautheit zu den umschlossenen Befehlen zurückgezogen. Du könntest anders sein. Ich habe Conch vor einiger Zeit noch einmal gegeben, aber es hat mich nicht angesprochen.

Wenn Sie sich für den Systemaufrufpfad entscheiden, bietet Python eine Reihe von Optionen wie os.system oder die Befehle / Unterprozessmodule. Ich würde mit dem Subprozessmodul gehen, wenn ich Version 2.4+ verwenden würde.


quelle
1
neugierig: was ist die Geschichte auf rsync vs. scp?
Alok
7

Das gleiche Problem erreicht, aber anstatt die Befehlszeile zu "hacken" oder zu emulieren:

Fand diese Antwort hier .

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
quelle
1
Hinweis - Dies hängt vom scp-Paket ab. Ab Dezember 2017 war das letzte Update für dieses Paket im Jahr 2015 und die Versionsnummer ist 0.1.x. Ich bin mir nicht sicher, ob ich diese Abhängigkeit hinzufügen möchte.
Chuck
1
es ist also 2018 und im Mai haben sie die Version 0.11.0 veröffentlicht. SCPClient scheint also doch nicht tot zu sein?
Adriano_Pinaffo
5

Sie können so etwas tun, um auch die Überprüfung des Hostschlüssels durchzuführen

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

fabric könnte verwendet werden, um Dateien vis ssh hochzuladen:

#!/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
quelle
2

Sie können das Vasallenpaket verwenden, das genau dafür ausgelegt ist.

Alles was Sie brauchen, ist Vasall zu installieren und zu tun

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

Außerdem werden Sie authentifizierte Anmeldeinformationen speichern und müssen diese nicht immer wieder eingeben.

Shawn
quelle
1

Ich habe sshfs verwendet , um das Remote-Verzeichnis über ssh bereitzustellen , und shutil, um die Dateien zu kopieren:

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

Dann in Python:

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

Diese Methode hat den Vorteil, dass Sie Daten streamen können, wenn Sie Daten generieren, anstatt sie lokal zwischenzuspeichern und eine einzelne große Datei zu senden.

Jonno_FTW
quelle
1

Versuchen Sie dies, wenn Sie keine SSL-Zertifikate verwenden möchten:

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
quelle
1

Verwenden der externen Ressource 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
quelle
0

Durch Aufrufen des scpBefehls über einen Unterprozess kann der Fortschrittsbericht nicht im Skript empfangen werden. pexpectkönnte verwendet werden, um diese Informationen zu extrahieren:

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

Siehe Python-Kopierdatei im lokalen Netzwerk (Linux -> Linux)

jfs
quelle
0

Ein sehr einfacher Ansatz ist der folgende:

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

Es ist keine Python-Bibliothek erforderlich (nur Betriebssystem), und es funktioniert. Die Verwendung dieser Methode setzt jedoch voraus, dass ein anderer SSH-Client installiert wird. Dies kann zu unerwünschtem Verhalten führen, wenn es auf einem anderen System ausgeführt wird.

Roberto Marzocchi
quelle
-3

Ein bisschen hacky, aber das Folgende sollte funktionieren :)

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