Làm cách nào để sao chép tệp vào máy chủ từ xa bằng Python bằng SCP hoặc SSH?


103

Tôi có một tệp văn bản trên máy cục bộ của mình được tạo bởi một tập lệnh Python hàng ngày chạy bằng cron.

Tôi muốn thêm một chút mã để tệp đó được gửi an toàn đến máy chủ của tôi qua SSH.


1
tìm thấy một cái gì đó tương tự, u có thể có một cái nhìn stackoverflow.com/questions/11009308/...
JJ84

Câu trả lời:


55

Bạn có thể gọi scplệnh bash (nó sao chép tệp qua SSH ) bằng subprocess.run:

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

Nếu bạn đang tạo tệp mà bạn muốn gửi trong cùng một chương trình Python, bạn sẽ muốn gọi subprocess.runlệnh bên ngoài withkhối bạn đang sử dụng để mở tệp (hoặc gọi .close()trên tệp trước nếu bạn không sử dụng withkhối), vì vậy bạn biết nó được chuyển sang đĩa từ Python.

Bạn cần tạo (trên máy nguồn) và cài đặt (trên máy đích) trước một khóa ssh để scp tự động được xác thực bằng khóa ssh chung của bạn (nói cách khác, vì vậy tập lệnh của bạn không yêu cầu mật khẩu) .


3
@CharlesDuffy: subprocess.check_call(['scp', srcfile, dest])có thể được sử dụng kể từ Python 2.5 thay vìrc = Popen(..).wait(); if rc != 0: raise ..
jfs

1
thêm khóa ssh không phải là một giải pháp tốt khi nó không cần thiết. Ví dụ: nếu bạn cần giao tiếp một lần, bạn không thiết lập khóa ssh vì lý do bảo mật.
orezvani

150

Để thực hiện điều này bằng Python (tức là không gói scp qua subprocess.Popen hoặc tương tự) với thư viện Paramiko , bạn sẽ làm như sau:

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

(Bạn có thể muốn xử lý các máy chủ không xác định, lỗi, tạo bất kỳ thư mục nào cần thiết, v.v.).


14
paramiko cũng có một hàm sftp.put (self, localpath, remotepath, callback = None) rất hay, vì vậy bạn không cần phải mở ghi và đóng từng tệp.
Jim Carroll

6
Tôi xin lưu ý rằng SFTP không giống với SCP.
Drahkar

4
@Drahkar câu hỏi yêu cầu tệp được gửi qua SSH. Đó là những gì điều này làm.
Tony Meyer

8
Lưu ý: để làm cho điều này hoạt động ngoài hộp, hãy thêm ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())sau khi khởi tạo ssh.
doda

1
Các bạn ơi, sử dụng mật khẩu máy chủ có an toàn không? có khả năng sử dụng Khóa cá nhân không?
Aysennoussi

32

Bạn có thể sẽ sử dụng mô-đun quy trình con . Một cái gì đó như thế này:

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

Nơi destinationcó lẽ là của hình thức user@remotehost:remotepath. Cảm ơn @Charles Duffy đã chỉ ra điểm yếu trong câu trả lời ban đầu của tôi, câu trả lời này sử dụng một đối số chuỗi đơn để chỉ định hoạt động scp shell=True- sẽ không xử lý khoảng trắng trong đường dẫn.

Tài liệu mô-đun có các ví dụ về kiểm tra lỗi mà bạn có thể muốn thực hiện cùng với thao tác này.

Đảm bảo rằng bạn đã thiết lập thông tin xác thực phù hợp để bạn có thể thực hiện scp không cần giám sát, không cần mật khẩu giữa các máy . Đã có một câu hỏi về stackoverflow cho điều này .


3
Sử dụng quy trình con. Mở là Điều đúng đắn. Chuyển cho nó một chuỗi chứ không phải một mảng (và sử dụng shell = True) là Điều sai, vì nó có nghĩa là tên tệp có dấu cách không hoạt động chính xác.
Charles Duffy

2
thay vì "sts = os.waitpid (p.pid, 0)", chúng ta có thể sử dụng "sts = p.wait ()".
db42

có cách thực thi nào miễn phí với API python trực tiếp cho giao thức này không?
lpapp 10/1213

1
subprocess.check_call(['scp', myfile, destination])có thể được sử dụng thay thế kể từ Python 2.5 (2006)
jfs Ngày

@JFSebastian: vâng, tôi đã kiểm tra nó vào tháng 12. Ít nhất thì số phiếu ủng hộ của tôi cũng chứng minh điều đó. :) Cảm ơn vì đã theo dõi.
lpapp

12

Có một số cách khác nhau để tiếp cận vấn đề:

  1. Gói các chương trình dòng lệnh
  2. sử dụng thư viện Python cung cấp các khả năng SSH (ví dụ: - Paramiko hoặc Twisted Conch )

Mỗi cách tiếp cận đều có những điểm kỳ quặc riêng. Bạn sẽ cần thiết lập khóa SSH để kích hoạt đăng nhập không cần mật khẩu nếu bạn đang gói các lệnh hệ thống như "ssh", "scp" hoặc "rsync." Bạn có thể nhúng mật khẩu vào tập lệnh bằng cách sử dụng Paramiko hoặc một số thư viện khác, nhưng bạn có thể cảm thấy khó chịu khi thiếu tài liệu, đặc biệt nếu bạn không quen thuộc với những điều cơ bản của kết nối SSH (ví dụ - trao đổi khóa, tác nhân, v.v.). Có lẽ không cần phải nói rằng khóa SSH hầu như luôn luôn là một ý tưởng tốt hơn so với mật khẩu cho loại nội dung này.

LƯU Ý: rất khó để đánh bại rsync nếu bạn định chuyển tệp qua SSH, đặc biệt nếu giải pháp thay thế là scp cũ.

Tôi đã sử dụng Paramiko với mục đích thay thế các lệnh gọi hệ thống nhưng thấy mình bị thu hút trở lại các lệnh được bọc do tính dễ sử dụng và quen thuộc ngay lập tức. Bạn có thể khác. Tôi đã đưa Conch một lần trước đây nhưng nó không hấp dẫn tôi.

Nếu chọn đường dẫn lệnh gọi hệ thống, Python cung cấp một loạt các tùy chọn như os.system hoặc các lệnh / mô-đun quy trình con. Tôi sẽ đi với mô-đun quy trình con nếu sử dụng phiên bản 2.4+.


1
tò mò: câu chuyện trên rsync so với scp là gì?
Alok

7

Gặp phải vấn đề tương tự, nhưng thay vì "hack" hoặc mô phỏng dòng lệnh:

Tìm thấy câu trả lời này ở đây .

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

1
Lưu ý - điều này phụ thuộc vào gói scp. Kể từ tháng 12 năm 2017, bản cập nhật mới nhất cho gói đó là vào năm 2015 và số phiên bản là 0,1.x. Không chắc tôi muốn thêm phụ thuộc này.
Chuck

2
vì vậy, bây giờ là năm 2018 và có thể họ đã phát hành phiên bản 0.11.0. Vì vậy, có vẻ như SCPClient cuối cùng vẫn chưa chết?
Adriano_Pinaffo

5

Bạn có thể làm điều gì đó như thế này, để xử lý việc kiểm tra khóa máy chủ lưu trữ

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

4

fabric có thể được sử dụng để tải lên tệp 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()

2

Bạn có thể sử dụng gói chư hầu, được thiết kế chính xác cho việc này.

Tất cả những gì bạn cần là cài đặt vassal và làm

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

Ngoài ra, nó sẽ giúp bạn tiết kiệm xác thực thông tin đăng nhập và không cần phải nhập lại chúng nhiều lần.


1

Tôi đã sử dụng sshfs để gắn kết thư mục từ xa qua ssh và tắt để sao chép các tệp:

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

Sau đó, trong python:

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

Phương pháp này có ưu điểm là bạn có thể truyền trực tuyến dữ liệu nếu bạn đang tạo dữ liệu thay vì lưu vào bộ nhớ đệm cục bộ và gửi một tệp lớn.


1

Hãy thử điều này nếu bạn không thể sử dụng chứng chỉ 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!')

1

Sử dụng paramiko tài nguyên bên ngoài;

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

0

scpLệnh gọi thông qua quy trình con không cho phép nhận báo cáo tiến trình bên trong tập lệnh. pexpectcó thể được sử dụng để trích xuất thông tin đó:

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

Xem tệp sao chép python trong mạng cục bộ (linux -> linux)


0

Một cách tiếp cận rất đơn giản như sau:

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

Không yêu cầu thư viện python (chỉ có hệ điều hành) và nó hoạt động, tuy nhiên việc sử dụng phương pháp này dựa vào một ứng dụng ssh khác được cài đặt. Điều này có thể dẫn đến hành vi không mong muốn nếu chạy trên hệ thống khác.


-3

Loại hacky, nhưng những điều sau đây sẽ hoạt động :)

import os
filePath = "/foo/bar/baz.py"
serverPath = "/blah/boo/boom.py"
os.system("scp "+filePath+" user@myserver.com:"+serverPath)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.