文件目录操作实例2——文件目录操作
本实例可用的实际操作:文件目录批量复制移动删除,目录压缩,文件批量解压缩。
更新上篇文章的遍历操作 & os、shutil的文件目录操作实例。
基础更新:上篇遍历操作更新
上一练习虽然最后呈现出来的结果优雅,但存在一个bug:当文件夹名为 “files” 的时候,会冲突,现采用列表装文件,字典装子目录,整体用列表装。即:
[[文件1, 文件2], {“目录1”: [[子文件…], {子目录…}], “目录2”: [[子文件…], {子目录…}]}]
示例:
操作对象:a文件夹里有b文件夹和c文件,b文件夹里有d文件夹和e文件。
操作结果:[[], {‘a’: [[‘c’], {‘b’: [[‘e’], {‘d’: [[], {}]}]}]}]。
拓展更新(基于新的目录树结构):输出目录树,搜索文件——模糊/精确查询,复制、移动、删除文件或目录、压缩目录、解压缩文件(部分操作批量)
(输出目录树)示例:
操作对象:a文件夹里有b文件夹和c文件,b文件夹里有d文件夹和e文件。通过基础更新生成的文件目录关系对象。
操作结果:
└── a
├── c
└── b
├── e
└── d
- 输出目录树
- 搜索文件——模糊/精确查询
- 复制文件或目录
- 移动文件或目录
- 删除文件或目录
- 压缩目录
- 解压缩文件
- 批量操作
基础更新
基础准备
首先导入相关库:
import os # 系统库之一,此处用来处理文件、目录以及路径操作
import shutil # 高级的文件、文件夹、压缩包处理模块
实际操作
核心操作未变,所以直接贴代码:
path = os.path.abspath('practise') # E:\Python\DEMO\Study\practise(示例路径,此处可以使用绝对路径,也可以使用相对路径)
paths = [[], {}]
def loop(zp: list, lj: str = path):
"""
循环遍历目录,添加文件。
:param zp: 子目录列表
:param lj: 当前路径
:return: None
"""
lj = lj.replace('/', '\\')
assert os.path.exists(lj), ValueError('请输入正确的文件或目录路径!') # 保证源路径存在
for i in os.listdir(lj):
if os.path.isfile(f'{lj}\\{i}'):
zp[0].append(i)
elif os.path.isdir(f'{lj}\\{i}'):
zp[1].update({i: [[], {}]})
loop(zp[1][i], f'{lj}\\{i}')
loop(paths)
在这里也没有导入json库,就不打印结果了,不要问为什么,问就是我对函数正确性有迷之自信~
拓展更新
基础准备
'.'.join(file.split('.')[:-1] or file.split('.'))
- 这行代码用于获取文件
file
除了后缀其他部分;or file.split('.')
为了防止没有没有点后缀造成空值,默认获取文件名本身。 - 例1(文件名:
a.b.c
):'.'.join('a.b.c'.split('.')[:-1] or 'a.b.c'.split('.'))
返回'a.b'
。 - 例2(文件名:
a
):'.'.join('a'.split('.')[:-1] or 'a'.split('.'))
返回'a'
。
- 这行代码用于获取文件
yield
、yield from
yield
:生成器,不会等待函数执行完毕就直接生成结果。yield from
:从生成器中获取生成器,即将<generator object test at XXX>
对象变成for generator in test(): yield generator
,这可以方便我们在递归中生成元素。
os.makedirs(name, mode=0o777, exist_ok=False)
- 递归调用
os.mkdir()
;name
为路径,为必须参数。 mode
为权限模式,一个八进制数,默认全部允许读、写、执行。exist_ok=True
的时候,如果路径存在不会报错;exist_ok=False
的时候,如果路径存在会抛出FileExistsError
等异常。
- 递归调用
shutil.copy2(src, dst, *, follow_symlinks=True)
- 用于带元数据复制文件;元数据:文件的创建日期、修改日期等数据。
src
:原路径(必须)。dst
:目标路径(必须)。follow_symlinks=False
:将不会跟踪快捷方式(选用,默认为True
)。
shutil.copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, ignore_dangling_symlinks=False)
- 用于复制目录树,默认复制元数据。
src
:原路径(必须)。dst
:目标路径(必须)。symlinks
:将不会跟踪快捷方式(选用,默认为False
)。ignore
:忽略复制的文件,为一个callable
对象(选用,默认为None
)。copy_function
:复制的方法,copy
方法不复制元数据,copy2
方法复制元数据(选用,默认为copy2
)。ignore_dangling_symlinks=False
:忽略复制快捷方式(选用,默认为False
)。
shutil.move(src, dst, copy_function=copy2)
- 用于移动文件或者目录,由于移动操作是先复制再删除,所以会有复制方法的参数。
src
:原路径(必须)。dst
:目标路径(必须)。copy_function
:复制的方法,copy
方法不复制元数据,copy2
方法复制元数据(选用,默认为copy2
)。
os.remove(path, *, dir_fd)
- 用于删除文件,和
os.unlink(path, *, dir_fd)
方法一致。 path
:文件路径(必须)。dir_fd
:引用目录的文件描述符(选用,默认为None
)。
- 用于删除文件,和
shutil.rmtree(path, ignore_errors=False, onerror=None)
- 用于删除目录树;为什么不用
os.rmdir(path, *, dir_fd)
和os.removedirs(name)
?理由如下:os.rmdir(path, *, dir_fd)
只能删除空的子目录。os.removedirs(name)
可以递归删除目录树,但要求目录路径最深层子文件夹必须是空的。- 综上这两种方法太过底层,默认缺少对环境的判断,不便于使用。
path
:文件路径(必须)。dir_fd
:引用目录的文件描述符(选用,默认为None
)。
- 用于删除目录树;为什么不用
shutil.make_archive(base_name, format, root_dir=None, base_dir=None, ...)
- 用于压缩目录。
base_name
:要压缩的目录路径(必须)。format
:压缩包种类,可用shutil.get_archive_formats()
获取合法的压缩包种类(必须)。root_dir
:要压缩的路径根目录,只能指定路径(选用,默认为当前目录;优先级低于base_dir
)。base_dir
:要压缩文件的路径,可以指定路径下的文件名,也可以指定路径(选用,默认为当前目录)。
shutil.unpack_archive(filename, extract_dir=None, format=None)
- 用于解压缩文件。
filename
:要解压缩的文件路径(必须)。extract_dir
:解压到的目录路径(选用,默认为当前目录)。format
:压缩包种类(选用,默认根据压缩文件后缀获取)。
实际操作
输出目录树
用└──前缀代表同根的最后节点,其他节点用├──前缀代表;当同根非最后节点目录输出其子文件和子文件夹时,使用│代表与上文的关联,各个变量解释参见函数文档注释:
def dump(node: list, depth: int = 0, space: str = ''):
"""
打印目录树。
:param node: 子目录字典
:param depth: 递归深度
:param space: 子项前缀
:return: None
"""
dirs = list(node[1].keys()) # 获取当前目录中的所有子目录
for f in range(len(node[0])): # 遍历打印当前目录中的所有文件
if f + 1 == len(node[0]) + len(dirs):
print(f"{space}└── {node[0][f]}") # 当前文件是该目录最后一个文件或目录
else:
print(f"{space}├── {node[0][f]}") # 当前文件非最后一个文件
for d in range(len(dirs)):
if d + 1 == len(dirs):
print(f"{space}└── {dirs[d]}")
dump(node[1][dirs[d]], depth + 1, space + ' ') # 该节点为最后节点,后续节点不添加竖线
else:
print(f"{space}├── {dirs[d]}")
dump(node[1][dirs[d]], depth + 1, space + '│ ') # 在一般节点前要添加竖线
dump(paths)
搜索文件——模糊/精确查询
分两个函数实现,为了适应各种返回情况;这里采用 yield
和 yield from
生成器来动态返回查询到的所有文件路径,允许文件名模糊查询,如果有后缀,则匹配后缀是否完全一致,各个变量解释参见函数文档注释:
def _search(node: list, file: str, lj: str, directory: bool = False):
lj = lj.replace('/', '\\')
for f in node[0]:
if '.'.join(file.split('.')[:-1] or file.split('.')) in '.'.join(f.split('.')[:-1] or file.split('.')): # 不管是没有点还是有多个点,始终匹配最后一个点前或整体
if '.' in f and '.' in file:
if f.split('.')[1] == file.split('.')[1]: # 有后缀的时候判断后缀是否相等
yield f'{lj}\\{f}'
else:
yield f'{lj}\\{f}'
for d in node[1].keys():
if directory and file in d:
yield f'{lj}\\{d}'
yield from _search(node[1][d], file, f'{lj}\\{d}', directory)
def search(node: list, file: str, lj: str, directory: bool = False, detail: bool = False):
"""
打印目录树。
:param node: 子目录字典
:param file: 要模糊查询的文件(允许用点后缀表示要查找的后缀,此时点前面仍然模糊查询)或目录
:param lj: 当前路径
:param directory: 允许查询目录
:param detail: 是否精确查询
:return: 找到的文件或目录路径
"""
assert os.path.exists(lj), ValueError('请输入正确的文件或目录路径!') # 保证源路径存在
res = list(_search(node, file, lj, directory))
if res:
if detail:
return [i for i in res if file == i.split('\\')[-1]]
return res
if directory:
return f'未找到文件或目录:{file}'
return f'未找到文件:{file}'
print(search(paths, 'dark.css', path, True, True))
复制文件或目录
通过 shutil.copy2
函数复制文件以及其元数据,通过 shutil.copytree
函数复制目录以及其元数据,各个变量解释参见函数文档注释:
def copy(ori: str, src: str):
"""
复制文件或目录
:param ori: 文件或目录路径
:param src: 目标地址,目标路径不存在则自动创建
:return: 执行的结果
"""
ori = ori.replace('/', '\\')
src = src.replace('/', '\\')
assert os.path.exists(ori), ValueError('请输入正确的文件或目录路径!') # 保证源路径存在
if not os.path.isdir(src):
os.makedirs(src) # 保证目标路径存在
try:
if os.path.isdir(ori):
shutil.copytree(ori, f'{src}\\' + ori.split('\\')[-1], symlinks=True) # 带元数据复制目录
elif os.path.isfile(ori):
shutil.copy2(ori, f'{src}\\' + ori.split('\\')[-1], follow_symlinks=True) # 带元数据复制文件
return '复制成功!'
except Exception as e:
print(e)
return '复制失败!'
print(copy('E:\\Python\\DEMO\\Study\\test', 'F:\\桌面'))
移动文件或目录
通过 shutil.move
函数移动文件或目录,各个变量解释参见函数文档注释:
def move(ori: str, src: str):
"""
移动文件或目录
:param ori: 文件或目录路径
:param src: 目标地址,目标路径不存在则自动创建
:return: 执行的结果
"""
ori = ori.replace('/', '\\')
src = src.replace('/', '\\')
assert os.path.exists(ori), ValueError('请输入正确的文件或目录路径!') # 保证源路径存在
if not os.path.isdir(src):
os.makedirs(src) # 保证目标路径存在
try:
shutil.move(ori, src) # 移动文件
return '移动成功!'
except Exception as e:
print(e)
return '移动失败!'
print(move('E:\\Python\\DEMO\\Study\\test', 'F:\\桌面'))
删除文件或目录
通过 os.remove
函数删除文件,通过 shutil.rmtree
函数删除目录,各个变量解释参见函数文档注释:
def delete(ori: str):
"""
删除文件或目录
:param ori: 文件或目录路径
:return: 执行的结果
"""
if not os.path.exists(ori):
return '目标路径不存在'
try:
if os.path.isfile(ori):
os.remove(ori) # 删除文件
elif os.path.isdir(ori):
shutil.rmtree(ori) # 删除目录
return '删除成功!'
except Exception as e:
print(e)
return '删除失败!'
print(delete('E:\\Python\\DEMO\\Study\\test'))
压缩目录
通过 shutil.make_archive
函数压缩目录,各个变量解释参见函数文档注释:
def compression(ori: str, src: str, type_: str):
"""
压缩文件
:param ori: 目录路径
:param src: 目标地址
:param type_: 压缩类型,为 ['bztar', 'gztar', 'tar', 'xztar', 'zip'] 之一
:return: 执行的结果
"""
ori = ori.replace('/', '\\')
src = src.replace('/', '\\')
assert os.path.isdir(ori), ValueError('请输入正确的目录路径!') # 保证源路径存在
assert type_.lower() in [_[0] for _ in shutil.get_archive_formats()] # 保证压缩类型合法
if '\\' not in src: # 防止原路径是根目录导致无文件名
src += ori.split('\\')[-1]
if not os.path.isdir('\\'.join(src.split('\\')[:-1])):
if '\\'.join(src.split('\\')[:-1]): # 防止原路径是根目录导致此值为空
os.makedirs('\\'.join(src.split('\\')[:-1])) # 保证目标路径存在
while os.path.isfile(f'{src}.{type_.lower()}'):
if input(f'文件 {src}.{type_.lower()} 已存在,是否覆盖?y/n(n)').lower() != 'y': # 文件覆盖提示
src = '\\'.join(src.split('\\')[:-1] + [input('请输入压缩后的文件名(不含后缀):')])
else:
break
try:
shutil.make_archive(src, type_.lower(), ori)
return '压缩成功!'
except Exception as e:
print(e)
return '压缩失败!'
print(compression('test\\a', 'F:\\桌面\\f', 'zip'))
解压缩文件
通过 shutil.unpack_archive
函数解压缩文件,各个变量解释参见函数文档注释:
def unzip(ori: str, src: str = os.getcwd()):
"""
压缩文件
:param ori: 文件路径
:param src: 目标地址
:return: 执行的结果
"""
ori = ori.replace('/', '\\')
src = src.replace('/', '\\')
assert os.path.isfile(ori), ValueError('请输入正确的文件路径!') # 保证源路径存在
if not os.path.isdir(src):
os.makedirs(src) # 保证目标路径存在
try:
shutil.unpack_archive(ori, src)
return '解压缩成功!'
except Exception as e:
print(e)
return '解压缩失败!'
print(unzip('F:\\桌面\\f.zip'))
批量操作
由于批量压缩要对应多输出文件名,与本处设计的单变量列表结构不同,所以此处只允许复制、移动、删除、解压缩这四种操作,其中 function.__name__
代表操作的函数名,会返回操作每一个文件或目录的执行结果,各个变量解释参见函数文档注释:
def batch(path_list: list, function: (copy, move, delete, unzip), *args):
"""
多文件目录复制、移动、删除、解压缩批量操作
:param path_list: 文件或目录路径列表
:param function: 操作函数
:param args: 除文件或目录路径参数的其他参数
:return: 执行的结果
"""
try:
res = []
for p in path_list:
res.append(f'{function.__name__} 文件 {p} 的操作结果:{function(p, *args)}')
return '\n'.join(res)
except Exception as e:
print(e)
return '批量操作执行失败!'
print(batch(['test\\a\\c', 'test\\a\\b'], copy, 'F:\\桌面'))
print(batch(['F:\\桌面\\c', 'F:\\桌面\\b'], delete))
结束语
如果有喜欢面向对象编程的朋友可以改写成类哦~ 不过写成类我感觉应该都是静态方法。