Clap 是 Rust 生态中最流行的命令行参数解析库。在 4.x 版本中,Clap 提供了两种主要的 API 风格:**Derive(派生宏) 和 Builder(构建器)**。其中,Derive 模式因其代码简洁、类型安全且易于维护,成为绝大多数场景的首选。
一、添加依赖
toml
[dependencies]
# 为了使用 Derive 宏,必须启用 derive 特性
clap = { version = "4.6", features = ["derive"] }
二、基本代码
rust
use clap::Parser;
/// 一个简单的文件处理工具示例
///
/// 这个文档字符串会自动成为 --help 中的描述信息
#[derive(Parser, Debug)]
#[command(name = "my_tool")]
#[command(author, version, about, long_about = None)]
struct Cli {
/// 要处理的输入文件名 (必填参数)
///
/// 如果未提供此参数,程序将报错并提示用法
#[arg(short, long)]
file: String,
/// 输出文件路径 (可选参数)
///
/// 如果未指定,默认输出到 stdout
#[arg(short, long)]
output: Option<String>,
/// 是否启用详细日志模式
///
/// 这是一个布尔标志(flag),出现即为 true,不出现为 false
#[arg(short, long, default_value_t = false)]
verbose: bool,
/// 并行处理的任务数量
///
/// 默认值为 1,必须是正整数
#[arg(short, long, default_value_t = 1)]
jobs: u32,
}
fn main() {
// parse() 方法会解析命令行参数
// 如果解析失败(如缺少必填参数),Clap 会自动打印错误信息并退出程序
let cli = Cli::parse();
if cli.verbose {
println!("正在以详细模式运行...");
}
println!("处理文件: {}", cli.file);
println!("输出目标: {:?}", cli.output);
println!("详细模式: {}", cli.verbose);
println!("并行任务数: {}", cli.jobs);
}
| 代码片段 | 含义 | 默认值/来源 | 用户可见效果 |
|---|---|---|---|
derive(Parser) |
启用 CLI 解析能力 | 无 | 允许调用 .parse() |
derive(Debug) |
启用调试打印 | 无 | 允许 println!("{:?}", obj) |
name = "..." |
程序显示名称 | Cargo 包名 | Usage: my_tool ... |
author |
作者信息 | Cargo.toml authors |
帮助信息中的作者栏 |
version |
版本号 | Cargo.toml version |
支持 --version,帮助中显示版本 |
about |
简短描述 | 结构体 Doc Comment 第一行 | 帮助信息顶部的简介 |
long_about = None |
长篇描述 | 结构体 Doc Comment 剩余部分 | 此处强制与 about 一致,不显示额外长文本 |
#[arg(xx)]代码片段 |
说明 | 应用 |
|---|---|---|
short |
短命令 | -v,-o |
long |
长命令 | --verbose,--output |
default_value_t |
默认值 | 初始值,字段类型必须实现 Default trait |
**运行效果:**
cargo run -- --help:自动生成格式精美的帮助文档,文档内容为结构体内的注释内容。cargo run -- -f input.txt:正常执行。cargo run:报错,提示the following required arguments were not provided: --file <FILE>。
三、子命令
rust
use clap::{Parser, Subcommand};
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// 全局调试标志,对所有子命令生效
#[arg(short, long, global = true)]
debug: bool,
/// 子命令
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand, Debug)]
enum Commands {
/// 添加新记录
Add {
/// 记录名称
#[arg(short, long)]
name: String,
},
/// 删除指定记录
Remove {
/// 要删除的记录 ID
#[arg(short, long)]
id: u32,
/// 强制删除,不二次确认
#[arg(short, long)]
force: bool,
},
/// 列出所有记录
List,
}
fn main() {
let cli = Cli::parse();
match &cli.command {
Commands::Add { name } => {
if cli.debug { println!("DEBUG: 添加的项 '{}'", name); }
},
Commands::Remove { id, force } => {
if cli.debug { println!("DEBUG: 移除的id {}", id); }
if !force {
println!("请确认是否删除 ID: {}", id);
} else {
println!("强制删除 ID: {}", id);
}
},
Commands::List => {
if cli.debug { println!("DEBUG: 显示全部列表"); }
}
}
}
执行命令:
-
cargo run -- -d add -n xxx:添加记录 -
cargo run -- -d remove -i 55:删除记录 -
cargo run -- -d remove -i 55 -f:强制删除记录 -
cargo run -- -d list:显示列表
global = true:
- 可以放在子命令前面,也可以放在子命令后面,
false只能放在子命令前面。
四、内置范围校验
rust
use clap::Parser;
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// 端口号必须在:1--65535
#[arg(short, long, value_parser=clap::value_parser!(u16).range(1..=65535))]
port: u16,
}
fn main() {
let cli = Cli::parse();
println!("接口: {}", cli.port);
}
bash
cargo run -- -p 55
五、自定义校验函数
rust
use clap::Parser;
use std::path::PathBuf;
/// 自定义路径校验
fn validate_path(s: &str) -> Result<PathBuf, String> {
let path = PathBuf::from(s);
if path.exists() {
Ok(path)
} else {
Err(format!("路径不存在:{}", s))
}
}
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// 配置文件路径必须存在
#[arg(short, long, value_parser=validate_path)]
config: PathBuf,
}
fn main() {
let cli = Cli::parse();
println!("路径: {:?}", cli.config);
}
bash
cargo run -- -c Cargo.toml
六、累积参数
rust
use clap::Parser;
#[derive(Parser, Debug)]
struct Cli {
/// 详细程度,每多一个 -v 增加一级
/// 使用 action = ArgAction::Count
#[arg(short, long, action = clap::ArgAction::Count)]
verbose: u8,
/// 允许指定多个文件
/// 使用 action = ArgAction::Append
#[arg(short, long, action = clap::ArgAction::Append)]
files: Vec<String>,
}
fn main() {
let cli = Cli::parse();
// cargo run -- -vvv -f a.txt -f b.txt
// verbose = 3, files = ["a.txt", "b.txt"]
println!("Verbos = {}", cli.verbose);
println!("Files = {:?}", cli.files);
}
bash
cargo run -- -vvv -f a.txt -f b.txt # verbose = 3, files = ["a.txt", "b.txt"]
cargo run -- -v -f a # verbose = 1, files = ["a"]
七、参数分组
rust
use clap::Parser;
#[derive(Parser, Debug)]
#[command(group(
clap::ArgGroup::new("test")
.required(false)
.multiple(true)
.args(&["json","text"])
))]
struct Cli {
#[arg(long)]
json: bool,
#[arg(long)]
text: bool,
#[arg(long, short)]
query: String,
}
fn main() {
let cli = Cli::parse();
println!("{:?}", cli);
}
clap::ArgGroup::new("test")
- 创建分组。
.args(&["json","text"])
- 设置属于分组的参数,结构体中的字段名。
.required(true)
false:(默认值)组内可以都不传,0-多个。true:至少组内一个参数,1-多个。
.multiple(true)
false:(默认值)互斥关系,只能选择组内一个。true:非互斥关系。
bash
# 0-1 个
#[command(group(clap::ArgGroup::new("test").args(&["json","text"])))]
cargo run -- -q ss
cargo run -- --json -q ss
cargo run -- --text -q ss
# 只能 1 个
#[command(group(clap::ArgGroup::new("test").required(true).args(&["json","text"])))]
cargo run -- --json -q ss
cargo run -- --text -q ss
# 1 - 多个
#[command(group(clap::ArgGroup::new("test").required(true).multiple(true).args(&["json","text"])))]
cargo run -- --json -q ss
cargo run -- --text -q ss
cargo run -- --json --text -q ss
# 0 - 多个
#[command(group(clap::ArgGroup::new("test").required(false).multiple(true).args(&["json","text"])))]
cargo run -- -q ss
cargo run -- --json -q ss
cargo run -- --text -q ss
cargo run -- --json --text -q ss