【问题标题】:Upload files using SFTP in Python, but create directories if path doesn't exist在 Python 中使用 SFTP 上传文件,但如果路径不存在则创建目录
【发布时间】:2013-01-26 23:59:41
【问题描述】:

我想用 Python 在远程服务器上上传文件。我想事先检查远程路径是否真的存在,如果不存在,则创建它。在伪代码中:

if(remote_path not exist):
    create_path(remote_path)
upload_file(local_file, remote_path)

我正在考虑在 Paramiko 中执行一个命令来创建路径(例如mkdir -p remote_path)。我想出了这个:

# I didn't test this code

import paramiko, sys

ssh = paramiko.SSHClient()
ssh.connect(myhost, 22, myusername, mypassword)
ssh.exec_command('mkdir -p ' + remote_path)
ssh.close

transport = paramiko.Transport((myhost, 22))
transport.connect(username = myusername, password = mypassword)

sftp = paramiko.SFTPClient.from_transport(transport)
sftp.put(local_path, remote_path)
sftp.close()

transport.close()

但是这个解决方案对我来说听起来不太好,因为我关闭了连接然后重新打开它。有没有更好的方法?

【问题讨论】:

标签: python ssh sftp paramiko


【解决方案1】:

【讨论】:

  • 我知道。但这是否处理不存在路径的情况?
【解决方案2】:

SFTP 支持常用的 FTP 命令(chdir、mkdir 等),因此请使用这些命令:

sftp = paramiko.SFTPClient.from_transport(transport)
try:
    sftp.chdir(remote_path)  # Test if remote_path exists
except IOError:
    sftp.mkdir(remote_path)  # Create remote_path
    sftp.chdir(remote_path)
sftp.put(local_path, '.')    # At this point, you are in remote_path in either case
sftp.close()

要完全模拟mkdir -p,您可以递归地使用remote_path:

import os.path

def mkdir_p(sftp, remote_directory):
    """Change to this directory, recursively making new folders if needed.
    Returns True if any folders were created."""
    if remote_directory == '/':
        # absolute path so change directory to root
        sftp.chdir('/')
        return
    if remote_directory == '':
        # top-level relative directory must exist
        return
    try:
        sftp.chdir(remote_directory) # sub-directory exists
    except IOError:
        dirname, basename = os.path.split(remote_directory.rstrip('/'))
        mkdir_p(sftp, dirname) # make parent directories
        sftp.mkdir(basename) # sub-directory missing, so created it
        sftp.chdir(basename)
        return True

sftp = paramiko.SFTPClient.from_transport(transport)
mkdir_p(sftp, remote_path) 
sftp.put(local_path, '.')    # At this point, you are in remote_path
sftp.close()

当然,如果 remote_path 还包含远程文件名,则需要将其拆分,将目录传递给 mkdir_p 并使用文件名而不是 '.'在 sftp.put 中。

【讨论】:

  • 它不处理不存在的父目录 (-p)。比较 os.mkdir() 与 os.makedirs()。拆分路径并在必要时进行递归调用以创建父目录
  • 在函数 mkdir_p 中没有 sftp 句柄
  • 我发现了另一个问题。上传文件时,它从主目录开始。例如,如果我想将一个文件放在 /var/www/temp/ 中,它会将其上传到 /home/user/var/www/temp/ 。有了这个更正它对我有用:if remote_directory == '/' or remote_directory == '': if remote_directory == '/': sftp_client.chdir('/') 。此外,我发现使用 os.path.split 更pythonic,也许:) remote_dirname, basename = os.path.split(remote_directory) mkdir_p(sftp_client, remote_dirname)
  • 好点。相应地更新(虽然不是 100% 确定更多 Pythonic 语句;P)
  • 您应该使用posixpath 而不是os.path 作为ftp 路径。您可以通过moving the recursive call into the exception handler 避免访问所有路径段
【解决方案3】:

一些更简单且可读性更高的东西

def mkdir_p(sftp, remote, is_dir=False):
    """
    emulates mkdir_p if required. 
    sftp - is a valid sftp object
    remote - remote path to create. 
    """
    dirs_ = []
    if is_dir:
        dir_ = remote
    else:
        dir_, basename = os.path.split(remote)
    while len(dir_) > 1:
        dirs_.append(dir_)
        dir_, _  = os.path.split(dir_)

    if len(dir_) == 1 and not dir_.startswith("/"): 
        dirs_.append(dir_) # For a remote path like y/x.txt 

    while len(dirs_):
        dir_ = dirs_.pop()
        try:
            sftp.stat(dir_)
        except:
            print "making ... dir",  dir_
            sftp.mkdir(dir_)

【讨论】:

  • +1 用于提供非递归替代方案。注意这里的“remote”输入参数是一个远程文件路径。如果您希望此函数将远程目录路径作为输入,请将“dir_, basename = os.path.split(remote)”替换为“dir_ = remote”。
  • @AlanEvangelista 感谢您的评论。更新了传递标志 is_dir 的代码。如果需要,请查看和编辑。
  • 您不应该使用except: 来检查错误。见:stackoverflow.com/a/18982771/1113207
【解决方案4】:

今天必须这样做。这是我的做法。

def mkdir_p(sftp, remote_directory):
    dir_path = str()
    for dir_folder in remote_directory.split("/"):
        if dir_folder == "":
            continue
        dir_path += r"/{0}".format(dir_folder)
        try:
            sftp.listdir(dir_path)
        except IOError:
            sftp.mkdir(dir_path)

【讨论】:

    【解决方案5】:

    假设 sftp 操作很昂贵, 我会选择:

    def sftp_mkdir_p(sftp, remote_directory):
        dirs_exist = remote_directory.split('/')
        dirs_make = []
        # find level where dir doesn't exist
        while len(dirs_exist) > 0:
            try:
                sftp.listdir('/'.join(dirs_exist))
                break
            except IOError:
                value = dirs_exist.pop()
                if value == '':
                    continue
                dirs_make.append(value)
            else:
                return False
        # ...and create dirs starting from that level
        for mdir in dirs_make[::-1]:
            dirs_exist.append(mdir)
            sftp.mkdir('/'.join(dirs_exist))```
    

    【讨论】:

      【解决方案6】:

      你可以使用pysftp包:

      import pysftp as sftp
      
      #used to pypass key login
      cnopts = sftp.CnOpts()
      cnopts.hostkeys = None
      
      srv = sftp.Connection(host="10.2.2.2",username="ritesh",password="ritesh",cnopts=cnopts)
      srv.makedirs("a3/a2/a1", mode=777)  # will happily make all non-existing directories
      

      您可以查看此链接了解更多详情: https://pysftp.readthedocs.io/en/release_0.2.9/cookbook.html#pysftp-connection-mkdir

      【讨论】:

        【解决方案7】:

        我的版本:

        def is_sftp_dir_exists(sftp, path):
            try:
                sftp.stat(path)
                return True
            except Exception:
                return False
        
        
        def create_sftp_dir(sftp, path):
            try:
                sftp.mkdir(path)
            except IOError as exc:
                if not is_sftp_dir_exists(sftp, path):
                    raise exc
        
        
        def create_sftp_dir_recursive(sftp, path):
            parts = deque(Path(path).parts)
        
            to_create = Path()
            while parts:
                to_create /= parts.popleft()
                create_sftp_dir(sftp, str(to_create))
        

        由于EAFP principle,我们先尝试mkdir而不尝试listdir/stat(发出一个网络请求也比发出多个请求更高效)。

        【讨论】:

        • 但是这样,create_sftp_dir 貌似会成功,如果目录不存在且无法创建。
        • @MartinPrikryl 你是对的,谢谢。我修复了代码,它仍然尝试在没有预先检查的情况下创建一个目录,并且仅在没有创建目录以确定原因的情况下进行检查。
        • 好的,但是现在,如果您使用 /foo/bar 调用它并且两者都存在,那么您的代码将执行四个请求,相比之下,如果您首先测试 /foo/bar 存在。
        • 顺便说一句,你确定 Path 类在 Windows 上使用时能正确处理 posix 样式的 SFTP 路径吗?
        • "将执行四个请求,与一个相比" - 准确地说 - 4 到 2(一个检查 + 一个创建)。只有当我们尝试创建一个已经存在的目录时,这才是正确的。对于许多目录不存在的情况,我们将获得更多好处更多目录不存在。请求的数量还取决于您检查事物的方式:从左到右或从右到左,这与 EAFP 无关。关于 Windows 上的 SFTP 路径的好点,谢谢,我会进一步考虑!
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-08-22
        • 2019-02-11
        • 1970-01-01
        • 2014-03-09
        • 2017-02-19
        • 2017-09-25
        • 2015-05-10
        相关资源
        最近更新 更多