对命令行参数解析
对 cli 工具的典型调用,如下所示:
$ grrs foobar test.txt
我们希望我们的程序,可以查阅test.txt
,并打印出包含foobar
的那些行。但是我们如何得到这两个值呢?
(调用的)程序名,之后的文本,通常称为“命令行参数”或“命令行标志”(尤其是当它们看起来像--this
的时候)。在内部,操作系统通常将它们表示为字符串列表,大致来说,它们由空格分隔。
这些参数有很多考虑方式,以及如何将它们解析为更容易处理的内容。您还需要告诉程序的用户,他们需要给出哪些参数,以及期望的格式。
获取参数
标准库包含函数std::env::args()
,这给了你一个迭代器,含有用户给出的命令行参数。第一个条目(在索引处0
)将是您的程序所称的名称(例如grrs
),接下去的条目是用户随后写下的。
以这种方式,获得原始参数非常容易:
let pattern = std::env::args().nth(1).expect("no pattern given");
let path = std::env::args().nth(2).expect("no path given");
作为数据类型的 CLI 参数
与其把它们看作一堆文本,不如把 cli 参数看作表示程序输入的自定义数据类型。
瞧瞧grrs foobar test.txt
:有两个参数,第一个是pattern
(模式)(要查找的字符串),然后path
(路径)(要查找的文件)。
我们还能对他们说些什么呢?嗯,首先,两者都是必需的。我们还没有讨论任何默认值,因此我们希望用户始终提供两个值。此外,我们可以稍微介绍一下它们的类型:模式应该是一个字符串,而第二个参数应该是一个文件的路径。
在 Rust 中,围绕所处理的数据,构建程序是很常见的,因此这种查看 CLI 参数的方式非常适合。让我们从这个开始:
struct Cli {
pattern: String,
path: std::path::PathBuf,
}
这定义了一个新的结构(一个struct
)它有两个字段用于存储数据:pattern
和path
。
现在,我们仍然需要得到进入程序的实际参数。一种选择是手动解析操作系统获得的字符串列表,并自己构建结构。它看起来像这样:
let pattern = std::env::args().nth(1).expect("no pattern given");
let path = std::env::args().nth(2).expect("no path given");
let args = Cli {
pattern: pattern,
path: std::path::PathBuf::from(path),
};
这样是可以的,但不太方便。但你要如何处理支持--pattern="foo"
或--pattern "foo"
的要求?你又如何实现--help
?
使用 StructOpt 分析 CLI 参数
一个更好的方法是运用一个可用库,当然还有许多其他可用库。最流行的用于分析命令行参数的库称为clap
. 它具有您所期望的所有功能,包括对,子命令的支持、shell 补全和伟大的帮助消息。
这个structopt
箱子建立在clap
之上,并提供一个“derive”宏,用来生成struct
定义的有关clap
代码。这很好:我们所要做的就是注释一个结构,而它将生成为,把(命令行)参数解析到字段中的代码。
我们先导入structopt
,具体先在Cargo.toml
文件的[dependencies]
部分,添加structopt = "0.2.10"
。
现在,在我们的代码中,写use structopt::StructOpt;
,并添加#[derive(StructOpt)]
,到我们struct Cli
的上面。同时,我们还将编写一些文档注释。
看起来像这样:
use structopt::StructOpt;
/// Search for a pattern in a file and display the lines that contain it.
#[derive(StructOpt)]
struct Cli {
/// The pattern to look for
pattern: String,
/// The path to the file to read
#[structopt(parse(from_os_str))]
path: std::path::PathBuf,
}
就在Cli
结构下面,我们的模板包含main
函数。当程序启动时,它将调用此函数。第一行是:
fn main() {
let args = Cli::from_args();
}
这将尝试将(命令行)参数解析为Cli
结构。
但如果失败了呢?下面就是这方式的好处:Clap 知道应期望哪个字段,以及它们期望的格式是什么。它可以自动生成一个--help
信息,以及一些重大错误(信息),建议您应把--output
,而不是--putput
作为传递参数。
这就是它的样子
在没有任何参数的情况下,运行它:
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 10.16s
Running `target/debug/grrs`
error: The following required arguments were not provided:
<pattern>
<path>
USAGE:
grrs <pattern> <path>
For more information try --help
我们可以在使用时传递参数cargo run
直接写在后面--
:
$ cargo run -- some-pattern some-file
Finished dev [unoptimized + debuginfo] target(s) in 0.11s
Running `target/debug/grrs some-pattern some-file`
如您所见,没有输出。很好:这意味着没有错误,我们的程序结束了。