在Python中递归地复制文件或目录

156

Python似乎有用于复制文件的函数(例如shutil.copy)和用于复制目录的函数(例如shutil.copytree),但我没有找到处理两者的函数。当然,检查你想要复制的是文件还是目录很简单,但这似乎是一个奇怪的遗漏。

真的没有标准函数可以像Unix的cp -r命令一样工作,即支持目录和文件,并进行递归复制吗?在Python中解决这个问题最优雅的方法是什么?


3
是的,这很混乱。Python试图反映底层系统调用的地方之一,导致了可见接口变得更糟。虽然在复制文件和复制树之间切换并不难,但本来不应该这样做。也许可以在Python错误跟踪器上提交一个增强请求,以允许copytree复制单个文件? - bobince
我认为copy_tree是你正在寻找的。 - algorythms
6个回答

191

我建议您首先调用shutil.copytree,如果抛出异常,则重试使用shutil.copy

import shutil, errno

def copyanything(src, dst):
    try:
        shutil.copytree(src, dst)
    except OSError as exc: # python >2.5
        if exc.errno in (errno.ENOTDIR, errno.EINVAL):
            shutil.copy(src, dst)
        else: raise

31
我认为使用os.path.isdir(src)检查src是否为目录会更加简洁,而不是像这样捕获异常。或者有什么特殊原因需要在这里使用异常吗? - pafcu
44
  1. 因为在Python世界中,EAFP(宁愿请求宽恕,而不是事先征得同意)优于LBYL(先看一眼再跳)。如果这对您来说是新的,请告诉我,我可以提供相关链接。2) 库函数已经间接地检查了这一点,那么为什么要重复检查呢?3) shutil.copytree 函数未来可以改进和管理两种情况,没有任何限制。4) 在Python中,异常并不那么特殊;例如迭代通过引发 StopIteration 异常停止。
- tzot
3
在这种情况下,处理异常需要6行代码,而检查类型只需要4行代码。虽然不多,但最终会累加起来。另外,正如你所说,copytree将来很可能也会支持文件。但是无法确定它的实现方式。也许在某些情况下,copytree会抛出一个异常,而copy函数则不会?那么我的代码就会因为添加的功能而突然停止工作。但你可能是对的,异常在Python中相当普遍,这是我觉得非常恼人的事情,但这可能是因为我似乎从来没有习惯它。 - pafcu
5
实际上,在这种情况下,异常确实具有一个明显的优点:在检查和调用正确函数之间,类型发生变化是完全可能的(尽管高度不太可能)。 - pafcu
9
个人认为,在任何语言中,将核心功能添加到except块中是不好的做法。这会把功能放在许多开发者不会搜索的地方。此外,如果您不写注释,一位经验较少的Python开发者可能无法真正理解重试的目的。如果您需要为像这样微不足道的事情添加注释,那么您的代码风格可能存在问题。最后,编写if/else语句将导致更易于阅读的代码。 - this.myself
显示剩余7条评论

13

Tzotgns的回答基础上,这里提供一种递归复制文件和文件夹的替代方法。(Python 3.X)

import os, shutil

root_src_dir = r'C:\MyMusic'    #Path/Location of the source directory
root_dst_dir = 'D:MusicBackUp'  #Path to the destination folder

for src_dir, dirs, files in os.walk(root_src_dir):
    dst_dir = src_dir.replace(root_src_dir, root_dst_dir, 1)
    if not os.path.exists(dst_dir):
        os.makedirs(dst_dir)
    for file_ in files:
        src_file = os.path.join(src_dir, file_)
        dst_file = os.path.join(dst_dir, file_)
        if os.path.exists(dst_file):
            os.remove(dst_file)
        shutil.copy(src_file, dst_dir)

如果这是您第一次操作并且不知道如何递归地复制文件和文件夹,我希望这可以帮到您。


6

shutil.copyshutil.copy2是用于复制文件的。

shutil.copytree用于复制一个包含所有文件和子文件夹的文件夹。 shutil.copytree使用shutil.copy2来复制文件。

因此,你所说的cp -r的类比是shutil.copytree,因为cp -r会选择并复制一个文件夹及其文件/子文件夹,就像shutil.copytree一样。 如果没有-r,那么cp会像shutil.copyshutil.copy2一样只复制文件。


1
我认为你没有理解问题。尝试使用shutil.copytree('C:\myfile.txt', 'C:\otherfile')。它不起作用。这就是OP在7年前所问的问题... - Jean-François Corbett
当然它不起作用。就像cp不能处理文件夹一样。你需要一个特殊的选项。copy和copytree是处理复制的最佳方式。如果copytree可以针对文件进行操作,那么犯错误就很容易了。你必须知道你在Linux和Python中要操作什么。这很困难。此外,有人在这里发表了评论,但看到问题和回复,无法抵制回答。优雅的方法是知道你想做什么,而不是没有任何控制的通用复制。 - gms

4
到目前为止,我发现最快、最优雅的方法是使用distutils.dir_util本地包的copy_tree函数: copy_treedistutils.dir_util
import distutils.dir_util
from_dir = "foo/bar"
to_dir = "truc/machin"
distutils.dir_util.copy_tree(from_dir, to_dir)

它是否支持复制文件,如果 from_dir = files.txt?请看问题。srcdst 必须是目录名。如果 src 不是目录,则引发 DistutilsFileError - Muhammad Yasirroni

1

Unix的cp命令不支持同时复制目录和文件:

betelgeuse:tmp james$ cp source/ dest/
cp: source/ is a directory (not copied).

为了让cp复制一个目录,你必须手动告诉cp它是一个目录,使用'-r'标志。
然而这里存在一些不连贯的地方 - 当传递一个文件名作为源时,cp -r会愉快地只复制单个文件; 但copytree不会。

2
http://docs.python.org/library/shutil.html 包含了 copytree() 的代码,演示了如何处理普通文件、符号链接和目录。 - James Polley
1
这个回答并没有解决问题,应该是一条评论而不是一个回答。 - Jean-François Corbett

-2

Python的shutil.copytree方法很混乱。我写了一个可以正确工作的方法:

def copydirectorykut(src, dst):
    os.chdir(dst)
    list=os.listdir(src)
    nom= src+'.txt'
    fitx= open(nom, 'w')

    for item in list:
        fitx.write("%s\n" % item)
    fitx.close()

    f = open(nom,'r')
    for line in f.readlines():
        if "." in line:
            shutil.copy(src+'/'+line[:-1],dst+'/'+line[:-1])
        else:
            if not os.path.exists(dst+'/'+line[:-1]):
                os.makedirs(dst+'/'+line[:-1])
                copydirectorykut(src+'/'+line[:-1],dst+'/'+line[:-1])
            copydirectorykut(src+'/'+line[:-1],dst+'/'+line[:-1])
    f.close()
    os.remove(nom)
    os.chdir('..')

1
这段代码适用于检查单个文件(检查覆盖问题),但对于二进制文件(如“zip”)无效。为什么不使用简单的Python文件复制,而不是逐行读写? - notilas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接