用 Rust 构建高性能 CLI 工具
使用 clap + serde + anyhow 构建一个带参数解析、配置文件、错误处理的命令行工具
你将学到
- 使用 clap 定义命令行参数和子命令
- 使用 serde 读写配置文件
- 使用 anyhow 进行错误处理
- 构建一个完整的 CLI 应用
前置知识
架构设计
rust-cli/
├── src/
│ ├── main.rs # 入口
│ ├── cli.rs # 命令行定义
│ ├── config.rs # 配置管理
│ └── commands/ # 子命令
│ ├── mod.rs
│ ├── init.rs
│ └── run.rs
├── Cargo.toml
└── config.toml
实现步骤
第一步:项目初始化
cargo new rust-cli
cd rust-cli
# Cargo.toml
[package]
name = "rust-cli"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
toml = "0.8"
anyhow = "1"
第二步:定义命令行参数
// src/cli.rs
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "rust-cli")]
#[command(about = "一个高性能的命令行工具")]
#[command(version)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
/// 配置文件路径
#[arg(short, long, default_value = "config.toml")]
pub config: String,
/// 启用详细输出
#[arg(short, long)]
pub verbose: bool,
}
#[derive(Subcommand)]
pub enum Commands {
/// 初始化项目
Init {
/// 项目名称
name: String,
},
/// 运行任务
Run {
/// 任务名称
#[arg(short, long)]
task: String,
/// 并发数
#[arg(short, long, default_value = "4")]
workers: usize,
},
/// 显示配置
Config,
}
第三步:配置管理
// src/config.rs
use serde::{Deserialize, Serialize};
use std::path::Path;
use anyhow::{Result, Context};
#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
pub project: ProjectConfig,
pub runtime: RuntimeConfig,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ProjectConfig {
pub name: String,
pub version: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct RuntimeConfig {
pub workers: usize,
pub timeout: u64,
}
impl Config {
pub fn load(path: &str) -> Result<Self> {
let content = std::fs::read_to_string(path)
.context(format!("无法读取配置文件: {}", path))?;
let config: Config = toml::from_str(&content)
.context("解析配置文件失败")?;
Ok(config)
}
pub fn save(&self, path: &str) -> Result<()> {
let content = toml::to_string_pretty(self)?;
std::fs::write(path, content)?;
Ok(())
}
pub fn default() -> Self {
Config {
project: ProjectConfig {
name: "my-project".to_string(),
version: "0.1.0".to_string(),
},
runtime: RuntimeConfig {
workers: 4,
timeout: 30,
},
}
}
}
第四步:实现子命令
// src/commands/init.rs
use anyhow::Result;
use std::fs;
use crate::config::Config;
pub fn execute(name: &str) -> Result<()> {
println!("初始化项目: {}", name);
// 创建目录
fs::create_dir_all(name)?;
// 创建配置文件
let config = Config {
project: crate::config::ProjectConfig {
name: name.to_string(),
version: "0.1.0".to_string(),
},
..Config::default()
};
config.save(&format!("{}/config.toml", name))?;
println!("✅ 项目初始化完成");
Ok(())
}
// src/commands/run.rs
use anyhow::Result;
use crate::config::Config;
pub fn execute(task: &str, workers: usize, config: &Config) -> Result<()> {
println!("运行任务: {}", task);
println!("并发数: {}", workers);
println!("超时: {}s", config.runtime.timeout);
// 模拟任务执行
for i in 0..workers {
println!(" Worker {} 处理中...", i);
}
println!("✅ 任务完成");
Ok(())
}
第五步:主程序
// src/main.rs
mod cli;
mod config;
mod commands;
use clap::Parser;
use cli::{Cli, Commands};
use config::Config;
use anyhow::Result;
fn main() -> Result<()> {
let cli = Cli::parse();
// 加载配置
let config = if std::path::Path::new(&cli.config).exists() {
Config::load(&cli.config)?
} else {
Config::default()
};
if cli.verbose {
println!("配置: {:?}", config);
}
// 执行命令
match cli.command {
Commands::Init { name } => {
commands::init::execute(&name)?;
}
Commands::Run { task, workers } => {
commands::run::execute(&task, workers, &config)?;
}
Commands::Config => {
println!("{:#?}", config);
}
}
Ok(())
}
// src/commands/mod.rs
pub mod init;
pub mod run;
完整项目结构
rust-cli/
├── src/
│ ├── main.rs # 程序入口
│ ├── cli.rs # clap 命令行定义
│ ├── config.rs # serde 配置管理
│ └── commands/
│ ├── mod.rs
│ ├── init.rs # init 子命令
│ └── run.rs # run 子命令
├── Cargo.toml
└── config.toml
最佳实践
- 使用 clap derive:比 builder 模式更简洁
- anyhow 统一错误处理:避免到处写
Box<dyn Error> - serde 序列化:轻松处理配置文件
- 模块化设计:每个子命令独立模块
- 完善的帮助信息:clap 自动生成
常见问题
Q: 如何支持多个配置文件格式? A: 使用 serde 支持多种格式(toml/json/yaml),根据扩展名选择。
Q: 如何处理子命令的参数冲突?
A: 使用 clap 的 conflicts_with 属性。
Q: 如何添加 Shell 自动补全?
A: 使用 clap_complete crate。
扩展挑战
- 添加 Shell 自动补全
- 实现插件系统
- 添加进度条显示
相关课程
| 课程 | 相关章节 |
|---|---|
| Rust | 结构体(Struct) |
| Rust | 错误处理:Result 与 Option |
| Rust | 常用集合:Vec, String, HashMap |
| Rust | 模块系统与可见性 |