¿Cómo copiar un archivo a un servidor remoto en Python usando SCP o SSH?

103

Tengo un archivo de texto en mi máquina local que es generado por un script de Python que se ejecuta diariamente en cron.

Me gustaría agregar un poco de código para que ese archivo se envíe de forma segura a mi servidor a través de SSH.

Alok
fuente
1
Encontré algo similar, ¿puedes echar un vistazo a stackoverflow.com/questions/11009308/…
JJ84

Respuestas:

55

Puede llamar al scpcomando bash (copia archivos a través de SSH ) con 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 está creando el archivo que desea enviar en el mismo programa de Python, querrá llamar al subprocess.runcomando fuera del withbloque que está usando para abrir el archivo (o llamar .close()al archivo primero si no está usando un withblock), para que sepa que se descarga en el disco desde Python.

Debe generar (en la máquina de origen) e instalar (en la máquina de destino) una clave ssh de antemano para que el scp se autentique automáticamente con su clave ssh pública (en otras palabras, para que su script no solicite una contraseña) .

pdq
fuente
3
@CharlesDuffy: subprocess.check_call(['scp', srcfile, dest])podría usarse desde Python 2.5 en lugar derc = Popen(..).wait(); if rc != 0: raise ..
jfs
1
agregar la clave ssh no es una buena solución cuando no es necesaria. Por ejemplo, si necesita una comunicación única, no configura la clave ssh por razones de seguridad.
orezvani
150

Para hacer esto en Python (es decir, sin ajustar scp a través de subprocess.Popen o similar) con la biblioteca Paramiko , debería hacer algo como esto:

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

(Probablemente quiera lidiar con hosts desconocidos, errores, crear los directorios necesarios, etc.).

Tony Meyer
fuente
14
paramiko también tiene una buena función sftp.put (self, localpath, remotepath, callback = None), por lo que no tiene que abrir escribir y cerrar cada archivo.
Jim Carroll
6
Me gustaría señalar que SFTP no es lo mismo que SCP.
Drahkar
4
@Drahkar la pregunta solicita que el archivo se envíe a través de SSH. Eso es lo que hace esto.
Tony Meyer
8
Nota: para que esto funcione fuera de la caja, agregue ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())después de crear una instancia ssh.
doda
1
Chicos, ¿es seguro usar la contraseña del servidor? ¿Existe la posibilidad de utilizar la clave privada?
Aysennoussi
32

Probablemente usaría el módulo de subproceso . Algo como esto:

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

Donde destinationes probablemente de la formauser@remotehost:remotepath . Gracias a @Charles Duffy por señalar la debilidad en mi respuesta original, que usaba un argumento de cadena única para especificar la operación scp shell=True, que no manejaría los espacios en blanco en las rutas.

La documentación del módulo tiene ejemplos de comprobación de errores que es posible que desee realizar junto con esta operación.

Asegúrese de haber configurado las credenciales adecuadas para poder realizar un scp desatendido y sin contraseña entre las máquinas . Ya hay una pregunta de stackoverflow para esto .

Blair Conrad
fuente
3
Usando subprocess.Popen es lo correcto. Pasarle una cadena en lugar de una matriz (y usar shell = True) es lo incorrecto, ya que significa que los nombres de archivo con espacios no funcionan correctamente.
Charles Duffy
2
en lugar de "sts = os.waitpid (p.pid, 0)", podemos usar "sts = p.wait ()".
db42
¿Existe una forma libre de ejecución con la API de Python directa para este protocolo?
lpapp
1
subprocess.check_call(['scp', myfile, destination])podría usarse en su lugar desde Python 2.5 (2006)
jfs
@JFSebastian: sí, lo revisé en diciembre. Mis votos a favor lo demuestran, al menos. :) Gracias por el seguimiento.
lpapp
12

Hay un par de formas diferentes de abordar el problema:

  1. Ajustar programas de línea de comandos
  2. use una biblioteca de Python que proporcione capacidades SSH (por ejemplo, Paramiko o Twisted Conch )

Cada enfoque tiene sus propias peculiaridades. Deberá configurar las claves SSH para habilitar inicios de sesión sin contraseña si está ajustando comandos del sistema como "ssh", "scp" o "rsync". Puede incrustar una contraseña en un script usando Paramiko o alguna otra biblioteca, pero la falta de documentación puede resultar frustrante, especialmente si no está familiarizado con los conceptos básicos de la conexión SSH (por ejemplo, intercambios de claves, agentes, etc.). Probablemente no hace falta decir que las claves SSH son casi siempre una mejor idea que las contraseñas para este tipo de cosas.

NOTA: es difícil superar a rsync si planea transferir archivos a través de SSH, especialmente si la alternativa es scp.

He usado Paramiko con miras a reemplazar las llamadas al sistema, pero me sentí atraído por los comandos envueltos debido a su facilidad de uso y familiaridad inmediata. Puede que seas diferente. Le di a Conch un vistazo hace algún tiempo pero no me atrajo.

Si opta por la ruta de llamada al sistema, Python ofrece una serie de opciones como os.system o los módulos de comandos / subprocesos. Iría con el módulo de subproceso si uso la versión 2.4+.


fuente
1
curioso: ¿cuál es la historia de rsync vs scp?
Alok
7

Llegó al mismo problema, pero en lugar de "piratear" o emular la línea de comando:

Encontré esta respuesta aquí .

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
fuente
1
Nota: esto se basa en el paquete scp. A diciembre de 2017, la última actualización de ese paquete fue en 2015 y el número de versión es 0.1.x. No estoy seguro de querer agregar esta dependencia.
Chuck
2
entonces, es 2018 ahora y en mayo lanzaron la versión 0.11.0. Entonces, ¿parece que SCPClient no está muerto después de todo?
Adriano_Pinaffo
5

Puede hacer algo como esto, para manejar la verificación de la clave del host también

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

fabric podría usarse para cargar archivos 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
fuente
2

Puede usar el paquete vasallo, que está diseñado exactamente para esto.

Todo lo que necesitas es instalar vasallo y hacer

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

Además, le ahorrará autenticar la credencial y no es necesario que la escriba una y otra vez.

Shawn
fuente
1

Solía sshfs para montar el directorio remoto a través de SSH, y shutil para copiar los archivos:

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

Luego en Python:

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

Este método tiene la ventaja de que puede transmitir datos si está generando datos en lugar de almacenarlos en caché localmente y enviar un solo archivo grande.

Jonno_FTW
fuente
1

Pruebe esto si no desea utilizar certificados 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
fuente
1

Utilizando el recurso externo 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')
Miguel
fuente
0

Llamar al scpcomando a través de un subproceso no permite recibir el informe de progreso dentro del script. pexpectpodría usarse para extraer esa información:

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

Ver archivo de copia de python en la red local (linux -> linux)

jfs
fuente
0

Un enfoque muy simple es el siguiente:

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

No se requiere una biblioteca de Python (solo sistema operativo), y funciona, sin embargo, el uso de este método depende de la instalación de otro cliente ssh. Esto podría resultar en un comportamiento no deseado si se ejecuta en otro sistema.

Roberto Marzocchi
fuente
-3

Un poco hacky, pero lo siguiente debería funcionar :)

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