【问题标题】:How copy files recursively to another folder but without copying the source folder?如何递归地将文件复制到另一个文件夹但不复制源文件夹?
【发布时间】:2022-10-14 02:28:20
【问题描述】:

我有一个脚本,它根据扩展名递归地将文件从一个文件夹复制到另一个文件夹,并且在 Dst 位于 Src 之前它工作正常。让我解释:

如果我将 .txt 文件发送到 Src 中的子文件夹,该子文件夹将自行复制。有什么办法可以防止这种情况发生吗?我尝试过中断,但它不会复制所有文件,因为当满足 source_fn 条件时,函数会停止。当 source_fn == Dst 不满足时,脚本将 Dst 复制回自身。我知道这听起来有点令人困惑,我希望你能帮助我。

这是 Src 的样子:

My documents
--- file.txt
--- folder (subfolder)
    --- file2.txt
--- Text_files (subfolder) Dst

如果我希望脚本仅将 .txt 文件从 Src(我的文档)复制到 Dst(Text_Files)递归地维护文件夹层次结构,这就是它当前的工作方式:

My documents
--- file.txt
--- folder (subfolder)
    --- file2.txt
--- Text_files (subfolder) Dst
    ---file.txt
    ---Text_files (Subfolder)
       --- folder (subfolder)
           --- file2.txt
       ---file.txt
       --- folder (subfolder)
           --- file2.txt

正如它应该看起来的那样:

My documents (Src)
---file.txt
--- Text_files (Subfolder) Dst
      ---file.txt
--- folder (subfolder)
    --- file2.txt

脚本:

import os
import winshell

def copywf1(Src, Dst, extensions)

        try:

            extensions = .txt .png

            for item in os.listdir(Src):
                source_fn = os.path.join(Src, item)

                if os.path.isdir(source_fn):
                    copywf1(source_fn, os.path.join(Dst, item), 
                    extensions, overwriten)

                elif os.path.splitext(item)[1] in extensions:

                    if not os.path.exists(Dst):
                        os.makedirs(Dst)

                        winshell.copy_file(source_fn, os.path.join(Dst, item), 
                        allow_undo=True, no_confirm=False, rename_on_collision=True, 
                        silent=False, hWnd=None)

        except winshell.x_winshell:
            pass

更新:

我已经设法让它在某种程度上起作用,现在我不创建二级文件夹,我只是复制一级子文件夹。我试图放置

if not os.path.exists(Dst):
     os.makedirs(Dst)

在不同的地方,但它没有效果。

import os
import shutil


def copywf1(Src, Dst, extensions):

    try:

        for item in os.listdir(Src):
            source_fn = os.path.join(Src, item)
            print(source_fn, Dst)

            if os.path.isdir(source_fn):
                split_dst = os.path.normpath(Dst).split(os.sep)
                if not all([path in split_dst for  path in os.path.normpath(source_fn).split(os.sep)]):
                    copywf1(source_fn, os.path.join(Dst, item), extensions)

            elif os.path.splitext(item)[1] in extensions:

                if not os.path.exists(Dst):
                    os.makedirs(Dst)

                shutil.copyfile(source_fn, os.path.join(Dst, item))

    except Exception as e:
        print(e)

copywf1(r"SRC", r"DST", ".extension")

【问题讨论】:

  • 欢迎来到堆栈溢出。 “我知道这听起来有点混乱,我希望你能帮助我。”那isn't answerable,但我至少可以更清楚地解释How to Ask。你能举一个例子:源文件夹中有什么当出现问题时,以及复制之前 dst 文件夹中的内容;然后解释应该发生什么(即,dst 文件夹之后应该是什么样子)和相反会发生什么(出了什么问题?它是否陷入了循环?是否有错误的文件?文件在错误的位置?错误消息?还有别的吗?)
  • 你是对的,我已经编辑了我的帖子,使其更清晰、更准确。
  • 为了更好的便携性,请使用操作系统模块而不是赢壳.由于语法错误,显示的代码无法运行
  • 啊,所以问题是因为Dst已经在里面Src?我看到代码已经手动使用递归来遍历目录树。您是否考虑过编写代码查看如果将递归到的文件夹与目标文件夹相同,如果是则跳过它?
  • 问题是如果 Dst 是 Src 的子文件夹,它会复制自己,它不会无限期地这样做,但正如我在示例中所说,它至少会这样做一次。这个想法是它不会复制自己。

标签: python python-3.x


【解决方案1】:

你写了

def copywf1(Src, Dst, extensions)
        try:
            extensions = .txt .png

是的,那是行不通的,是吗?

分配一个集合,而不是语法错误:

            extensions = {'.txt', '.png'}

并使用更短的签名 - 没有必要问 如果你要无条件地传递它的调用者 覆盖它。

您可以使用retrieve 给定文件的扩展名

            root, ext = os.path.splitext(filepath)

然后问

            if ext in extensions:

有条件地复制所需的文件类型。


您的代码具有递归调用:

                if os.path.isdir(source_fn):
                    copywf1( ... )

太好了,给你更多的力量!如果你能让它工作。 这对我来说并不明显。 我没有弄明白它保持的路径长度不变量。 此外,您的代码传递了 4 个参数,但签名只接受 3 个。

这是一个更简单的方法,使用标准os.walk() 它为你递归到树上:

        for root, dirs, files in os.walk(Src):
            ...

拼写 nit:pep-8 要求您将其小写:src


当你让这个工作令你满意时, 请do 发布最终代码。

【讨论】:

    【解决方案2】:

    如果我将 .txt 文件发送到 Src 中的子文件夹,该子文件夹将自行复制。有什么办法可以防止这种情况发生吗?

    有一种方法可以通过“开箱即用”的思考来解决此类问题。与其寻找解决检测到的问题的方法,不如更改使用的方法。例如,通过拆分搜索应该从实际复制它们的部分复制的文件的部分,例如像这样:

    • 首先收集要在列表中复制的所有文件的路径

    • 然后遍历之前创建的列表的所有元素以实际复制文件

    将任务拆分为这两个步骤有助于避免复制文件的文件夹树中的目标文件夹出现潜在问题。如果目标文件夹中已经有文件并且您没有在第一步中将它们过滤掉,您可以在第一步和第二步之间的中间步骤中从列表中删除目标文件夹路径下的所有文件。

    一般说:避免通过删除或添加结构中的项目(如文件系统或 Python 列表或其他类型的包含多个项目的对象)来遍历或循环或递归进行更改。

    它将帮助您将来避免像您刚刚遇到的此类问题。

    下面是关于如何分两步对任务进行编码的建议:

    # First Step: 
    def osWalkFiles(fullPathDir, fileExtensions=("",)):
        import os
        files = []
        osWalkLLists = [
            [ os.path.join(root, fname) 
                  for fname in files if fname.endswith(fileExtensions) ] 
            for root, dirs, files in os.walk(fullPathDir) ]
        for item in osWalkLLists: files += item
        return files
    #:def
    fileExtensions = (".png", ".txt")
    lstFiles = osWalkFiles(fullPathSrcDir, fileExtensions)
    
    # Second step: 
    def shutilCpyFiles(lstFullPathFileNames, fullPathSrcDir, fullPathTgtDir):
        import os
        import shutil
        for srcFileName in lstFullPathFileNames:
            assert fullPathSrcDir in srcFileName
            tgtFileName = srcFileName.replace(fullPathSrcDir, fullPathTgtDir)
            try: 
                shutil.copyfile(srcFileName, tgtFileName)
                print(f'copyfile {srcFileName=} to 
             {tgtFileName=}')
            except FileNotFoundError:
                print(f'creating directory {os.path.split(tgtFileName)[0]}')
                os.mkdir(os.path.split(tgtFileName)[0])
                shutil.copyfile(srcFileName, tgtFileName)
                print(f'copyfile {srcFileName=} to 
             {tgtFileName}=')
    #:def
    shutilCpyFiles(lstFiles, fullPathSrcDir, fullPathTgtDir)
    
    

    【讨论】:

    • 非常感谢您的回答。我尝试了另一种方法,因为无论我多么努力,如果文件已经存在,我无法让您的脚本正常替换文件,因为复制问题再次出现。我尝试了一种不同的方法,但我不这么认为,它将文件复制到第二级文件夹。我已经根据这个答案更新了我的问题。