Bagaimana cara menyalin file ke server jarak jauh dengan Python menggunakan SCP atau SSH?

103

Saya memiliki file teks di mesin lokal saya yang dihasilkan oleh skrip Python harian yang dijalankan di cron.

Saya ingin menambahkan sedikit kode agar file tersebut dikirim dengan aman ke server saya melalui SSH.

Alok
sumber
1
menemukan sesuatu yang serupa, bisakah kamu melihat-lihat stackoverflow.com/questions/11009308/…
JJ84

Jawaban:

55

Anda dapat memanggil scpperintah bash (ini menyalin file melalui SSH ) dengan subprocess.run:

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

Jika Anda membuat file yang ingin Anda kirim dalam program Python yang sama, Anda akan ingin memanggil subprocess.runperintah di luar withblok yang Anda gunakan untuk membuka file (atau panggil .close()file terlebih dahulu jika Anda tidak menggunakan withblock), jadi Anda tahu itu di-flush ke disk dari Python.

Anda perlu membuat (pada mesin sumber) dan menginstal (pada mesin tujuan) kunci ssh sebelumnya sehingga scp secara otomatis diautentikasi dengan kunci ssh publik Anda (dengan kata lain, sehingga skrip Anda tidak meminta kata sandi) .

pdq
sumber
3
@CharlesDuffy: subprocess.check_call(['scp', srcfile, dest])dapat digunakan sejak Python 2.5 alih-alihrc = Popen(..).wait(); if rc != 0: raise ..
jfs
1
menambahkan kunci ssh bukanlah solusi yang baik jika tidak diperlukan. Misalnya, jika Anda memerlukan komunikasi satu kali, Anda tidak menyiapkan kunci ssh karena alasan keamanan.
orezvani
150

Untuk melakukan ini dengan Python (yaitu tidak membungkus scp melalui subprocess.Popen atau serupa) dengan pustaka Paramiko , Anda akan melakukan sesuatu seperti ini:

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

(Anda mungkin ingin berurusan dengan host yang tidak dikenal, kesalahan, membuat direktori yang diperlukan, dan sebagainya).

Tony Meyer
sumber
14
paramiko juga memiliki fungsi sftp.put (self, localpath, remotepath, callback = None) yang bagus, jadi Anda tidak perlu membuka tulis, dan menutup setiap file.
Jim Carroll
6
Saya akan mencatat bahwa SFTP bukanlah hal yang sama dengan SCP.
Drahkar
4
@Drahkar pertanyaannya meminta file untuk dikirim melalui SSH. Itulah yang dilakukannya.
Tony Meyer
8
Catatan: agar ini berhasil, tambahkan ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())setelah membuat contoh ssh.
doda
1
Teman-teman, apakah aman menggunakan kata sandi server? apakah ada kemungkinan untuk menggunakan Kunci Pribadi?
Aysennoussi
32

Anda mungkin akan menggunakan modul subproses . Sesuatu seperti ini:

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

Dimana destinationmungkin bentuknya user@remotehost:remotepath. Terima kasih kepada @Charles Duffy karena menunjukkan kelemahan dalam jawaban asli saya, yang menggunakan argumen string tunggal untuk menentukan operasi scp shell=True- yang tidak akan menangani spasi kosong di jalur.

Dokumentasi modul memiliki contoh pemeriksaan kesalahan yang mungkin ingin Anda lakukan sehubungan dengan operasi ini.

Pastikan Anda telah menyiapkan kredensial yang tepat sehingga Anda dapat melakukan scp tanpa pengawasan tanpa kata sandi di antara mesin . Sudah ada pertanyaan stackoverflow untuk ini .

Blair Conrad
sumber
3
Menggunakan subprocess.Popen is the Right Thing. Meneruskan string daripada array (dan menggunakan shell = True) adalah Hal yang Salah, karena itu berarti nama file dengan spasi tidak berfungsi dengan benar.
Charles Duffy
2
alih-alih "sts = os.waitpid (p.pid, 0)", kita dapat menggunakan "sts = p.wait ()".
db42
apakah ada cara eksekusi gratis dengan API python langsung untuk protokol ini?
lpapp
1
subprocess.check_call(['scp', myfile, destination])dapat digunakan sebagai gantinya sejak Python 2.5 (2006)
jfs
@JFSebastian: ya, saya check-out di bulan Desember. Suara positif saya membuktikan hal itu, setidaknya. :) Terima kasih atas tindak lanjutnya.
lpapp
12

Ada beberapa cara berbeda untuk menangani masalah:

  1. Bungkus program baris perintah
  2. menggunakan pustaka Python yang menyediakan kemampuan SSH (misalnya - Paramiko atau Twisted Conch )

Setiap pendekatan memiliki keunikannya sendiri. Anda perlu mengatur kunci SSH untuk mengaktifkan login tanpa kata sandi jika Anda menggabungkan perintah sistem seperti "ssh", "scp" atau "rsync." Anda dapat menyematkan kata sandi dalam skrip menggunakan Paramiko atau pustaka lain, tetapi Anda mungkin merasa kekurangan dokumentasi membuat frustrasi, terutama jika Anda tidak terbiasa dengan dasar-dasar koneksi SSH (misalnya - pertukaran kunci, agen, dll). Mungkin tidak perlu dikatakan bahwa kunci SSH hampir selalu merupakan ide yang lebih baik daripada kata sandi untuk hal semacam ini.

CATATAN: sulit untuk mengalahkan rsync jika Anda berencana mentransfer file melalui SSH, terutama jika alternatifnya adalah scp lama biasa.

Saya telah menggunakan Paramiko dengan tujuan untuk mengganti panggilan sistem tetapi mendapati diri saya ditarik kembali ke perintah yang dibungkus karena kemudahan penggunaan dan keakraban langsungnya. Anda mungkin berbeda. Saya memberikan Conch sekali lagi beberapa waktu lalu tetapi itu tidak menarik bagi saya.

Jika memilih jalur panggilan sistem, Python menawarkan serangkaian opsi seperti os.system atau modul perintah / subproses. Saya akan menggunakan modul subproses jika menggunakan versi 2.4+.


sumber
1
penasaran: bagaimana ceritanya di rsync vs. scp?
Alok
7

Mencapai masalah yang sama, tetapi bukannya "meretas" atau meniru baris perintah:

Temukan jawaban ini di sini .

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
sumber
1
Catatan - ini bergantung pada paket scp. Pada Desember 2017, pembaruan terbaru untuk paket itu pada 2015 dan nomor versinya adalah 0.1.x. Tidak yakin saya ingin menambahkan ketergantungan ini.
Chuck
2
jadi, ini tahun 2018 sekarang dan di bulan Mei mereka merilis versi 0.11.0. Jadi, sepertinya SCPClient tidak mati sama sekali?
Adriano_Pinaffo
5

Anda dapat melakukan sesuatu seperti ini, untuk menangani pemeriksaan kunci host juga

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

fabric dapat digunakan untuk mengunggah 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()
jfs
sumber
2

Anda dapat menggunakan paket pengikut, yang dirancang khusus untuk ini.

Yang Anda butuhkan hanyalah menginstal pengikut dan melakukannya

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

Selain itu, ini akan menghemat otentikasi kredensial dan tidak perlu mengetiknya lagi dan lagi.

Shawn
sumber
1

Saya menggunakan sshfs untuk memasang direktori jarak jauh melalui ssh, dan shutil untuk menyalin file:

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

Kemudian di python:

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

Metode ini memiliki keuntungan karena Anda dapat mengalirkan data jika Anda membuat data daripada melakukan caching secara lokal dan mengirim satu file besar.

Jonno_FTW
sumber
1

Coba ini jika Anda tidak ingin menggunakan sertifikat 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
sumber
1

Menggunakan paramiko sumber daya eksternal;

    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
sumber
0

Memanggil scpperintah melalui subproses tidak memungkinkan untuk menerima laporan kemajuan di dalam skrip. pexpectdapat digunakan untuk mengekstrak info itu:

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

Lihat file copy python di jaringan lokal (linux -> linux)

jfs
sumber
0

Pendekatan yang sangat sederhana adalah sebagai berikut:

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

Tidak ada pustaka python yang diperlukan (hanya os), dan ini berfungsi, namun menggunakan metode ini bergantung pada klien ssh lain yang akan diinstal. Ini dapat menyebabkan perilaku yang tidak diinginkan jika dijalankan di sistem lain.

Roberto Marzocchi
sumber
-3

Agak hacky, tetapi yang berikut harus berfungsi :)

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