标准库提供了std::env::args()用来获取命令行的参数,第一个值是程序的名称,这和其它语言中的获取参数的方式类似:
1 2 3 4 5 6 7 let args : Vec <String > = env::args ().collect ();let query = &args[1 ];let filename = &args[2 ];println! ("Searching for {}" , query);println! ("In file {}" , filename);
但是在产品开发的过程中,我们需要比较多的程序参数,并且需要一定的规则和校验,这个时候我们就需要使用其它的一些库对这些参数进行解析,比如structopt 库。
structopt可以方便的将命令行参数解析为一个struct。
下面是官方的一个例子,可以cargo run -- --help测试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 use std::path::PathBuf;use structopt::StructOpt;#[derive(StructOpt, Debug)] #[structopt(name = "basic" )] struct Opt { #[structopt(short, long)] debug: bool , #[structopt(short, long, parse(from_occurrences))] verbose: u8 , #[structopt(short, long, default_value = "42" )] speed: f64 , #[structopt(short, long, parse(from_os_str))] output: PathBuf, #[structopt(short = "c" , long)] nb_cars: Option <i32 >, #[structopt(short, long)] level: Vec <String >, #[structopt(name = "FILE" , parse(from_os_str))] files: Vec <PathBuf>, } fn main () { let opt = Opt::from_args (); println! ("{:#?}" , opt); }
我们定义一个struct:来保存命令行参数: Opt, 这个struct的定义的时候使用来宏来定义参数的一些属性。 然后通过一行代码let opt = Opt::from_args();就可以把命令行参数解析为Opt的一个实例。
官方库提供了很多的例子 ,可以用来了解和学习structopt的功能和使用方法。
structopt 使用 作为参数的解析,但是通过宏的方式,大大简化了clap的使用难度。
首先我们看看structopt通过宏对上面的例子做了什么处理。
structopt 宏的花招 structopt 为 Opt 实现了 structopt::StructOpt trait:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #[allow(unused_variables)] impl ::structopt::StructOpt for Opt { fn clap <'a , 'b >() -> ::structopt::clap::App<'a , 'b > { let app = ::structopt::clap::App::new ("basic" ) .about ("A basic example" ) .version ("0.1.0" ); Self ::augment_clap (app) } fn from_clap (matches: &::structopt::clap::ArgMatches) -> Self { Opt { debug: matches.is_present ("debug" ), verbose: { |v| v as _ }(matches.occurrences_of ("verbose" )), speed: matches .value_of ("speed" ) .map (|s| ::std::str ::FromStr::from_str (s).unwrap ()) .unwrap (), output: matches .value_of_os ("output" ) .map (::std::convert::From ::from) .unwrap (), nb_cars: matches .value_of ("nb-cars" ) .map (|s| ::std::str ::FromStr::from_str (s).unwrap ()), level: matches .values_of ("level" ) .map (|v| { v.map (|s| ::std::str ::FromStr::from_str (s).unwrap ()) .collect () }) .unwrap_or_else (Vec ::new), files: matches .values_of_os ("file" ) .map (|v| v.map (::std::convert::From ::from).collect ()) .unwrap_or_else (Vec ::new), } } }
fn clap<'a, 'b>() -> clap::App<'a, 'b> 生成一个clap::App,这个App的名称就是我们定义的basic,我们把没有定义about属性,所以这里它取注释作为about描述信息,这是clap库使用的姿势,clap应用定义一些属性和参数:
。
但是我们使用structopt并不是要创建一个clap应用,只是用来解析参数,映射成一个struct,所以这里创建的clap app只是一个辅助处理参数的对象。
在clap()方法中还调用了augment_clap(app)函数,这个函数在下面定义,定义了App的参数。
fn from_clap(matches: &ArgMatches) -> Self是将clap的App对象中的参数映射成Opt的字段。
比如speed字段:
1 2 3 4 speed: matches .value_of("speed" ) .map(|s| ::std::str::FromStr::from_str(s).unwrap()) .unwrap(),
配置clap app Args的方法在函数augment_clap中:
参数的属性配置依照Opt中各个字段的定义而生成。
这样,当我们在代码中调用let opt = Opt::from_args()时候,实际调用from_clap(&Self::clap().get_matches())
整体可以看到,structopt其实就是把宏的各种定义转换成clap的配置,我们可以学习它的宏的复杂的运用。
属性 你定义的struct会映射成 clap::App, 而这个struct的非子命令字段会映射成clap::Arg。 通过属性#[structopt(...)]进行设置,所以让我们看看它的属性。
structopt的属性可以分为两类:
structopt自己的magical method: structopt自己使用,attr = ["whatever"]或者attr(args...)格式
raw attributes: 映射成clap::Arg/App的方法调用, #[structopt(raw(...))]格式
raw 属性/方法 属性和 clap::App/clap::Arg一一对应。
格式:
#[structopt(method_name = single_arg)] 或者 #[structopt(method_name(arg1, arg2))]
magical 属性/方法 比如name、version、no_version、author、about、short、long、rename_all、parse、skip、flatten、subcommand。
详细信息可以参考: Magical methods 。
类型 定义了一些内置类型,以及对应的clap方法。
bool: .takes_value(false).multiple(false)
Option<T: FromStr>: .takes_value(true).multiple(false)
Option<Option<T: FromStr>>:.takes_value(true).multiple(false).min_values(0).max_values(1)
Vec<T: FromStr>: .takes_value(true).multiple(true)
Option<Vec<T: FromStr>: .takes_values(true).multiple(true).min_values(0)
T: FromStr: .takes_value(true).multiple(false).required(!has_default)
子命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #[derive(StructOpt)] #[structopt(about = "the stupid content tracker" )] enum Git { Add { #[structopt(short)] interactive: bool , #[structopt(short)] patch: bool , #[structopt(parse(from_os_str))] files: Vec <PathBuf> }, Fetch { #[structopt(long)] dry_run: bool , #[structopt(long)] all: bool , repository: Option <String > }, Commit { #[structopt(short)] message: Option <String >, #[structopt(short)] all: bool } }
定制字符串解析 如果类型没有实现FromStr trait, 或者你就想定制解析方式,你可以设置自定义的解析方法。
更多的信息可以查看structopt的文档doc.rs/structopt 。
在开发cli/terminal应用程序的时候,如果你不想这种声明式的获取参数的方式,那么你可以直接使用clap 库, 这个库功能强大,也被广泛使用。
也有一些基于structopt的扩展库:
paw 将rust main函数转换成c风格的带传入参数的main函数,也可以结合structopt使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 use std::io::prelude::*;use std::net::TcpListener;#[derive(structopt::StructOpt)] struct Args { #[structopt(short = "p" , long = "port" , env = "PORT" , default_value = "8080" )] port: u16 , #[structopt(short = "a" , long = "address" , default_value = "127.0.0.1" )] address: String , } #[paw::main] fn main (args: Args) -> Result <(), std::io::Error> { let listener = TcpListener::bind ((&*args.address, args.port))?; println! ("listening on {}" , listener.local_addr ()?); for stream in listener.incoming () { stream?.write (b"hello world!" )?; } Ok (()) }