จะคัดลอกไฟล์ไปยังเซิร์ฟเวอร์ระยะไกลใน Python โดยใช้ SCP หรือ SSH ได้อย่างไร?


104

ฉันมีไฟล์ข้อความในเครื่องของฉันซึ่งสร้างโดยสคริปต์ Python ทุกวันที่ทำงานใน cron

ฉันต้องการเพิ่มโค้ดเล็กน้อยเพื่อให้ไฟล์นั้นถูกส่งไปยังเซิร์ฟเวอร์ของฉันอย่างปลอดภัยผ่าน SSH


1
เจออะไรคล้าย ๆ กันดูได้
ไหม

คำตอบ:


55

คุณสามารถเรียกscpคำสั่ง bash (มันคัดลอกไฟล์ผ่านSSH ) ด้วย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"])

หากคุณกำลังสร้างไฟล์ที่คุณต้องการส่งในโปรแกรม Python เดียวกันคุณจะต้องเรียกsubprocess.runคำสั่งนอกwithบล็อกที่คุณใช้เพื่อเปิดไฟล์ (หรือเรียก.close()ไฟล์ก่อนหากคุณไม่ได้ใช้withblock) ดังนั้นคุณจะรู้ว่ามันถูกล้างไปยังดิสก์จาก Python

คุณต้องสร้าง (บนเครื่องต้นทาง) และติดตั้ง (บนเครื่องปลายทาง) คีย์ ssh ไว้ล่วงหน้าเพื่อให้ scp ได้รับการรับรองความถูกต้องโดยอัตโนมัติด้วยคีย์ ssh สาธารณะของคุณ (กล่าวอีกนัยหนึ่งสคริปต์ของคุณจะไม่ขอรหัสผ่าน) .


3
@CharlesDuffy: subprocess.check_call(['scp', srcfile, dest])สามารถใช้ได้ตั้งแต่ Python 2.5 แทนที่จะเป็นrc = Popen(..).wait(); if rc != 0: raise ..
jfs

1
การเพิ่มคีย์ ssh ไม่ใช่วิธีแก้ปัญหาที่ดีเมื่อไม่จำเป็น ตัวอย่างเช่นหากคุณต้องการการสื่อสารแบบครั้งเดียวคุณไม่ได้ตั้งค่าคีย์ ssh เนื่องจากเหตุผลด้านความปลอดภัย
orezvani

151

หากต้องการทำสิ่งนี้ใน Python (เช่นไม่ตัด scp ผ่านกระบวนการย่อยเปิดหรือคล้ายกัน) กับไลบรารีParamikoคุณต้องทำสิ่งนี้:

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

(คุณอาจต้องการจัดการกับโฮสต์ที่ไม่รู้จักข้อผิดพลาดการสร้างไดเรกทอรีใด ๆ ที่จำเป็นและอื่น ๆ )


15
paramiko มีฟังก์ชั่น sftp.put (self, localpath, remotepath, callback = None) ที่ดีดังนั้นคุณจึงไม่ต้องเปิดเขียนและปิดแต่ละไฟล์
Jim Carroll

7
ฉันจะทราบว่า SFTP ไม่ใช่สิ่งเดียวกับ SCP
Drahkar

4
@Drahkar คำถามขอให้ส่งไฟล์ผ่าน SSH นั่นคือสิ่งที่ทำ
Tony Meyer

9
หมายเหตุ: เพื่อให้งานนี้ออกมาจากกล่องเพิ่มหลังจากที่อินสแตนซ์ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh
doda

1
พวกคุณใช้รหัสผ่านเซิร์ฟเวอร์ปลอดภัยหรือไม่? มีความเป็นไปได้ที่จะใช้คีย์ส่วนตัวหรือไม่?
Aysennoussi

32

คุณอาจต้องการใช้โมดูลกระบวนการย่อย สิ่งนี้:

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

ในกรณีที่อาจจะเป็นในรูปแบบdestination user@remotehost:remotepathขอบคุณ @Charles Duffy ที่ชี้ให้เห็นจุดอ่อนในคำตอบเดิมของฉันซึ่งใช้อาร์กิวเมนต์สตริงเดียวเพื่อระบุการดำเนินการ scp shell=Trueซึ่งจะไม่จัดการช่องว่างในเส้นทาง

เอกสารประกอบโมดูลมีตัวอย่างการตรวจสอบข้อผิดพลาดที่คุณอาจต้องการดำเนินการร่วมกับการดำเนินการนี้

ตรวจสอบให้แน่ใจว่าคุณได้ตั้งค่าข้อมูลประจำตัวที่เหมาะสมเพื่อให้คุณสามารถดำเนินการแบบอัตโนมัติ SCP passwordless ระหว่างเครื่อง มีเป็นคำถาม StackOverflow สำหรับเรื่องนี้อยู่แล้ว


3
การใช้กระบวนการย่อยการเปิดคือสิ่งที่ถูกต้อง การส่งผ่านสตริงแทนที่จะเป็นอาร์เรย์ (และใช้ shell = True) เป็นสิ่งที่ไม่ถูกต้องเนื่องจากหมายถึงชื่อไฟล์ที่มีช่องว่างทำงานไม่ถูกต้อง
Charles Duffy

2
แทนที่จะเป็น "sts = os.waitpid (p.pid, 0)" เราสามารถใช้ "sts = p.wait ()"
db42

มีวิธีดำเนินการฟรีด้วย API python โดยตรงสำหรับโปรโตคอลนี้หรือไม่
lpapp

1
subprocess.check_call(['scp', myfile, destination])สามารถใช้แทนได้ตั้งแต่ Python 2.5 (2006)
jfs

@JFSebastian: ใช่ฉันตรวจสอบแล้วในเดือนธันวาคม การโหวตของฉันพิสูจน์ได้อย่างน้อยที่สุด :) ขอบคุณสำหรับการติดตามแม้ว่า
lpapp

12

มีสองวิธีในการแก้ไขปัญหา:

  1. ตัดโปรแกรมบรรทัดคำสั่ง
  2. ใช้ไลบรารี Python ที่ให้ความสามารถ SSH (เช่น - ParamikoหรือTwisted Conch )

แต่ละแนวทางมีนิสัยใจคอของตัวเอง คุณจะต้องตั้งค่าคีย์ SSH เพื่อเปิดใช้งานการเข้าสู่ระบบแบบไม่ใช้รหัสผ่านหากคุณกำลังตัดคำสั่งของระบบเช่น "ssh", "scp" หรือ "rsync" คุณสามารถฝังรหัสผ่านในสคริปต์โดยใช้ Paramiko หรือไลบรารีอื่น ๆ ได้ แต่คุณอาจพบว่าไม่มีเอกสารประกอบที่น่าหงุดหงิดโดยเฉพาะอย่างยิ่งหากคุณไม่คุ้นเคยกับพื้นฐานของการเชื่อมต่อ SSH (เช่น - การแลกเปลี่ยนคีย์ตัวแทน ฯลฯ ) อาจเป็นไปโดยไม่ได้บอกว่าคีย์ SSH มักจะเป็นความคิดที่ดีกว่ารหัสผ่านสำหรับสิ่งประเภทนี้

หมายเหตุ: มันยากที่จะเอาชนะ rsync หากคุณวางแผนที่จะถ่ายโอนไฟล์ผ่าน SSH โดยเฉพาะอย่างยิ่งถ้าทางเลือกอื่นคือ scp เก่าธรรมดา

ฉันใช้ Paramiko ด้วยความตั้งใจที่จะเปลี่ยนการโทรของระบบ แต่พบว่าตัวเองกลับไปใช้คำสั่งที่รวมไว้เนื่องจากใช้งานง่ายและคุ้นเคยในทันที คุณอาจจะแตกต่าง ฉันให้ Conch ครั้งหนึ่งเมื่อนานมาแล้ว แต่มันก็ไม่ถูกใจฉัน

หากเลือกใช้เส้นทางการเรียกระบบ Python จะมีอาร์เรย์ของตัวเลือกเช่น os.system หรือโมดูลคำสั่ง / กระบวนการย่อย ฉันจะใช้โมดูลกระบวนการย่อยถ้าใช้เวอร์ชัน 2.4+


1
อยากรู้อยากเห็น: เรื่องราวของ rsync กับ scp คืออะไร?
Alok

7

พบปัญหาเดียวกัน แต่แทนที่จะ "แฮ็ก" หรือเลียนแบบบรรทัดคำสั่ง:

พบคำตอบที่นี่

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
หมายเหตุ - สิ่งนี้ขึ้นอยู่กับแพ็คเกจ scp ณ เดือนธันวาคม 2017 การอัปเดตล่าสุดของแพ็คเกจนั้นคือในปี 2015 และหมายเลขเวอร์ชันคือ 0.1.x ไม่แน่ใจว่าฉันต้องการเพิ่มการอ้างอิงนี้
Chuck

2
ดังนั้นมันคือปี 2018 ในตอนนี้และในเดือนพฤษภาคมพวกเขาปล่อยเวอร์ชัน 0.11.0 ดูเหมือนว่า SCPClient จะยังไม่ตาย?
Adriano_Pinaffo

5

คุณสามารถทำสิ่งนี้เพื่อจัดการการตรวจสอบคีย์โฮสต์ได้เช่นกัน

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

4

fabric สามารถใช้เพื่ออัปโหลดไฟล์ 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

คุณสามารถใช้แพ็คเกจข้าราชบริพารซึ่งออกแบบมาสำหรับสิ่งนี้

สิ่งที่คุณต้องมีคือติดตั้งข้าราชบริพารและทำ

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

นอกจากนี้ยังช่วยให้คุณสามารถรับรองความถูกต้องข้อมูลรับรองและไม่จำเป็นต้องพิมพ์ซ้ำแล้วซ้ำอีก


1

ฉันใช้sshfsเพื่อเมานต์ไดเร็กทอรีระยะไกลผ่าน ssh และปิดเพื่อคัดลอกไฟล์:

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

จากนั้นใน python:

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

วิธีนี้มีข้อดีคือคุณสามารถสตรีมข้อมูลได้หากคุณกำลังสร้างข้อมูลแทนที่จะใช้แคชในเครื่องและส่งไฟล์ขนาดใหญ่ไฟล์เดียว


1

ลองทำเช่นนี้หากคุณไม่ต้องการใช้ใบรับรอง 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

การใช้ทรัพยากรภายนอก 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')

0

การเรียกใช้scpคำสั่งผ่านกระบวนการย่อยไม่อนุญาตให้รับรายงานความคืบหน้าภายในสคริปต์ pexpectสามารถใช้เพื่อดึงข้อมูลนั้น:

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

ดูไฟล์คัดลอก python ในเครือข่ายท้องถิ่น (linux -> linux)


0

แนวทางที่ง่ายมากมีดังต่อไปนี้:

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

ไม่จำเป็นต้องใช้ไลบรารี python (เฉพาะระบบปฏิบัติการ) และใช้งานได้อย่างไรก็ตามการใช้วิธีนี้ต้องอาศัยไคลเอ็นต์ ssh อื่นที่จะติดตั้ง ซึ่งอาจส่งผลให้เกิดพฤติกรรมที่ไม่ต้องการหากทำงานบนระบบอื่น


-3

ประเภทของการแฮ็ก แต่สิ่งต่อไปนี้ควรใช้งานได้ :)

import os
filePath = "/foo/bar/baz.py"
serverPath = "/blah/boo/boom.py"
os.system("scp "+filePath+" user@myserver.com:"+serverPath)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.