查看: 318|回复: 0

Python从零开发fileman命令行工具:目录统计与文件整理

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
在Python生态中,命令行工具是自动化脚本最常见的落地形态。本文基于一个完整的fileman项目,演示如何用Python从零实现一个可安装、可测试、支持 python -m 调用的命令行工具。该项目主要提供两个能力:扫描目录并统计文件数量与大小,以及按扩展名把文件自动归类到对应子目录。适合刚接触命令行工具开发、希望理解argparse、pathlib、setuptools与pytest协作方式的开发者参考。

一、项目结构与职责划分

项目采用标准的src-less包结构,核心代码放在fileman包内,测试独立在tests目录。根目录含有pyproject.toml用于打包安装,README.md和.gitignore属于常规工程配置。

fileman/
├── fileman/
│ ├── __init__.py
│ ├── __main__.py   # 支持 python -m fileman
│ ├── cli.py        # 命令行参数解析与交互逻辑
│ └── core.py       # 核心业务逻辑,不依赖命令行
├── tests/
│ └── test_core.py
├── pyproject.toml
├── README.md
└── .gitignore

这种把“核心逻辑”和“命令行入口”拆开的做法,能让纯函数更容易被单元测试覆盖,也方便以后扩展成库或接入其他界面。

二、核心逻辑:扫描与整理

core.py只使用标准库,不涉及任何CLI相关模块。它定义了scan_directory和organize_files两个函数。

scan_directory接收一个路径字符串,内部用Path(path)转为Path对象。如果目录不存在,抛出FileNotFoundError,错误信息带中文提示。接着用root.rglob("*")递归匹配所有文件,再用f.is_file()过滤出真实文件,避免目录项混入。文件数量就是列表长度,总大小通过对每个文件的stat().st_size累加获得。扩展名统计使用collections.Counter,通过file.suffix.lower()拿到小写扩展名,没有扩展名的文件标记为“(无扩展名)”,最后用most_common(5)返回出现次数最多的前5个扩展名。

organize_files接收源目录和目标目录。它只处理src.iterdir()直接子级中的普通文件,不递归子目录。每个文件取扩展名去掉点号后作为目标子目录名,如果无扩展名则归入other。使用target_dir.mkdir(parents=True, exist_ok=True)创建目录,然后用file.rename(new_path)移动文件。函数返回成功移动的文件数量。

三、命令行入口与参数解析

cli.py负责把用户输入的命令转换为对core函数的调用。它使用argparse创建名为fileman的解析器,并通过add_subparsers定义两个子命令:scan和organize。

scan子命令只接受一个path参数,用于指定要扫描的目录。organize子命令接受source源目录,以及一个可选参数-o/--output,默认值为organized,表示整理后的输出目录。

main()函数解析参数后,根据args.command分发处理。在scan分支中,程序调用scan_directory,然后打印文件总数、总大小和扩展名统计。总大小显示使用format_size函数:从B开始不断除以1024,在小于1024时保留一位小数并返回对应的KB、MB或GB单位,如果超过GB则继续除到TB。

四、关于f-string嵌套引号的兼容性说明

原代码在print输出时直接使用了f-string内嵌套同类型双引号的写法,例如:

print(f"文件总数:{info["total_files"]}")

这种写法在Python 3.12之前会触发SyntaxError,因为f-string表达式内不能使用与外部相同的引号。既然pyproject.toml声明requires-python为>=3.8,代码就必须兼容3.8到3.11。因此实际实现时应改用单引号访问字典键,写成下面这样:

print(f"文件总数:{info['total_files']}")

这也提醒我们,在编写跨版本Python代码时,f-string内部嵌套字典键访问需要留意引号冲突。

五、支持 python -m fileman 运行

为了让包支持python -m fileman,需要在fileman包里增加__main__.py。这个文件只做一件事:从cli模块导入main,并在__name__ == "__main__"时调用它。这样用户即使不安装命令行入口,也可以用python -m方式运行工具,便于源码调试。

六、打包安装配置

pyproject.toml采用setuptools作为构建后端。build-system指定setuptools>=61.0和build_meta,项目元数据包含name、version、description以及requires-python。最关键的是[project.scripts]段:

[project.scripts]
fileman = "fileman.cli:main"

这行配置会在安装时生成一个名为fileman的可执行脚本,脚本内容本质上是一个Python入口,调用fileman.cli.main。执行以下命令即可完成开发模式安装,并让fileman命令全局可用:

pip install -e .

之后就可以直接使用:

fileman scan .
fileman organize ~/Downloads

七、编写自动化测试

tests/test_core.py使用pytest和tempfile构造临时目录,验证organize_files是否正确移动文件。测试思路是:在临时目录下创建src和dst,src里放入a.txt和b.jpg两个文件,调用organize_files把src移动到dst,然后断言返回值为2,并且目标目录下出现了txt/a.txt和jpg/b.jpg。

测试代码如下:
  1. import pytest
  2. from fileman.core import organize_files
  3. import tempfile
  4. from pathlib import Path
  5. def test_organize_files():
  6.     with tempfile.TemporaryDirectory() as tmp:
  7.         src = Path(tmp) / "src"
  8.         dst = Path(tmp) / "dst"
  9.         src.mkdir()
  10.         (src / "a.txt").write_text("hello")
  11.         (src / "b.jpg").write_bytes(b"123")
  12.         moved = organize_files(str(src), str(dst))
  13.         assert moved == 2
  14.         assert (dst / "txt" / "a.txt").exists()
  15.         assert (dst / "jpg" / "b.jpg").exists()
复制代码

运行pytest tests/ -v即可看到测试通过。这个测试覆盖了多扩展名、无递归、目录自动创建这几个关键行为。

八、完整使用演示

安装完成后,扫描某个目录的效果如下:

$ fileman scan ~/Downloads
文件总数:125
总大小:2.3GB
扩展名统计:
.jpg: 45 个
.pdf: 30 个
.zip: 20 个
.docx: 15 个
(无扩展名): 15 个

执行整理操作:

$ fileman organize ~/Downloads -o ~/Organized
已整理 125 个文件到 ~/Organized/ 目录

整理后的目录结构会按扩展名自动创建子文件夹:

$ ls ~/Organized
jpg/ pdf/ zip/ docx/ other/

九、适用场景与扩展建议

这个工具适合处理下载目录、临时文件归档、日志文件分类等日常任务。如果要用于生产环境,还需要考虑几个细节:同名文件冲突时rename会覆盖目标文件,建议在移动前检查目标路径是否存在;iterdir只处理当前层文件,如果希望递归整理,可以改成rglob;统计大目录时rglob会先构建完整文件列表,内存占用会随文件数量增长,可以考虑用os.scandir生成器逐个处理。

整体来看,这个项目麻雀虽小,但覆盖了Python命令行开发的主要环节:业务逻辑与CLI解耦、argparse子命令、包入口、setuptools安装、pytest测试。掌握这套流程后,很容易套用到其他自动化工具中,比如批量重命名、日志分析、数据转换等场景。开发者只需替换core.py中的逻辑并调整对应参数,就能快速生成一个可分发、可安装的Python命令。

最后的工程建议是:始终把核心逻辑写成不依赖sys.argv的纯函数,这样测试和维护都会轻松得多。命令行参数解析只是最外层的一层“薄壳”,真正值得认真设计的永远是函数边界与返回值约定。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-8-26 14:37 , Processed in 0.020735 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部