Como copiar um arquivo para um servidor remoto em Python usando SCP ou SSH?

103

Eu tenho um arquivo de texto em minha máquina local que é gerado por um script Python diário executado no cron.

Eu gostaria de adicionar um pouco de código para que esse arquivo seja enviado com segurança ao meu servidor por SSH.

Alok
fonte
1
encontrou algo semelhante, você pode dar uma olhada stackoverflow.com/questions/11009308/…
JJ84

Respostas:

55

Você pode chamar o scpcomando bash (ele copia arquivos por SSH ) com subprocess.run:

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

Se você estiver criando o arquivo que deseja enviar no mesmo programa Python, convém chamar o subprocess.runcomando fora do withbloco que está usando para abrir o arquivo (ou chamar .close()o arquivo primeiro se não estiver usando um withbloco), para que você saiba que é liberado para o disco a partir do Python.

Você precisa gerar (na máquina de origem) e instalar (na máquina de destino) uma chave ssh de antemão para que o scp seja autenticado automaticamente com sua chave ssh pública (em outras palavras, para que seu script não peça uma senha) .

PDQ
fonte
3
@CharlesDuffy: subprocess.check_call(['scp', srcfile, dest])pode ser usado desde Python 2.5 em vez derc = Popen(..).wait(); if rc != 0: raise ..
jfs
1
adicionar a chave ssh não é uma boa solução quando não é necessária. Por exemplo, se você precisa de uma comunicação única, não configure a chave ssh por motivos de segurança.
orezvani
150

Para fazer isso em Python (ou seja, sem envolver scp por meio de subprocesso.Popen ou semelhante) com a biblioteca Paramiko , você faria algo assim:

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

(Você provavelmente gostaria de lidar com hosts desconhecidos, erros, criar quaisquer diretórios necessários e assim por diante).

Tony Meyer
fonte
14
paramiko tem uma boa função sftp.put (self, localpath, remotepath, callback = None) também, então você não precisa abrir o write e fechar cada arquivo.
Jim Carroll,
6
Eu observaria que SFTP não é a mesma coisa que SCP.
Drahkar,
4
@Drahkar a pergunta pede que o arquivo seja enviado via SSH. Isso é o que isso faz.
Tony Meyer de
8
Nota: para fazer isso funcionar imediatamente, adicione ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())após a instanciação ssh.
doda,
1
Pessoal, é seguro usar a senha do servidor? existe a possibilidade de usar a chave privada?
Aysennoussi
32

Você provavelmente usaria o módulo de subprocesso . Algo assim:

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

Onde destinationestá provavelmente da forma user@remotehost:remotepath. Obrigado a @Charles Duffy por apontar a fraqueza em minha resposta original, que usava um único argumento de string para especificar a operação scp shell=True- que não lidaria com espaços em branco nos caminhos.

A documentação do módulo tem exemplos de verificação de erros que você pode querer executar em conjunto com esta operação.

Certifique-se de configurar as credenciais adequadas para que possa executar um scp autônomo e sem senha entre as máquinas . Já existe uma questão stackoverflow para isso .

Blair Conrad
fonte
3
Usar subprocess.Popen é a coisa certa. Passar uma string em vez de um array (e usar shell = True) é a coisa errada, pois significa que nomes de arquivos com espaços não funcionam corretamente.
Charles Duffy,
2
em vez de "sts = os.waitpid (p.pid, 0)", podemos usar "sts = p.wait ()".
db42
existe uma forma de execução livre com Python API direta para este protocolo?
lpapp de
1
subprocess.check_call(['scp', myfile, destination])poderia ser usado em vez disso desde Python 2.5 (2006)
jfs
@JFSebastian: sim, eu verifiquei em dezembro. Meus votos positivos provam isso, pelo menos. :) Obrigado pelo acompanhamento.
lpapp
12

Existem algumas maneiras diferentes de abordar o problema:

  1. Encapsular programas de linha de comando
  2. use uma biblioteca Python que forneça recursos SSH (por exemplo, Paramiko ou Twisted Conch )

Cada abordagem tem suas peculiaridades. Você precisará configurar as chaves SSH para habilitar logins sem senha se estiver agrupando comandos do sistema como "ssh", "scp" ou "rsync". Você pode inserir uma senha em um script usando Paramiko ou alguma outra biblioteca, mas pode achar a falta de documentação frustrante, especialmente se você não estiver familiarizado com os fundamentos da conexão SSH (por exemplo, trocas de chaves, agentes, etc). Provavelmente nem é preciso dizer que as chaves SSH quase sempre são uma ideia melhor do que as senhas para esse tipo de coisa.

NOTA: é difícil superar o rsync se você planeja transferir arquivos via SSH, especialmente se a alternativa for o scp antigo.

Usei Paramiko com o objetivo de substituir chamadas de sistema, mas me vi atraído de volta para os comandos agrupados devido à sua facilidade de uso e familiaridade imediata. Você pode ser diferente. Dei uma olhada em Conch há algum tempo, mas não me agradou.

Se optar pelo caminho de chamada do sistema, Python oferece uma variedade de opções, como os.system ou os comandos / módulos de subprocesso. Eu iria com o módulo de subprocesso se estiver usando a versão 2.4+.


fonte
1
curioso: qual é a história do rsync vs. scp?
Alok,
7

Alcançou o mesmo problema, mas em vez de "hackear" ou emular linha de comando:

Encontrou esta resposta aqui .

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
fonte
1
Nota - isso depende do pacote scp. Em dezembro de 2017, a atualização mais recente desse pacote foi em 2015 e o número da versão é 0.1.x. Não tenho certeza se gostaria de adicionar essa dependência.
Chuck
2
então, é 2018 agora e em maio eles lançaram a versão 0.11.0. Então, parece que o SCPClient não está morto, afinal?
Adriano_Pinaffo
5

Você pode fazer algo assim, para lidar com a verificação da chave do host também

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

fabric pode ser usado para fazer upload de arquivos 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
fonte
2

Você pode usar o pacote vassalo, que é projetado exatamente para isso.

Tudo que você precisa é instalar o vassalo e fazer

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

Além disso, você salvará as credenciais de autenticação e não precisará digitá-las novamente.

Shawn
fonte
1

Usei sshfs para montar o diretório remoto via ssh e shutil para copiar os arquivos:

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

Então, em python:

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

Esse método tem a vantagem de poder transmitir dados se estiver gerando dados, em vez de armazenar em cache localmente e enviar um único arquivo grande.

Jonno_FTW
fonte
1

Tente fazer isso se não quiser usar 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
fonte
1

Usando o 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')
Michael
fonte
0

Chamar o scpcomando via subprocesso não permite receber o relatório de progresso dentro do script. pexpectpode ser usado para extrair essa informação:

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

Veja o arquivo de cópia do python na rede local (linux -> linux)

jfs
fonte
0

Uma abordagem muito simples é a seguinte:

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

Nenhuma biblioteca python é necessária (apenas sistema operacional) e funciona, no entanto, o uso desse método depende de outro cliente ssh para ser instalado. Isso pode resultar em um comportamento indesejado se executado em outro sistema.

Roberto Marzocchi
fonte
-3

Tipo de hacky, mas o seguinte deve funcionar :)

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