Rust命令行解析程序:Clap

发布时间 2023-10-12 11:31:31作者: iZhang

Rust命令行解析程序:Clap

基于Clap 4.4.6

参考资料:Clap官方手册

Chapter 1

配置解析器

使用Command结构体的new方法构建解析器:

// arg.exe
use clap::{arg, Command};

fn main() {
    // 只要调用clap解析命令行参数,--help和--version会自动添加到命令行参数列表中
    // 使用arg!宏定义了两个命令行参数:two, one
    let matches = Command::new("MyApp")
        .version("1.0")
        .author("Kevin K. <kbknapp@gmail.com>")
        .about("Does awesome things")
        .arg(arg!(--two <VALUE>).required(true))
        .arg(arg!(--one <VALUE>).required(true))
        .get_matches();

    println!(
        "two: {:?}",
        matches.get_one::<String>("two").expect("required")
    );
    println!(
        "one: {:?}",
        matches.get_one::<String>("one").expect("required")
    );
}

用法:

  • arg.exe --two foo --one bar:两个参数分别对应foobar
  • arg.exe -harg.exe --help:输出帮助信息
  • arg.exe -varg.exe --version:输出版本号

使用command!宏构建解析器:

use clap::{arg, command};

fn main() {
    // 需要启用 `cargo` 这个feature,在Cargo.toml的clap依赖中添加;
    // requires `cargo` feature, reading name, version, author, and description from `Cargo.toml`
    let matches = command!()
        .arg(arg!(--two <VALUE>).required(true))
        .arg(arg!(--one <VALUE>).required(true))
        .get_matches();

    println!(
        "two: {:?}",
        matches.get_one::<String>("two").expect("required")
    );
    println!(
        "one: {:?}",
        matches.get_one::<String>("one").expect("required")
    );
}

使用Command::next_line_help方法把帮助信息中的参数描述挪到下一行打印,需要启用 cargofeature

use clap::{arg, command, ArgAction};

fn main() {
    let matches = command!() // requires `cargo` feature
        .next_line_help(true)
        .arg(arg!(--two <VALUE>).required(true).action(ArgAction::Set))
        .arg(arg!(--one <VALUE>).required(true).action(ArgAction::Set))
        .get_matches();

    println!(
        "two: {:?}",
        matches.get_one::<String>("two").expect("required")
    );
    println!(
        "one: {:?}",
        matches.get_one::<String>("one").expect("required")
    );
}

Chapter 2

Positionals

无需输入参数名称,直接输入参数内容,按位对应

// arg.exe
fn main() {
    let matches = command!() // requires `cargo` feature
        .arg(Arg::new("name"))
        .arg(Arg::new("age"))
        .get_matches();
	// get_one返回一个Option,如果用户有输入则解析出来,没有输入则是None
    println!("name: {:?}, age: {:?}", matches.get_one::<String>("name"), matches.get_one::<String>("age"));
}

用法:arg.exe zhangsan

输出:name: Some("zhangsan"), age: None

为一个参数传递多个内容,需要更改解析这个参数时的行为,相关方法为action(),其参数为枚举类型ArgAction

use clap::{command, Arg, ArgAction};

fn main() {
    let matches = command!() // requires `cargo` feature
        .arg(Arg::new("name").action(ArgAction::Append))
        .get_matches();

    let args = matches
        .get_many::<String>("name")
        .unwrap_or_default()
        .map(|v| v.as_str())
        .collect::<Vec<_>>();

    println!("names: {:?}", &args);
}

用法:arg.exe zhangsan lisi

输出:names: ["zhangsan", "lisi"]

Options

前面一小节传递参数无需指明参数的名称,直接传递内容;接下来为每个参数指定名称,这样在传递参数时更清晰,并且可以不按位置传递

use clap::{command, Arg, ArgAction};

fn main() {
    let matches = command!() // requires `cargo` feature
        .arg(Arg::new("name").short('n').long("name").action(ArgAction::Append)) // 定义新参数,id为name,传入可缩写为n,扩写为name, 是列表
        .arg(Arg::new("age").short('a').long("age")) // 定义新参数,id为age,缩写为a,扩写为age,是个Set(即只能传入一个)
        .get_matches();

    let args = matches
    .get_many::<String>("name")
    .unwrap_or_default()
    .map(|v| v.as_str())
    .collect::<Vec<_>>();

    println!("names: {:?}", &args);
    println!("age: {:?}", matches.get_one::<String>("age"));
}

当我们给参数定义缩写或者扩写时,如果要传递(因为并没有指明这是个必选参数)这个参数,传入时必须使用-n或者--name,其后接参数内容;另外,我们指明name参数接收的是个列表,所以如果给name传递多个参数,每次传递时都要使用-n或者--name,否则无法识别;另外,由于我们给参数指定了缩写或者扩写,因此可以无序传递

以下传递方式均合法:

arg.exe -n zhangsan -a 18 --name lisi -n wangwu

arg.exe -a 13 --name zhangsan -n lisi

arg.exe:由于两个参数均非必选,所以可以一个也不传递

以下传递方式非法:

arg.exe -a 18 -a 9:age参数为Set,只能传递一次

arg.exe zhangsan -a 2:没有使用-n或者--name

arg.exe zangsan lisi -a 8:同上

Flags

根据文档给的示例,把Flags理解为开关即可

use clap::{command, Arg, ArgAction};

fn main() {
    let matches = command!() // requires `cargo` feature
        .arg(
            Arg::new("verbose")
                .short('v')
                .long("verbose")
                .action(ArgAction::SetTrue),
        )
        .get_matches();

    println!("verbose: {:?}", matches.get_flag("verbose"));
}

将verbose的action设置为ArgAction::SetTrue,如果在参数中出现-v或者--verbose即为打开,否则默认为关闭;而ArgAction::SetFalse的行为相反。

use clap::{command, Arg, ArgAction};

fn main() {
    let matches = command!() // requires `cargo` feature
        .arg(
            Arg::new("verbose")
                .short('v')
                .long("verbose")
                .action(ArgAction::Count),
        )
        .get_matches();

    println!("verbose: {:?}", matches.get_count("verbose"));
}

将verbose的action设置为ArgAction::Count,即统计-v或者--verbose在参数中出现的次数

用法:arg.exe -vvv -v --verbose

输出:verbose: 5

Subcommand

子命令也是Command,通过Command::subcommand来添加;子命令可以有自己的版本号、帮助信息等,甚至可以有自己的子命令,即可以嵌套

use clap::{arg, command, Command};

fn main() {
    let matches = command!() // requires `cargo` feature
        .propagate_version(true)
        .subcommand_required(true)
        .arg_required_else_help(true)
        .subcommand(
            Command::new("add")
                .about("Adds files to myapp")
                .arg(arg!([NAME])),
        )
        .get_matches();

    match matches.subcommand() {
        Some(("add", sub_matches)) => println!(
            "'myapp add' was used, name is: {:?}",
            sub_matches.get_one::<String>("NAME")
        ),
        _ => unreachable!("Exhausted list of subcommands and subcommand_required prevents `None`"),
    }
}

添加子命令add,并且该子命令有一个参数,并且该子命令为必选,如果没有则显示帮助信息

用法:arg.exe add hello

输出:myapp add' was used, name is: Some("hello")

Defaults

use clap::{arg, command, value_parser};

fn main() {
    let matches = command!() // requires `cargo` feature
        .arg(
            arg!([PORT])
                .value_parser(value_parser!(u16))
                .default_value("2020"),
        )
        .get_matches();

    println!(
        "port: {:?}",
        matches
            .get_one::<u16>("PORT")
            .expect("default ensures there is always a value")
    );
}

默认选项。当用户没有传递该参数时,使用默认值

用法:arg.exe 443arg.exe

输出:port: 443port: 2020