Rust Clap库学习
Clap学习
本片内容主要参考clap的官方文档
在使用Rust
的库之前, 首先需要添加clap库:
cargo add clap --features derive
运行这个命令行会在Cargo.toml
中添加
clap = { version = "4.2.1", features = ["derive"] }
关于为什么要加features,可以阅读 Rust语言圣经中的内容.
或者可以直接手动在Cargo.toml
中添加clap库的配置.
从我添加的这个配置可以看到, 这篇文章是根据clap==4.2.1版本写的. 之前的版本我也不了解, 先不写了.
一.基础用法例子
use clap::Parser; /// Simple program to greet a person #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { /// Name of the person to greet #[arg(short, long)] name: String, /// Number of times to greet #[arg(short, long, default_value_t = 1)] count: u8, } fn main() { let args = Args::parse(); for _ in 0..args.count { println!("Hello {}!", args.name) } }
这个是官方文档中的例子, 这是使用derive
派生的方式实现.
从这个例子中可以看到, clap的使用方式是:
- 先创建一个
struct
, 其中的字段就是命令行的参数名称. - 给
struct
添加Parser
的派生. - 添加
command
, 为了控制命令行展示的行为, 也可以不添加. - 给参数添加
arg
, 为了控制单个参数的信息, 也可以不添加. 不添加每个参数都是必填的 - 在
main
函数中解析参数(Args::parse()
)
在这个例子中可以看到command
, arg
, short
, long
, default_value_t
这些名字, 下来先了解一下这些名字的含义,和为什么要使用这些名词.
二. clap概念说明
Attributes
官方文档中所说的Attributes
是指
- #[derive(Debug, Parser)]
- #[command(author, version, about, long_about = None)]
- #[arg(short, long, default_value_t = 1)]
使用 #[]
语法定义的属性注解。
这其中分为Raw attributes
和 Clap
自定义的Magic attributes
. 其中 derive
, command
, arg
就相当于Raw attributes
, command
下面的author
, version
就是Magic attributes
. 在Clap官方文档中有句话
Raw attributes are forwarded directly to the underlying clap builder. Any
Command
,Arg
, orPossibleValue
method can be used as an attribute.
说是raw attributes
会被转发给底层的clap builder
, 并且Command
方法等会被用作属性. 我理解的是: Command
, Arg
这些raw attributes
会作为 #[]
语法来使用, 这个转换是由clap builder
完成的. 官方给的例子是:
#[arg( global = true, // name = arg form, neat for one-arg methods required_if_eq("out", "file") // name(arg1, arg2, ...) form. )]
当点开clap builder
的连接,在其中再点开Command 的连接, 再看官方给的例子:
let m = Command::new("My Program") .author("Me, me@mail.com") .version("1.0.2") .about("Explains in brief what the program does") .arg( Arg::new("in_file") ) .after_help("Longer explanation to appear after the options when \ displaying the help information from --help or -h") .get_matches(); // Your program logic starts here...
我们可以理解成
#[command(author, version, about, long_about=None)]
被转化为
let m = Command::new("struct的名").author("").about("").long_about(None);
总结
command, arg, SubCommand 被称作Raw attributes
.
author, long, short, version等被称作Magic attributes
.
(如果可以这样理解的话)
这样我们就可以理解官方文档中的一些概念了.
三. Magic Attributes
首先要说一下Arg
和Command
的区别:
Arg
的定义是:命令行参数的抽象表示,用于设置定义程序有效参数的所有选项和关系.Command
是:建一个命令行界面.
从这个定义看, Command
是包含Arg
的概念的. 先有一个命令行的界面内容, 里面才有Arg
参数. 而magic attributes
则是控制每一个具体的功能项目.
接下来看一下clap提供的magic attributes
(只说一部分).
Command Attributes
name = <expr>
未设置时,取crate name(Parser中), 变量名(Subcommand中)version [=<expr>]
启用但未设置值时, crate version. 未启用为空author [=<expr>]
启用但未设置值时, crate authors. 未启用为空about [=<expr>]
启用但未设置值时, crate description. 未启用时为Doc commentlong_about [=<expr>]
启用但未设置值时, 使用Doc comment. 未启用时verbatim_doc_comment
在将doc注释转换为about/long_about时最小化预处理
从Command
的定义出发, 这些magic attributes
涉及的是程序版本, 作者, 介绍等, 整体和宏观的控制(比如后面例子中的next_line_help
).
Arg Attributes
id = <expr>
未设置时, 取struct中的字段名字,指定了就用指定名字value_parser [=<expr>]
未设置时, 根据类型使用value_parser!
的行为action [=<expr>]
未设置时, 使用ArgAction
的默认行为.help=<expr>
未设置时,使用文档注释内容
Arg
的magic attributes
是设置每个参数的属性和功能.
四.derive使用例子
依赖官方文件的例子理解.
例子1 简单使用
use clap::Parser; #[derive(Parser)] #[command(name="MyApp", author="AName", version="1.0", about="Does awesome things", long_about=None)] struct Cli { #[arg(long)] two: String, #[arg(long)] one: String, } fn main() { let cli = Cli::parse(); println!("two: {:?}", cli.two); println!("one: {:?}", cli.one); }
官方的例子可以改成一行command
属性, 这和分开写是一样的. one
和two
是必传的参数.
把command
中的值取消掉就会使用Cargo.toml
中的值
#[command(name, author, version, about, long_about=None)]
例子2 加入next_line_help
添加#[command(next_line_help = true)]
use clap::Parser; #[derive(Parser)] #[command(name="MyApp", author="AName", version="1.0", about="Does awesome things", long_about=None)] #[command(next_line_help = true)] struct Cli { /// 123456 #[arg(long)] two: String, /// 123456 #[arg(long)] one: String, } fn main() { let cli = Cli::parse(); println!("two: {:?}", cli.two); println!("one: {:?}", cli.one); }
效果是使用-h
/-help
. 添加next_line_help
和不添加,输出效果不同.
$ ./practice -h >> Does awesome things Usage: practice --two <TWO> --one <ONE> Options: --two <TWO> 123456 <-这里, 加入next_line_help --one <ONE> 123456 <-这里, 加入next_line_help -h, --help Print help <-这里, 加入next_line_help -V, --version Print version <-这里, 加入next_line_help $ ./practice -h >> Does awesome things Usage: practice --two <TWO> --one <ONE> Options: --two <TWO> 123456 <- 没加next_line_help --one <ONE> 123456 <- 没加next_line_help -h, --help Print help <- 没加next_line_help -V, --version Print version <- 没加next_line_help
例子3 位置参数
use clap::Parser; #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli { name: Option<String>, } fn main() { let cli = Cli::parse(); println!("name: {:?}", cli.name.as_deref()); }
name 只能接收一个参数值, 输入多个就会报错, 文档说ArgAction
的默认行为是Set
会收集多个参数, 但是要将name改为name: Vec<String>
, 感觉这并不能称为默认行为, 因为还要手动改.
$ ./practice bob >> name: Some("bob") $ ./practice bob tom >> error: unexpected argument 'tom' found
文档例子中#[arg(short, long)]
的作用是为name
参数设置单字母选项和长选项. 同时设置#[arg]
后会将name
放在Option
选项中. 否则是Arguments
中.
$ ./practice -h >> Usage: practice [NAME] Arguments: [NAME] Options: -h, --help Print help -V, --version Print version $ ./practice -h >> Usage: practice [OPTIONS] Options: -n, --name <NAME> -h, --help Print help -V, --version Print version
例子4 可选参数
use clap::Parser; #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli { #[arg(short, long)] name: Option<String>, } fn main() { let cli = Cli::parse(); println!("name: {:?}", cli.name.as_deref()); }
可选参数就是通过对变量类型的指定实现的, 如果是Option
的就是可选参数, 如果不赋值, 就使用空值None
.
例子5 标志位
use clap::Parser; #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli { #[arg(short, long)] verbose: bool, } fn main() { let cli = Cli::parse(); println!("verbose: {:?}", cli.verbose); }
使用标志位的方式就是, 将元素的类型设置成布尔值, 但是布尔值的特性是, 只能被设置一次, 第二次设置时会报错.
$ ./practice --verbose >> verbose: true $ ./practice --verbose --verbose >> error: the argument '--verbose' cannot be used multiple times
使用action = clap::ArgAction::Count
, 同时将元素类型设置为int可以对参数的数量进行计数.
use clap::Parser; #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli { #[arg(short, long, action = clap::ArgAction::Count)] verbose: u8, } fn main() { let cli = Cli::parse(); println!("verbose: {:?}", cli.verbose); } $ ./practice --verbose -v -v >> name: 3
例子6 子命令
子命令可以是另一套参数集合, 比如git
命令有自己的参数, git config
也有自己的参数, config
就是子命令.
use clap::{Parser, Subcommand}; #[derive(Parser)] #[command(author, version, about, long_about = None)] #[command(propagate_version = true)] struct Cli { #[command(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { /// Adds files to myapp Add { name: Option<String> }, } fn main() { let cli = Cli::parse(); // You can check for the existence of subcommands, and if found use their // matches just as you would the top level cmd match &cli.command { Commands::Add { name } => { println!("'myapp add' was used, name is: {name:?}") } } }
设置子命令是可选的
use clap::{Parser, Subcommand}; #[derive(Parser)] #[command(author, version, about, long_about = None)] #[command(propagate_version = true)] struct Cli { #[command(subcommand)] command: Option<Commands>, } #[derive(Subcommand)] enum Commands { /// Adds files to myapp Add { name: Option<String> }, } fn main() { let cli = Cli::parse(); // You can check for the existence of subcommands, and if found use their // matches just as you would the top level cmd match &cli.command { Some(Commands::Add { name }) => { println!("'myapp add' was used, name is: {name:?}") }, None => { println!("'myapp add' don't used") } } }
例子7 设置默认值
use clap::Parser; #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli { #[arg(default_value_t = 2020)] port: u16, } fn main() { let cli = Cli::parse(); println!("port: {:?}", cli.port); }
例子8 校验输入值, 使用枚举校验有限的值
use clap::{Parser, ValueEnum}; #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli { /// What mode to run the program in #[arg(value_enum)] -> 这里 mode: Mode, } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] ->这里 enum Mode { /// Run swiftly Fast, /// Crawl slowly but steadily /// /// This paragraph is ignored because there is no long help text for possible values. Slow, } fn main() { let cli = Cli::parse(); match cli.mode { Mode::Fast => { println!("Hare"); } Mode::Slow => { println!("Tortoise"); } } }
要列举出可以输入的值, 需要使用枚举类型, clap
提供的是Arg::value_enum
将参数值解析为ValueEnum
.
例子9 使用value_parser!验证值
value_parser!
不是支持所有的类型, 只是支持下列的类型:- bool, String, OsString, PathBuf
- u8, i8, u16, i16, u32, i32, u64, i64
- ValueEnum
- From
From<&OsStr> - From
From<&str> - FromStr
use clap::Parser; #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli { /// Network port to use #[arg(value_parser = clap::value_parser!(u16).range(1..))] port: u16, } fn main() { let cli = Cli::parse(); println!("PORT = {}", cli.port); }
例子10 自定义验证逻辑
use std::ops::RangeInclusive; use clap::Parser; #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli { /// Network port to use #[arg(value_parser = port_in_range)] -> 这里添加自定义的函数入口 port: u16, } fn main() { let cli = Cli::parse(); println!("PORT = {}", cli.port); } const PORT_RANGE: RangeInclusive<usize> = 1..=65535; fn port_in_range(s: &str) -> Result<u16, String> { -> 注意函数签名 let port: usize = s .parse() .map_err(|_| format!("`{s}` isn't a port number"))?; if PORT_RANGE.contains(&port) { Ok(port as u16) } else { Err(format!( "port not in range {}-{}", PORT_RANGE.start(), PORT_RANGE.end() )) } }
文档中给的解析器的类型是
value_parser!(T) for auto-selecting a value parser for a given type Or range expressions like 0..=1 as a shorthand for RangedI64ValueParser Fn(&str) -> Result<T, E> [&str] and PossibleValuesParser for static enumerated values BoolishValueParser, and FalseyValueParser for alternative bool implementations NonEmptyStringValueParser for basic validation for strings or any other TypedValueParser implementation
解析函数的签名是Fn(&str) -> Result<T, E>
自定义的解析函数需要符合这个函数签名.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!