构建一个专业的 Python CLI 工具
使用 Click + Rich 构建一个带颜色输出、进度条、子命令的命令行工具
你将学到
- 使用 Click 框架定义命令、参数和选项
- 使用 Rich 库实现彩色输出、表格、进度条
- 设计清晰的 CLI 交互体验
- 将 Python 脚本打包成可分发的命令行工具
前置知识
架构设计
filetool/
├── filetool/
│ ├── __init__.py
│ ├── cli.py # CLI 入口和命令定义
│ ├── core.py # 核心业务逻辑
│ └── formatters.py # 输出格式化
├── pyproject.toml # 项目配置和打包
└── README.md
实现步骤
第一步:项目初始化
# 创建项目目录
mkdir filetool && cd filetool
# 创建虚拟环境
python -m venv venv
source venv/bin/activate # Linux/Mac
# venv\Scripts\activate # Windows
# 安装依赖
pip install click rich
第二步:定义 CLI 入口
# filetool/cli.py
import click
from rich.console import Console
from rich.table import Table
from .core import scan_directory, get_file_stats
console = Console()
@click.group()
@click.version_option(version="1.0.0")
def cli():
"""FileTool - 一个专业的文件管理命令行工具"""
pass
@cli.command()
@click.argument("directory", default=".")
@click.option("-e", "--ext", help="按扩展名过滤")
@click.option("-s", "--sort", type=click.Choice(["name", "size", "date"]),
default="name", help="排序方式")
@click.option("--hidden", is_flag=True, help="显示隐藏文件")
def list(directory, ext, sort, hidden):
"""列出目录中的文件"""
files = scan_directory(directory, ext_filter=ext, show_hidden=hidden)
table = Table(title=f"📁 {directory} 目录内容")
table.add_column("文件名", style="cyan")
table.add_column("大小", justify="right", style="green")
table.add_column("修改时间", style="yellow")
table.add_column("类型", style="magenta")
for f in sorted(files, key=lambda x: x[sort]):
table.add_row(f["name"], f["size"], f["modified"], f["type"])
console.print(table)
console.print(f"\n共 {len(files)} 个文件")
@cli.command()
@click.argument("directory", default=".")
def stats(directory):
"""统计目录信息"""
with console.status("[bold blue]扫描目录中..."):
result = get_file_stats(directory)
console.print(f"\n📊 [bold]目录统计:{directory}[/bold]")
console.print(f" 文件总数:{result['total_files']}")
console.print(f" 目录总数:{result['total_dirs']}")
console.print(f" 总大小:{result['total_size']}")
# 按类型统计
table = Table(title="按文件类型统计")
table.add_column("类型", style="cyan")
table.add_column("数量", justify="right", style="green")
table.add_column("大小", justify="right", style="yellow")
for ext, info in result["by_extension"].items():
table.add_row(ext or "(无扩展名)", str(info["count"]), info["size"])
console.print(table)
if __name__ == "__main__":
cli()
第三步:实现核心逻辑
# filetool/core.py
import os
from pathlib import Path
from datetime import datetime
def format_size(size_bytes):
"""将字节数转换为人类可读格式"""
for unit in ["B", "KB", "MB", "GB"]:
if size_bytes < 1024:
return f"{size_bytes:.1f} {unit}"
size_bytes /= 1024
return f"{size_bytes:.1f} TB"
def scan_directory(directory=".", ext_filter=None, show_hidden=False):
"""扫描目录,返回文件信息列表"""
path = Path(directory)
files = []
for item in path.iterdir():
# 跳过隐藏文件
if not show_hidden and item.name.startswith("."):
continue
# 按扩展名过滤
if ext_filter and item.suffix != f".{ext_filter}":
continue
stat = item.stat()
files.append({
"name": item.name,
"size": format_size(stat.st_size),
"size_bytes": stat.st_size,
"modified": datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M"),
"modified_timestamp": stat.st_mtime,
"type": item.suffix or "(无)",
"is_dir": item.is_dir(),
})
return files
def get_file_stats(directory="."):
"""获取目录统计信息"""
path = Path(directory)
total_files = 0
total_dirs = 0
total_size = 0
by_extension = {}
for item in path.rglob("*"):
if item.is_file():
total_files += 1
size = item.stat().st_size
total_size += size
ext = item.suffix
if ext not in by_extension:
by_extension[ext] = {"count": 0, "size_bytes": 0}
by_extension[ext]["count"] += 1
by_extension[ext]["size_bytes"] += size
elif item.is_dir():
total_dirs += 1
# 格式化大小
for ext in by_extension:
by_extension[ext]["size"] = format_size(by_extension[ext]["size_bytes"])
return {
"total_files": total_files,
"total_dirs": total_dirs,
"total_size": format_size(total_size),
"by_extension": by_extension,
}
第四步:添加进度条支持
# filetool/formatters.py
from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn
from rich.console import Console
import time
console = Console()
def process_with_progress(items, process_func, description="处理中"):
"""带进度条的批量处理"""
results = []
with Progress(
SpinnerColumn(),
TextColumn("[progress.description]{task.description}"),
BarColumn(),
TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
console=console,
) as progress:
task = progress.add_task(description, total=len(items))
for item in items:
result = process_func(item)
results.append(result)
progress.advance(task)
time.sleep(0.01) # 模拟处理时间
return results
# 在 cli.py 中使用
@cli.command()
@click.argument("directory", default=".")
@click.option("-p", "--pattern", default="*", help="文件匹配模式")
def process(directory, pattern):
"""批量处理文件(带进度条)"""
from pathlib import Path
files = list(Path(directory).glob(pattern))
def process_file(f):
# 这里可以添加实际处理逻辑
return {"file": f.name, "status": "完成"}
results = process_with_progress(files, process_file, "处理文件")
console.print(f"\n✅ 处理完成,共 {len(results)} 个文件")
第五步:打包为可分发工具
# pyproject.toml
[build-system]
requires = ["setuptools>=68.0", "wheel"]
build-backend = "setuptools.backends._legacy:_Backend"
[project]
name = "filetool"
version = "1.0.0"
description = "一个专业的文件管理命令行工具"
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
"click>=8.0",
"rich>=13.0",
]
[project.scripts]
filetool = "filetool.cli:cli"
[tool.setuptools.packages.find]
where = ["."]
# 安装为全局命令
pip install -e .
# 现在可以直接使用
filetool list .
filetool stats .
完整项目结构
filetool/
├── filetool/
│ ├── __init__.py # 包初始化
│ ├── cli.py # CLI 入口和命令定义
│ ├── core.py # 核心业务逻辑
│ └── formatters.py # 输出格式化和进度条
├── pyproject.toml # 项目配置和打包
└── README.md # 项目说明
最佳实践
- 使用 Click 而非 argparse:Click 的装饰器语法更简洁,自动生成帮助文档
- Rich 让输出更专业:彩色输出、表格、进度条能显著提升用户体验
- 分离业务逻辑:CLI 层只负责参数解析和输出,核心逻辑放在独立模块
- 提供合理的默认值:参数有合理的默认值,减少用户输入
- 使用 pyproject.toml:现代 Python 项目的标准配置方式
常见问题
Q: 如何添加子命令?
A: 使用 @click.group() 创建命令组,然后用 @group.command() 添加子命令。
Q: 如何处理大文件的进度显示?
A: 使用 Rich 的 Progress 上下文管理器,在处理每个文件时调用 progress.advance()。
Q: 如何支持 Windows?
A: Click 和 Rich 都支持跨平台,但注意路径处理要用 pathlib.Path 而非硬编码路径分隔符。
扩展挑战
- 添加
search命令,支持按文件名搜索 - 添加
duplicate命令,查找重复文件 - 支持配置文件,保存常用选项
相关课程
| 课程 | 相关章节 |
|---|---|
| Python | 函数定义与调用 |
| Python | 模块、包与导入机制 |
| Python | 文件读写操作 |
| Python | 面向对象编程:类与对象 |