对命令行参数解析

对 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)它有两个字段用于存储数据:patternpath

现在,我们仍然需要得到进入程序的实际参数。一种选择是手动解析操作系统获得的字符串列表,并自己构建结构。它看起来像这样:

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`

如您所见,没有输出。很好:这意味着没有错误,我们的程序结束了。