项目实战:命令行工具
本章用 clap 做一个实用的 CLI 工具:统计文本文件的行数、词数、字符数(类 wc)。
1. 依赖
[dependencies]
clap = { version = "4", features = ["derive"] }2. 用 derive 定义参数
use clap::Parser;
#[derive(Parser)]
#[command(name = "mywc", about = "简易 wc 工具")]
struct Cli {
/// 要统计的文件路径
#[arg(required = true)]
path: String,
/// 是否统计字符数
#[arg(short, long)]
chars: bool,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
let content = std::fs::read_to_string(&cli.path)?;
let lines = content.lines().count();
let words = content.split_whitespace().count();
let chars = content.chars().count();
println!("行数: {}", lines);
println!("词数: {}", words);
if cli.chars {
println!("字符数: {}", chars);
}
Ok(())
}ℹ️clap derive 极简
#[derive(Parser)] + 字段上的 #[arg(...)] 即声明式地定义了 CLI。自动获得 --help、类型解析与报错,省去手写参数解析。
3. 子命令
#[derive(clap::Subcommand)]
enum Command {
/// 统计文件
Count { path: String },
/// 显示版本信息
Version,
}
#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Command,
}4. 运行效果
cargo run -- ./README.md --chars
# 行数: 120
# 词数: 800
# 字符数: 5400💡把逻辑拆成函数
真实项目里把「统计」抽成 fn analyze(text: &str) -> Stats,把 IO 与 CLI 解耦,便于单元测试(呼应第 23 章)。
5. 错误处理
本例用 Box<dyn Error> 统一兜底。? 把各处的 io::Error 自动转换成返回类型,main 里 Result 让任何失败都干净退出。
⚠️路径要用 &str 而非 String 移动
read_to_string(&cli.path) 传引用即可;若写成 read_to_string(cli.path) 会把 path move 走,之后无法再用。
🎯练习
给工具加一个 --words-only 与 --lines-only 开关(互斥时用 ArgGroup),并把统计逻辑抽成返回结构体的函数,补一个单元测试验证「a b\nc」词数=2、行数=2。
小结
- clap 的
derive模式声明式定义 CLI 参数/子命令 #[arg(short, long)]定义短/长选项,required设必填- 用
Box<dyn Error>+?统一错误处理 - 把业务逻辑抽成函数,便于测试
- 下一章做 Web 服务实战 →