Python 系列(五十) 掌握 os 和 os.path,实现文件与目录自动化管理
1 | 作者:李晓辉 |
- Python 系列(一) 从零开始认识 Python,入门其实没那么难
- Python 系列(二) 学 Python 的第一步,把开发环境搭建好
- Python 系列(三) 基础语法避坑指南,缩进、大小写、续行与注释
- Python 系列(四) Python 输入输出详解,print()、input() 与常见避坑指南
- Python 系列(五) 数值类型与运算符详解,整除、浮点精度与进制转换实战
- Python 系列(六) 字符串处理实战,查找、替换、切片与日志分析
- Python 系列(七) 类型转换详解,int()、float()、str() 与金额计算避坑
- Python 系列(八) Python 流程控制入门,掌握 if、elif 和 else
- Python 系列(九) Python 逻辑运算符详解,and、or、not 一篇搞懂
- Python 系列(十) while、for 循环实战,彻底搞懂 range、break 与 continue
- Python 系列(十一) list、tuple、set、dict,到底该怎么选?
- Python 系列(十二) List 列表实战,搞定数据增删改查与批量处理
- Python 系列(十三) Tuple 元组实战,搞懂不可变特性与 enumerate() 遍历
- Python 系列(十四) Set 集合实战,搞定数据去重与集合运算
- Python 系列(十五) Dict 字典详解,掌握快速查找与键值操作
- Python 系列(十六) sort() 和 sorted() 详解,掌握数据排序技巧
- Python 系列(十七) 自定义函数入门,让重复代码一次编写多次使用
- Python 系列(十八) 函数参数详解,搞懂位置参数、关键字参数和默认参数
- Python 系列(十九) 从 *args 到 **kwargs,让函数调用更灵活
- Python 系列(二十) 变量作用域是什么?一文搞懂 global 和 nonlocal
- Python 系列(二十一) 搞懂变量、对象和引用,理解函数参数传递机制
- Python 系列(二十二) map() 和 filter() 详解,轻松实现批量数据处理与筛选
- Python 系列(二十三) 嵌套函数与 lambda 表达式详解,搞懂作用域与闭包
- Python 系列(二十四) 搞懂递归函数,从基线条件到实际应用
- Python 系列(二十五) 聊聊模块与 import,让代码告别重复编写
- Python 系列(二十六) Python 模块入门,从 import 到模块复用
- Python 系列(二十七) Python dir() 函数详解,快速查看模块中的函数与属性
- Python 系列(二十八) 不会用 Python 标准库?教你快速查找模块和函数
- Python 系列(二十九) 搞懂 Python sys 模块,让脚本更适合自动化运维
- Python 系列(三十) 为什么 0.1 + 0.2 不等于 0.3?彻底搞懂 Decimal
- Python 系列(三十一) 搞懂 datetime 日期时间,轻松处理时区与时间计算
- Python 系列(三十二) 面向对象编程入门,从零理解类、对象、封装与继承
- Python 系列(三十三) 什么是类和对象?零基础理解 Python class
- Python 系列(三十四) Python 属性详解,搞懂类属性、实例属性和 setter
- Python 系列(三十五) 学会特殊方法,让对象打印出你想要的内容
- Python 系列(三十六) 多个对象如何共享数据?彻底搞懂类变量
- Python 系列(三十七) 搞懂父类、子类和方法重写,真正理解继承与多态
- Python 系列(三十八) 如何判断数据类型?三个内置函数一次讲明白
- Python 系列(三十九) 搞懂异常模型与常见异常类型,读懂程序报错
- Python 系列(四十) 从零学会异常处理,掌握 try、except、else 和 finally
- Python 系列(四十一) ValueError、TypeError 到底是什么?一文搞懂常见异常
- Python 系列(四十二) ValueError、TypeError 到底是什么?一文搞懂常见异常
- Python 系列(四十三) 别只会处理异常!学会自定义异常,让程序更容易维护
- Python 系列(四十四) 从零认识 assert 断言,让程序学会检查自己
- Python 系列(四十五) 使用 sys 模块处理标准输入与输出
- Python 系列(四十六) 使用 open() 读取和写入文件
- Python 系列(四十七) 学会读写文本文件,让程序保存运行结果
- Python 系列(四十八) 搞懂 bytes、bytearray 和二进制读写
- Python 系列(四十九) 使用 seek() 和 tell() 灵活控制文件读取位置
- Python 系列(五十) 掌握 os 和 os.path,实现文件与目录自动化管理
- Python 系列(五十一) 掌握列表推导式,轻松实现列表生成与数据筛选
- Python 系列(五十二) 掌握字典推导式,让字典创建更简单
- Python 系列(五十三) 从零理解生成器,轻松处理大量数据
- Python 系列(五十五) 使用 zip() 同时遍历多个集合
前面我们学习了如何使用 Python 读取文件、写入文件,以及通过 seek() 和 tell() 定位文件中的内容。
这些操作解决了文件内容的问题,但在日常工作中,我们还经常需要处理另一类任务。
例如:
- 想知道当前 Python 程序运行在哪个目录中。
- 想查看某个目录下有哪些文件。
- 想判断一个配置文件是否存在。
- 想创建一个目录,专门保存程序生成的日志。
- 想删除不再需要的临时文件。
- 想清理已经完成任务的空目录。
- 想批量检查 Linux 服务器上的配置文件是否齐全。
如果每次都手动打开目录、查找文件、创建目录或者删除文件,不仅效率低,而且很难自动化处理大量文件。
能不能让 Python 帮我们完成这些工作?
当然可以。Python 提供了 os 和 os.path 等工具,帮助我们获取目录信息、检查文件是否存在、创建目录、删除文件,以及处理文件路径。
这篇文章就从最基础的操作开始,逐步学习如何使用它们管理文件和目录。
认识 os 和 os.path
在学习具体代码之前,我们先理解这两个名称分别代表什么。
os 是 Python 提供的一个标准库模块,可以理解为 Python 与操作系统打交道的工具箱。通过它,我们可以执行一些与文件、目录、环境变量和当前工作目录有关的操作。
os.path 则主要负责处理文件路径,例如拼接路径、获取文件名、提取目录名,以及判断路径是否存在。
可以简单理解为:
1 | os |
这两个模块都属于 Python 标准库,不需要单独安装。
查看当前目录下有哪些文件
假设你正在维护一台 Linux 服务器,某个目录中保存着应用程序的配置文件、日志文件和脚本。
如果想知道这个目录中有哪些内容,可以使用 os.listdir()。
创建一个名为 os_demo.py 的文件,写入以下代码:
1 | import os |
理解 import os
1 | import os |
import 可以理解为导入工具。
通过 import os,我们就可以在程序中使用 os 模块提供的功能。
理解 os.listdir()
1 | files = os.listdir(".") |
listdir() 用于获取指定目录中的内容名称,其中 . 表示当前工作目录。
假设当前目录中有以下文件:
1 | python-demo/ |
运行程序:
1 | python os_demo.py |
可能得到类似的结果:
1 | ['os_demo.py', 'app.py', 'config.ini', 'access.log'] |
实际输出顺序可能不同,具体内容也取决于目录中的实际文件。
os.listdir() 返回一个列表,其中保存了目录下的文件名和子目录名。它只列出当前目录这一层的内容,不会自动进入子目录继续查找。
另外,返回的名称通常不包含完整路径。如果需要进一步操作文件,还要根据实际情况拼接路径。
获取当前工作目录
有时候,我们不清楚 Python 程序究竟从哪个目录开始运行。
这时,可以使用 os.getcwd()。
1 | import os |
运行程序:
1 | python os_demo.py |
假设当前工作目录是 /home/python-demo,输出可能是:
1 | 当前工作目录: /home/python-demo |
getcwd() 是 get current working directory 的缩写,可以理解为获取当前工作目录。
这里需要区分两个概念:
- 当前工作目录:程序解析相对路径时使用的目录。
- Python 脚本所在目录:Python 脚本文件实际保存的位置。
两者不一定相同。
例如,你可以在 /home 目录下运行:
1 | python /opt/scripts/os_demo.py |
此时,脚本位于 /opt/scripts,但当前工作目录仍可能是 /home。
这也是一些初学者明明写对了文件名,程序却提示找不到文件的常见原因。
使用 os.path 拼接文件路径
假设我们需要处理 /var/log 目录中的 messages 文件。
可以直接写出完整路径:
1 | log_file = "/var/log/messages" |
但如果目录和文件名经常变化,每次都手动拼接路径就很麻烦。
这时可以使用 os.path.join()。
1 | import os |
在 Linux 系统上,输出为:
1 | /var/log/messages |
这段代码可以分成三个步骤:
directory保存目录路径。filename保存文件名。os.path.join()按照当前操作系统的路径规则组合它们。
手动拼接字符串也能实现类似效果,但如果需要兼容 Windows,路径分隔符就可能带来问题。
使用 os.path.join() 可以让 Python 根据当前操作系统选择合适的路径分隔符。
需要注意,os.path.join() 只负责拼接路径,并不会检查目标文件是否真实存在。
获取文件名和目录名
假设我们拿到一个完整路径:
1 | /var/log/nginx/access.log |
我们可能只想获取文件名 access.log,或者获取它所在的目录 /var/log/nginx。
这时可以使用 os.path.basename() 和 os.path.dirname()。
1 | import os |
运行结果:
1 | 文件名: access.log |
这两个方法的作用分别是:
basename():获取路径最后一部分的名称。dirname():获取路径中的目录部分。
它们主要处理路径字符串,不要求对应文件真实存在。
判断文件或目录是否存在
假设我们编写了一个日志分析程序,需要读取 access.log。
如果文件不存在,程序就可能无法正常运行。因此,在读取文件之前,可以先判断目标路径是否存在。
使用 os.path.exists():
1 | import os |
如果当前工作目录中没有 access.log,运行结果就是:
1 | 目标路径不存在 |
如果文件存在,则会输出:
1 | 目标路径存在 |
exists() 既可以判断文件,也可以判断目录。
如果想进一步区分路径究竟是文件还是目录,可以使用另外两个方法。
判断路径是否为文件
使用 os.path.isfile():
1 | import os |
判断路径是否为目录
使用 os.path.isdir():
1 | import os |
这三个方法的区别如下:
| 方法 | 作用 |
|---|---|
os.path.exists(path) | 判断路径是否存在 |
os.path.isfile(path) | 判断路径是否指向文件 |
os.path.isdir(path) | 判断路径是否指向目录 |
需要注意,路径存在并不意味着程序一定有权限读取它,也不代表后续操作一定成功。
创建目录
假设我们正在编写一个自动巡检脚本,希望把每次运行的结果保存到 reports 目录中。
如果目录不存在,就需要先创建它。
创建单个目录
可以使用 os.mkdir():
1 | import os |
如果当前目录中还没有 reports,运行成功后会创建这个目录。
但如果 reports 已经存在,再次运行代码就可能出现 FileExistsError 异常。
创建多层目录
如果需要创建下面这样的目录结构:
1 | reports/ |
可以使用 os.makedirs():
1 | import os |
这里的 exist_ok=True 表示:如果目标目录已经存在,就不因为目录已存在而报错。
这很适合用在需要反复执行的自动化脚本中。
不过,如果目标路径存在但不是目录,或者程序没有相应权限,仍然可能发生异常。
删除文件
目录创建好了,接下来学习另一个常见操作:删除文件。
在 Linux 运维中,我们可能需要删除已经处理完成的临时文件、过期的测试文件,或者不再需要的程序输出文件。
Python 可以使用 os.remove() 删除文件。
例如,当前目录中有一个 temp.txt 文件:
1 | python-demo/ |
我们希望通过 Python 删除它,可以编写以下代码:
1 | import os |
运行程序:
1 | python os_demo.py |
如果 temp.txt 存在,输出为:
1 | 文件删除成功 |
删除后,目录结构变为:
1 | python-demo/ |
理解 os.remove()
1 | os.remove(file_path) |
remove() 用于删除指定路径对应的文件。
需要注意,删除操作会直接影响文件系统。对于普通文件,删除成功后,文件名就不再指向原来的文件。
如果文件不存在,或者程序没有删除权限,操作就可能引发异常。
另外,os.remove() 不能用来删除一个非空目录。
特别提醒: 删除文件前应确认路径正确,尤其是在服务器自动化脚本中,不要直接对未经检查的用户输入执行删除操作。上面的示例通过 os.path.isfile() 做了基础检查,但在复杂环境下,还需要考虑符号链接、权限、路径变化等情况。
删除空目录
如果我们想删除的是目录,而不是文件,可以使用 os.rmdir()。
例如,当前目录中存在一个空的 old_logs 目录:
1 | python-demo/ |
可以这样删除:
1 | import os |
运行结果:
1 | 空目录删除成功 |
为什么只能删除空目录
os.rmdir() 的用途是删除空目录。
如果 old_logs 里面还有文件,例如:
1 | old_logs/ |
执行:
1 | os.rmdir("old_logs") |
就会因为目录不为空而失败,通常会引发 OSError。
这样设计可以避免程序在删除目录时,无意中将里面的文件一起删除。
如果你只想删除空目录,os.rmdir() 就很合适。
删除非空目录
如果目录里面还有文件和子目录,但我们确实需要将整个目录删除,就需要使用 shutil 模块提供的 shutil.rmtree()。
例如:
1 | old_logs/ |
如果希望删除整个 old_logs 目录,可以这样写:
1 | import os |
运行成功后,old_logs 目录及其内部文件、子目录都会被删除。
这里多了一个新模块:
1 | import shutil |
shutil 是 Python 标准库中用于执行高级文件操作的模块,除了删除目录树,还支持复制文件、复制目录等操作。
rmdir() 和 rmtree() 有什么区别
| 方法 | 用途 | 是否允许目录非空 |
|---|---|---|
os.rmdir() | 删除空目录 | 不允许 |
shutil.rmtree() | 删除目录及其内部内容 | 允许 |
重要警告: shutil.rmtree() 可能一次性删除大量文件,通常不会把文件放入回收站。不要在不确认路径的情况下使用它,更不要直接对 /、/var、/home 等重要系统目录执行删除操作。
实际运维脚本中,应当明确限定待删除目录,必要时增加路径白名单、人工确认和日志记录。仅仅检查 os.path.isdir() 并不能保证目标目录就是你想删除的目录。
删除操作的异常处理
前面的示例已经对文件和目录是否存在做了检查,但检查与删除之间仍然可能发生变化。
例如,文件可能被其他程序删除,或者当前用户没有足够权限。
因此,在实际程序中,还可以使用 try 和 except 处理异常。
下面以删除文件为例:
1 | import os |
如果文件不存在,程序会输出:
1 | 文件不存在 |
如果删除成功,则会输出:
1 | 文件删除成功 |
这里的 try 表示尝试执行一段可能发生错误的代码,except 则用于捕获并处理对应异常。
最后的 OSError 用来处理其他常见的操作系统层面错误,例如文件被占用时可能出现的问题、文件系统限制等。
前面我们已经学习过异常处理,因此这里把它应用到实际文件操作中。
实战:准备巡检报告目录并清理临时文件
现在把前面学习的知识组合起来,编写一个简单的运维辅助脚本。
假设程序需要完成以下任务:
- 创建
reports目录,保存巡检结果。 - 检查
temp.txt是否存在。 - 如果存在,则删除这个临时文件。
- 如果不存在,则提示用户。
代码如下:
1 | import os |
运行程序:
1 | python os_demo.py |
如果 reports 目录不存在,并且 temp.txt 也不存在,输出可能是:
1 | 报告目录已准备好 |
如果 temp.txt 存在并且可以删除,输出可能是:
1 | 报告目录已准备好 |
整个过程如下:
flowchart TD
A["程序启动"] --> B["创建 reports 目录"]
B --> C["检查 temp.txt"]
C --> D{"文件存在吗?"}
D -->|不存在| E["提示无需删除"]
D -->|存在| F["尝试删除文件"]
F --> G{"删除成功吗?"}
G -->|成功| H["提示删除成功"]
G -->|失败| I["捕获异常并提示"]
E --> J["程序结束"]
H --> J
I --> J这个例子还比较简单,但已经包含了自动化脚本常见的处理流程:准备工作目录、检查目标对象、执行操作,并处理可能发生的错误。
在真正的生产环境中,如果要清理日志或备份文件,还需要增加保留期限、文件匹配规则、路径校验和操作日志等机制,避免误删重要数据。
常用方法汇总
学习到这里,我们已经接触了不少方法。可以通过下面的表格快速回顾。
| 方法 | 作用 |
|---|---|
os.listdir(path) | 列出目录中的内容 |
os.getcwd() | 获取当前工作目录 |
os.mkdir(path) | 创建单个目录 |
os.makedirs(path, exist_ok=True) | 创建多层目录,并允许目录已存在 |
os.remove(path) | 删除文件 |
os.rmdir(path) | 删除空目录 |
os.path.join(a, b) | 拼接路径 |
os.path.basename(path) | 获取路径最后一部分的名称 |
os.path.dirname(path) | 获取路径中的目录部分 |
os.path.exists(path) | 判断路径是否存在 |
os.path.isfile(path) | 判断路径是否为文件 |
os.path.isdir(path) | 判断路径是否为目录 |
shutil.rmtree(path) | 删除目录及其内部内容 |
写在最后
这篇文章我们学习了 Python 中用于处理文件和目录的两个重要工具:os 和 os.path,还认识了用于删除非空目录的 shutil 模块。
通过实际示例,我们已经掌握了查看目录、获取当前工作目录、拼接路径、判断文件是否存在、创建目录,以及删除文件和目录等操作。
这些能力是编写自动化运维脚本的重要基础。无论是检查配置文件、整理日志,还是保存巡检报告,都离不开对文件和目录的管理。
不过,文件和目录的删除操作需要格外谨慎:os.remove() 用于删除文件,os.rmdir() 只能删除空目录,而 shutil.rmtree() 会删除目录及其内部内容。使用前一定要确认路径,避免误删重要数据。
到这里,Python 输入与输出相关的内容就基本学习完了。
下一章,我们将进入新的主题:使用紧凑的语法和专用语句来创建、处理和排序数据结构,进一步学习如何用更简洁的方式组织和处理数据。
