跳转至

6.16.shutil

import shutil
import os
from toolkit import H

复制文件

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

copyfile() 复制源文件的内容到目标位置,如果没有创建新文件的权限将会引发 IOError,如果dst已存在,则会被替换。srcdst 不能是目录。一些特殊的文件(例如 Unix 设备)是不能使用它进行复制的,因为 copyfile() 不管文件的类型如何,都会打开进行读取。

shutil.copyfileobj(fsrc, fdst, length=16384)

copyfile() 是调用低级方法 copyfileobj() 实现的。但传入 copyfile() 的参数是文件名称,而传入 copyfileobj() 是打开的文件描述符。可选的第三个参数是用来设置读取块的大小。默认行为是使用大块读取。使用 -1 一次性读取所有输入,或者使用一个正整数设定一个具体的区块大小。下面的示例代码使用了不同的块大小展示效果。

import io

class VerboseStringIO(io.StringIO):
    def read(self, n=-1):
        next = io.StringIO.read(self, n)
        print(f'read({n}) got {len(next)} bytes')
        return next

lorem_ipsum = '''Lorem ipsum dolor sit amet, consectetuer
adipiscing elit.  Vestibulum aliquam mollis dolor. Donec
vulputate nunc ut diam. Ut rutrum mi vel sem. Vestibulum
ante ipsum.
'''

print('Default:')
input = VerboseStringIO(lorem_ipsum)
output = io.StringIO()
shutil.copyfileobj(input, output)

print()

print('All at once:')
input = VerboseStringIO(lorem_ipsum)
output = io.StringIO()
shutil.copyfileobj(input, output, -1)

print()

print('Blocks of 256:')
input = VerboseStringIO(lorem_ipsum)
output = io.StringIO()
shutil.copyfileobj(input, output, 100)
Default:
read(16384) got 167 bytes
read(16384) got 0 bytes

All at once:
read(-1) got 167 bytes
read(-1) got 0 bytes

Blocks of 256:
read(100) got 100 bytes
read(100) got 67 bytes
read(100) got 0 bytes
shutil.copy(src, dst, follow_symlinks=True)

复制一个文件到目标路径,如果目标路径是一个目录,而不是文件,那么将会在这个目录中创建一个与源文件同名的新文件。copy()函数实际上是先后调用了 copyfile()函数和copymode()函数,因此文件的权限将会随着内容一起复制。copy() 函数会像 Unix 命令行工具 cp 那样打印出新文件的路径。

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

copy2() 类似 copy() ,但先后调用的是 copyfile()函数和copystat()函数,因此在复制时会包含元数据中的访问和修改时间。但新旧文件的权限可能不一样,因为默认情况下,Unix 上创建一个新文件的时候,它的权限依赖于当前用户的 umask。如果要复制文件权限,请使用 copymode()

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

从src复制权限位到dst。该文件的内容,所有者和组不受影响。

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

从src复制权限位、最后访问时间、最后修改时间、文件的flags到dst。该文件的内容,所有者和组不受影响。在 Linux 中,copystat 会在可能的情况下复制"extended attributes",

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

把 src 目录复制到 dst,src 和 dst 都只能是目录,且 dst 必须不存在。

symlinks 参数控制符号链接是被复制为链接还是文件。默认为False,会将符号链接指向的文件复制到 dst,因此,如果符号链接指向的文件已经不存在,则会引发异常。如果这个参数是 true ,将会在 dst 中创建新的符号链接。

ignore_dangling_symlinks 参数为True时,如果 src 存在损坏的 symbolic link,也不会抛出异常。

copytree() 函数使用两个回调参数控制它的行为。ignore 参数在每个目录或者子目录以及目录中内容被复制时调用,它返回一个应该被复制的内容列表。copy_function 参数用于在文件实际复制时调用。

import glob
import pprint
import shutil

def verbose_copy(src, dst):
    print('copying\n {!r}\n to {!r}'.format(src, dst))
    return shutil.copy2(src, dst)

print('BEFORE:')
pprint.pprint(glob.glob('/tmp/example/*'))
print()

shutil.copytree(
    '../shutil', '/tmp/example',
    copy_function=verbose_copy,
    ignore=shutil.ignore_patterns('*.py'),
)

print('\nAFTER:')
pprint.pprint(glob.glob('/tmp/example/*'))

这个例子中,ignore_patterns() 用于去创建一个忽略方法跳过 Python 源文件。verbose_copy() 打印复制的文件名称然后调用 copy2() 复制,它是默认的复制方法。

删除文件

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

错误默认情况下引发为异常,但是如果第二个参数为 true 将被忽略,也可以通过第三个参数提供一个错误处理方法。

移动文件

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

原理类似于 Unix 命令 mv 。如果源文件和目标文件都存在,源文件将会被重命名。否则源文件被复制到目的地然后被删除。

查找文件

shutil.which(cmd, mode=1, path=None)

which() 方法会按照一个搜索路径查找文件。典型的使用场景是在环境变量 PATH 定义的路径中查找可执行程序的位置。如果没有找到文件,which() 返回 None

print(shutil.which("cmd"))
C:\WINDOWS\system32\cmd.EXE

which() 方法接收参数依据文件权限以及搜索路径进行过滤。path 参数默认是 os.environ('PATH'),但是可以是任何由 os.pathsep 分隔的字符串。mode 参数应该是一个匹配文件权限的位掩码。默认情况下查找可执行文件。下列的例子使用了可读掩码以及一个搜索路径去查找配置文件。

path = os.pathsep.join([
    '.',
    os.path.expanduser('~/pymotw'),
])

mode = os.F_OK | os.R_OK
filename = shutil.which('config.ini', mode=mode, path=path)

解压缩

Python 标准库包含了许多模块用于管理压缩文件,例如 tarfile 和 zipfile。shutil 模块中也有几个高阶方法用于解压缩文件。

shutil 模块维护了一个可在当前系统上压缩、解压缩的格式注册表,通过get_archive_formats()get_unpack_formats() 访问。支持的格式依赖于哪些模块和底层库可用。

shutil.get_archive_formats()
[('bztar', "bzip2'ed tar-file"),
 ('gztar', "gzip'ed tar-file"),
 ('tar', 'uncompressed tar file'),
 ('xztar', "xz'ed tar-file"),
 ('zip', 'ZIP file')]

解压缩的注册表不同于创建压缩文件的注册表,因为它还包括用于每种格式的常用文件扩展名,以便解压方法根据文件扩展名猜测要使用的格式。

shutil.get_unpack_formats()
[('bztar', ['.tar.bz2', '.tbz2'], "bzip2'ed tar-file"),
 ('gztar', ['.tar.gz', '.tgz'], "gzip'ed tar-file"),
 ('tar', ['.tar'], 'uncompressed tar file'),
 ('xztar', ['.tar.xz', '.txz'], "xz'ed tar-file"),
 ('zip', ['.zip'], 'ZIP file')]

也可以使用 register_archive_format()register_unpack_format() 函数注册自定义的压缩、解压缩函数。

shutil.register_archive_format(name, function, extra_args=None,
                               description='')
shutil.register_unpack_format(name, extensions, function, extra_args=None,
                              description='')

或者使用 unregister_archive_format()unregister_unpack_format() 函数取消注册压缩、解压缩函数。

shutil.unregister_archive_format(name)
shutil.unregister_unpack_format(name)

可以使用 make_archive() 创建一个新的压缩文件。make_archive() 的参数似乎有点混乱,可以参考以下简单的用法。

shutil.make_archive(base_name, format, root_dir=None, base_dir=None,
                    verbose=0, dry_run=0, owner=None, group=None, logger=None)
import os, shutil
def make_archive(source, destination):
        base = os.path.basename(destination)
        name = base.split('.')[0]
        format = base.split('.')[1]
        archive_from = os.path.dirname(source)
        archive_to = os.path.basename(source.strip(os.sep))
        print(source, destination, archive_from, archive_to)
        shutil.make_archive(name, format, archive_from, archive_to)
        shutil.move('%s.%s'%(name,format), destination)

make_archive('/path/to/folder', '/path/to/folder.zip')

https://www.thinbug.com/q/45245079

使用 unpack_archive() 解压文件,传入压缩文件名以及可选的解压目录,默认是当前目录。

shutil.unpack_archive(filename, extract_dir=None, format=None)

filename 是压缩档案的名称。extract_dir 是解压的目标目录。如果未提供,则使用当前工作目录。format 是压缩档案格式:zip 、tar、gztar、bztar、xztar 其中之一,或着任何其他的注册格式。如果未提供,unpack_archive 将使用文件扩展名,并查看是否为该扩展名注册了解压程序。如果没有找到,则会引发ValueError

文件系统空间

disk_usage() 返回一个元组表示系统总空间,当前使用总量以及剩余总量,其单位为字节。

shutil.disk_usage(path)
import shutil

total_b, used_b, free_b = shutil.disk_usage('.')

gib = 2 ** 30  # GiB == gibibyte
gb = 10 ** 9   # GB == gigabyte

print('Total: {:6.2f} GB  {:6.2f} GiB'.format(
    total_b / gb, total_b / gib))
print('Used : {:6.2f} GB  {:6.2f} GiB'.format(
    used_b / gb, used_b / gib))
print('Free : {:6.2f} GB  {:6.2f} GiB'.format(
    free_b / gb, free_b / gib))
Total: 479.88 GB  446.92 GiB
Used : 318.18 GB  296.33 GiB
Free : 161.69 GB  150.59 GiB

获取终端窗口的大小

shutil.get_terminal_size(fallback=(80, 24))

忽略函数

shutil.ignore_patterns(patterns)

ignore_patterns 返回一个函数,可以用作 copytree() 的函数的 ignore 参数。以下表示忽略 python 文件。

shutil.ignore_patterns('*.py')
<function shutil.ignore_patterns.<locals>._ignore_patterns(path, names)>