# 11.10. `shutil` - 高级文件操作

源代码： Lib / shutil.py

`shutil`模块提供了对文件和容器文件的一些高级操作。特别地，提供了支持文件复制和移除的功能。有关单个文件的操作，另请参见`os`模块。

警告

即使较高级别的文件复制功能（`shutil.copy()`，`shutil.copy2()`）也无法复制所有文件元数据。

在POSIX平台上，这意味着文件所有者和组会丢失以及ACL。在Mac OS上，不使用资源分支和其他元数据。这意味着资源将丢失，文件类型和创建者代码将不正确。在Windows上，不复制文件所有者，ACL和备用数据流。

## 11.10.1.目录和文件操作

`shutil.``copyfileobj`(fsrc, fdst[, length])

  将类似文件的对象fsrc的内容复制到类似文件的对象fdst。整数length（如果给出）是缓冲区大小。具体地，负的长度值意味着复制数据而不使块中的源数据循环；默认情况下，以块为单位读取数据，以避免不受控制的内存消耗。请注意，如果fsrc对象的当前文件位置不为0，则只会复制当前文件位置到文件末尾的内容。

`shutil.``copyfile`(src, dst, *, follow_symlinks=True)

  将名为src的文件的内容（无元数据）复制到名为dst的文件，然后返回dst。src和dst是以字符串形式给出的路径名称。dst必须是完整的目标文件名；请查看`shutil.copy()`以获取接受目标目录路径的副本。如果src和dst指定相同的文件，则会引发`SameFileError`。

  目标位置必须可写；否则，将引发`OSError`异常。如果dst已经存在，它将被替换。使用此功能无法复制特殊文件，例如字符或块设备和管道。

  如果follow_symlinks为假且src是符号链接，则将创建一个新的符号链接，而不是复制src指向的文件。

  在版本3.3中更改： `IOError`已升级，而不是`OSError`。添加了follow_symlinks参数。现在返回dst。

  在版本3.4中更改：提高`SameFileError`，而不是`Error`。因为前者是后者的子类，所以这种变化是向后兼容的。

exception `shutil.``SameFileError`

  如果`copyfile()`中的源和目标是相同的文件，则会引发此异常。

  版本3.4中的新功能。

`shutil.``copymode`(src, dst, *, follow_symlinks=True)

  将权限位从src复制到dst。文件内容，所有者和组不受影响。src和dst是以字符串形式给出的路径名称。If follow_symlinks is false, and both src and dst are symbolic links, `copymode()` will attempt to modify the mode of dst itself (rather than the file it points to). 此功能不适用于每个平台；请参阅`copystat()`了解更多信息。如果`copymode()`无法修改本地平台上的符号链接，并且要求这样做，它将不会执行任何操作并返回。

  在版本3.3中已更改：添加了follow_symlinks参数。

`shutil.``copystat`(src, dst, *, follow_symlinks=True)

  将权限位，最后访问时间，上次修改时间和标志从src复制到dst。在Linux上，`copystat()`也会尽可能复制“扩展属性”。文件内容，所有者和组不受影响。src和dst是以字符串形式给出的路径名称。

  If follow_symlinks is false, and src and dst both refer to symbolic links, `copystat()` will operate on the symbolic links themselves rather than the files the symbolic links refer to–reading the information from the src symbolic link, and writing the information to the dst symbolic link.

  注意

  并非所有平台都提供检查和修改符号链接的能力。Python本身可以告诉你本地可用的功能。

  - 如果 os.supports_follow_symlinks中的`os.chmod 是`True`，`copystat()`可以修改符号链接的权限位。`
  - 如果 t> os.supports_follow_symlinks中的`os.utime 是`True`，`copystat()`可以修改符号链接的最后访问和修改时间。`
  - 如果 t> os.supports_follow_symlinks中的`os.chflags 是`True`，`copystat()`可以修改符号链接的标志。`（`os.chflags`不适用于所有平台。）

  在某些或所有功能不可用的平台上，当要求修改符号链接时，`copystat()`将复制所有可能的内容。`copystat()`从不会返回失败。

  有关详细信息，请参阅`os.supports_follow_symlinks`。

  在版本3.3中已更改：添加了follow_symlinks参数并支持Linux扩展属性。

`shutil.``copy`(src, dst, *, follow_symlinks=True)

  将文件src复制到文件或目录dst。src和dst应为字符串。如果dst指定目录，则文件将使用src的基本文件名复制到dst中。返回新创建的文件的路径。

  如果follow_symlinks为false，并且src是符号链接，则dst将创建为符号链接。如果follow_symlinks为true且src是符号链接，则dst将是文件的副本src 。

  `copy()`复制文件数据和文件的权限模式（请参阅`os.chmod()`）。其他元数据（如文件的创建和修改时间）不会保留。要保留原始文件的所有文件元数据，请改用`copy2()`。

  在版本3.3中已更改：添加了follow_symlinks参数。现在返回新创建的文件的路径。

`shutil.``copy2`(src, dst, *, follow_symlinks=True)

  与`copy()`相同，但`copy2()`也尝试保留所有文件元数据。

  当follow_symlinks为false，且src是符号链接时，`copy2()`尝试从src t5 >符号链接到新创建的dst符号链接。但是，此功能不适用于所有平台。在某些或所有功能不可用的平台上，`copy2()`将保留其可用的所有元数据； `copy2()`从不会返回失败。

  `copy2()`使用`copystat()`复制文件元数据。有关修改符号链接元数据的平台支持的详细信息，请参阅`copystat()`。

  在版本3.3中更改：添加了follow_symlinks参数，尝试复制扩展文件系统属性（当前仅限于Linux）。现在返回新创建的文件的路径。

`shutil.``ignore_patterns`(*patterns)

  此工厂函数创建一个函数，可用作`copytree()`的忽略参数的可调用函数，忽略与glob类型模式。请参见下面的示例。

`shutil.``copytree`(src, dst, symlinks=False, ignore=None, copy_function=copy2, ignore_dangling_symlinks=False)

  递归地复制以src为根的整个目录树，返回目标目录。由dst命名的目标目录不能已经存在；它将被创建以及缺少父目录。使用`copystat()`复制目录的权限和时间，使用`shutil.copy2()`复制单个文件。

  如果符号链接为真，则源树中的符号链接在新树中表示为符号链接，并且原始链接的元数据将被复制到平台允许的范围内；如果为false或省略，链接文件的内容和元数据将复制到新树。

  当符号链接为false时，如果符号链接指向的文件不存在，则在`Error`异常中引发的错误列表中将添加一个异常复制过程。如果要冻结此异常，可以将可选的ignore_dangling_symlinks标志设置为true。请注意，此选项对不支持`os.symlink()`的平台没有影响。

  If ignore is given, it must be a callable that will receive as its arguments the directory being visited by `copytree()`, and a list of its contents, as returned by `os.listdir()`. 由于递归调用`copytree()`，因此对于要复制的每个目录，将调用忽略可调用对象。可调用必须返回相对于当前目录的目录和文件名的序列（即，在其第二参数中的项目的子集）；这些名称将在复制过程中被忽略。`ignore_patterns()`可用于创建基于glob样式模式忽略名称的可调用方法。

  如果发生异常，则会出现一个`Error`与一系列原因。

  如果给定copy_function，它必须是一个可调用的，将用于复制每个文件。它将使用源路径和目标路径作为参数进行调用。默认情况下，使用`shutil.copy2()`，但可以使用支持相同声明的任何函数（如`shutil.copy()`）。

  在版本3.3中更改：在符号链接为false时，复制元数据。现在返回dst。

  在版本3.2中已更改：添加了copy_function参数，以便能够提供自定义复制功能。在符号链接为假时，对静默挂起符号链接错误添加ignore_dangling_symlinks参数。

`shutil.``rmtree`(path, ignore_errors=False, onerror=None)

  删除整个目录树； 路径必须指向目录（而不是指向目录的符号链接）。如果ignore_errors为true，则删除失败导致的错误将被忽略；如果为false或省略，则通过调用onerror指定的处理程序处理这些错误，如果省略，则引发异常。

  注意

  在支持必要的基于fd的函数的平台上，默认使用`rmtree()`的符号链接攻击防御版本。在其他平台上，`rmtree()`实现容易受到符号链接攻击：在适当的时间和情况下，攻击者可以操作文件系统上的符号链接以删除他们无法访问的文件。应用程序可以使用`rmtree.avoids_symlink_attacks`函数属性来确定适用哪种情况。

  如果提供onerror，它必须是可接受三个参数的可调用：函数，路径和excinfo。

  第一个参数function是引发异常的函数；它取决于平台和实现。第二个参数path将是传递给函数的路径名。第三个参数excinfo将是`sys.exc_info()`返回的异常信息。将不会捕获onerror引发的异常。

  在版本3.3中已更改：添加了一个符合symlink攻击的版本，如果平台支持基于fd的函数，则会自动使用该版本。

  `rmtree.``avoids_symlink_attacks`

    指示当前平台和实现是否提供`rmtree()`的符号链接攻击抵御版本。目前，这只适用于支持基于fd的目录访问功能的平台。

    版本3.3中的新功能。

`shutil.``move`(src, dst, copy_function=copy2)

  递归地将文件或目录（src）移动到另一个位置（dst），并返回目标。

  如果目标是现有目录，则src在该目录中移动。如果目标已经存在，但不是目录，根据`os.rename()`语义，它可能会被覆盖。

  如果目标位于当前文件系统上，则使用`os.rename()`。否则，使用copy_function将src复制到dst，然后删除。在符号链接的情况下，指向src的目标的新符号链接将在dst中创建或创建，src将被删除。

  如果给出copy_function，它必须是一个可调用，它接受两个参数src和dst，并且将用于复制src 到dest如果`os.rename()`不能使用。如果源是目录，则调用`copytree()`，将其传递给`copy_function()`。默认的copy_function是`copy2()`。使用`copy()`作为copy_function允许移动成功，当不可能也复制元数据，而不复制任何元数据。

  在版本3.3中已更改：为外部文件系统添加了显式符号链接处理，从而使其适应GNU的mv的行为。现在返回dst。

  在版本3.5中已更改：添加了copy_function关键字参数。

`shutil.``disk_usage`(path)

  将给定路径的磁盘使用情况统计信息作为named tuple返回总计，使用和免费是总的，已用和可用空间的量，以字节为单位。

  版本3.3中的新功能。

  可用性：Unix，Windows。

`shutil.``chown`(path, user=None, group=None)

  更改给定路径的所有者用户和/或组。

  用户可以是系统用户名或uid；这同样适用于组。至少需要一个参数。

  另请参见底层函数`os.chown()`。

  可用性：Unix。

  版本3.3中的新功能。

`shutil.``which`(cmd, mode=os.F_OK | os.X_OK, path=None)

  返回可执行文件的路径，如果给定的cmd被调用，它将运行。如果不调用cmd，则返回`None`。

  mode是传递给`os.access()`的权限掩码，默认情况下确定文件是否存在和可执行。

  当未指定路径时，将使用`os.environ()`的结果，返回“PATH”值或返回`os.defpath`

  在Windows上，当前目录始终位于路径之前，无论是使用默认路径还是提供自己的路径，这是命令shell在查找可执行文件时使用的行为。此外，在路径中找到cmd时，会检查`PATHEXT`环境变量。For example, if you call `shutil.which("python")`, `which()` will search `PATHEXT` to know that it should look for `python.exe` within the path directories. 例如，在Windows上：

      >>> shutil.which("python")
      'C:\\Python33\\python.EXE'

  版本3.3中的新功能。

exception `shutil.``Error`

  此异常收集在多文件操作期间引发的异常。对于`copytree()`，异常参数是3元组的列表（srcname，dstname，异常） 。

### 11.10.1.1. copytree示例

这个例子是上面描述的`copytree()`函数的实现，省略了docstring。它演示了此模块提供的许多其他功能。

    def copytree(src, dst, symlinks=False):
        names = os.listdir(src)
        os.makedirs(dst)
        errors = []
        for name in names:
            srcname = os.path.join(src, name)
            dstname = os.path.join(dst, name)
            try:
                if symlinks and os.path.islink(srcname):
                    linkto = os.readlink(srcname)
                    os.symlink(linkto, dstname)
                elif os.path.isdir(srcname):
                    copytree(srcname, dstname, symlinks)
                else:
                    copy2(srcname, dstname)
                # XXX What about devices, sockets etc.?
            except OSError as why:
                errors.append((srcname, dstname, str(why)))
            # catch the Error from the recursive copytree so that we can
            # continue with other files
            except Error as err:
                errors.extend(err.args[0])
        try:
            copystat(src, dst)
        except OSError as why:
            # can't copy file access times on Windows
            if why.winerror is None:
                errors.extend((src, dst, str(why)))
        if errors:
            raise Error(errors)

使用`ignore_patterns()`帮助程序的另一个示例：

    from shutil import copytree, ignore_patterns

    copytree(source, destination, ignore=ignore_patterns('*.pyc', 'tmp*'))

这将复制除`.pyc`文件和名称以`tmp`开头的文件或目录之外的所有内容。

使用ignore参数添加日志调用的另一个示例：

    from shutil import copytree
    import logging

    def _logpath(path, names):
        logging.info('Working in %s' % path)
        return []   # nothing will be ignored

    copytree(source, destination, ignore=_logpath)

### 11.10.1.2. rmtree示例

此示例显示如何在Windows上删除目录树，其中一些文件的只读位集已设置。它使用onerror回调来清除readonly位并重新尝试删除。任何后续故障都会传播。

    import os, stat
    import shutil

    def remove_readonly(func, path, _):
        "Clear the readonly bit and reattempt the removal"
        os.chmod(path, stat.S_IWRITE)
        func(path)

    shutil.rmtree(directory, onerror=remove_readonly)

## 11.10.2.归档操作

版本3.2中的新功能。

还提供了用于创建和读取压缩和归档文件的高级实用程序。它们依赖于`zipfile`和`tarfile`模块。

`shutil.``make_archive`(base_name, format[, root_dir[, base_dir[, verbose[, dry_run[, owner[, group[, logger]]]]]]])

  创建归档文件（例如zip或tar）并返回其名称。

  base_name是要创建的文件的名称，包括路径，减去任何特定于格式的扩展名。format is the archive format: one of “zip”, “tar”, “bztar” (if the `bz2` module is available), “xztar” (if the `lzma` module is available) or “gztar”.

  root_dir是将成为归档的根目录的目录；例如，在创建归档之前，我们通常将chdir插入到root_dir中。

  base_dir是我们开始归档的目录；即：base_dir将是归档中所有文件和目录的公共前缀。

  root_dir和base_dir都默认为当前目录。

  如果dry_run为true，则不创建归档，但将执行的操作记录到logger。

  所有者和组用于创建tar存档。默认情况下，使用当前所有者和组。

  logger必须是与 PEP 282兼容的对象，通常为`logging.Logger`的实例。

  verbose参数未使用且已弃用。

  在3.5版本中进行了更改：添加了对xztar格式的支持。

`shutil.``get_archive_formats`()

  返回支持的归档格式列表。返回序列的每个元素都是元组`（名称， 描述）`。

  默认情况下，`shutil`提供以下格式：

  - gztar：gzip的tar文件
  - bztar：bzip2'ed tar文件（如果`bz2`模块可用）。
  - xztar：xz'ed tar文件（如果`lzma`模块可用）。
  - tar：未压缩的tar文件
  - zip：ZIP文件

  您可以使用`register_archive_format()`注册新格式或为任何现有格式提供自己的存档。

`shutil.``register_archive_format`(name, function[, extra_args[, description]])

  为格式名称注册档案库。

  函数是将用于分拆归档的可调用项。可调用程序将接收要创建的文件的base_name，然后是base_dir（默认为`os.curdir`）以开始归档。Further arguments are passed as keyword arguments: owner, group, dry_run and logger (as passed in `make_archive()`).

  如果给定，extra_args是将被用作额外关键字参数的`（名称， 值） 当使用archiver callable时。`

  description由`get_archive_formats()`使用，返回归档列表。默认为空字符串。

`shutil.``unregister_archive_format`(name)

  从支持的格式列表中删除归档格式名称。

`shutil.``unpack_archive`(filename[, extract_dir[, format]])

  分拆归档。filename是归档的完整路径。

  extract_dir是解压缩归档的目标目录的名称。如果未提供，则使用当前工作目录。

  格式是存档格式：“zip”，“tar”或“gztar”之一。或使用`register_unpack_format()`注册的任何其他格式。如果未提供，`unpack_archive()`将使用存档文件扩展名，查看是否为该扩展插件注册了解包程序。如果没有找到，则会引发`ValueError`。

`shutil.``register_unpack_format`(name, extensions, function[, extra_args[, description]])

  注册分拆格式。名称是格式的名称，扩展是对应于格式的扩展名列表，如`.zip`用于Zip文件。

  函数是将用于分拆归档的可调用项。可调用程序将接收归档的路径，然后是归档必须提取到的目录。

  提供时，extra_args是将作为关键字参数传递的元组的`（名称， 值） 可调。`

  description可用于描述格式，并由`get_unpack_formats()`函数返回。

`shutil.``unregister_unpack_format`(name)

  取消注册分拆格式。name是格式的名称。

`shutil.``get_unpack_formats`()

  返回分拆的所有注册格式的列表。返回序列的每个元素都是元组`（名称， 扩展名， 描述）`。

  默认情况下，`shutil`提供以下格式：

  - gztar：gzip的tar文件
  - bztar：bzip2'ed tar文件（如果`bz2`模块可用）。
  - xztar：xz'ed tar文件（如果`lzma`模块可用）。
  - tar：未压缩的tar文件
  - zip：ZIP文件

  您可以使用`register_unpack_format()`注册新格式或为任何现有格式提供自己的解压缩程序。

### 11.10.2.1。Archiving example

在本示例中，我们创建一个gzip的tar文件归档，其中包含用户的`.ssh`目录中找到的所有文件：

    >>> from shutil import make_archive
    >>> import os
    >>> archive_name = os.path.expanduser(os.path.join('~', 'myarchive'))
    >>> root_dir = os.path.expanduser(os.path.join('~', '.ssh'))
    >>> make_archive(archive_name, 'gztar', root_dir)
    '/Users/tarek/myarchive.tar.gz'

生成的归档包含：

    $ tar -tzvf /Users/tarek/myarchive.tar.gz
    drwx------ tarek/staff       0 2010-02-01 16:23:40 ./
    -rw-r--r-- tarek/staff     609 2008-06-09 13:26:54 ./authorized_keys
    -rwxr-xr-x tarek/staff      65 2008-06-09 13:26:54 ./config
    -rwx------ tarek/staff     668 2008-06-09 13:26:54 ./id_dsa
    -rwxr-xr-x tarek/staff     609 2008-06-09 13:26:54 ./id_dsa.pub
    -rw------- tarek/staff    1675 2008-06-09 13:26:54 ./id_rsa
    -rw-r--r-- tarek/staff     397 2008-06-09 13:26:54 ./id_rsa.pub
    -rw-r--r-- tarek/staff   37192 2010-02-06 18:23:10 ./known_hosts

## 11.10.3。查询输出终端的大小

版本3.3中的新功能。

`shutil.``get_terminal_size`(fallback=(columns, lines))

  获取终端窗口的大小。

  对于两个维度中的每一个，分别检查环境变量`COLUMNS`和`LINES`。如果定义变量并且值为正整数，则使用它。

  When `COLUMNS` or `LINES` is not defined, which is the common case, the terminal connected to `sys.__stdout__` is queried by invoking `os.get_terminal_size()`.

  如果无法成功查询终端大小，或者因为系统不支持查询，或者因为我们未连接到终端，则使用`fallback`参数中给出的值。`fallback`默认为许多终端仿真器使用的默认大小`（80， 24）`。

  返回的值是类型为`os.terminal_size`的命名元组。

  另请参阅：单UNIX规范第2版，其他环境变量。