接受命令行参数
让我们一如既往地使用 cargo new
创建一个新项目。我们将我们的项目称为
minigrep
将其与系统上可能已有的 grep
工具区分开来。
$ cargo new minigrep
Created binary (application) `minigrep` project
$ cd minigrep
第一个任务是让 minigrep
接受它的两个命令行参数:文件路径和要搜索的字符串。也就是说,我们希望能够使用 cargo run
来运行我们的程序,两个连字符表示以下参数用于我们的程序而不是 cargo
,一个要搜索的字符串,以及一个要搜索的文件路径,如下所示:
$ cargo run -- searchstring example-filename.txt
现在,cargo new
生成的程序无法处理我们给它的参数。crates.io 上的一些现有库可以帮助编写接受命令行参数的程序,但因为你只是在学习这个概念,所以让我们自己实现这个功能。
读取 Argument 值
要使 minigrep
能够读取我们传递给它的命令行参数的值,我们需要 Rust 标准库中提供的 std::env::args
函数。此函数返回传递给 minigrep
的命令行参数的迭代器。我们将在第 13 章中全面介绍迭代器。现在,你只需要了解关于迭代器的两个细节:迭代器产生一系列值,我们可以在迭代器上调用 collect
方法,将其转换为包含迭代器生成的所有元素的集合,例如向量。
示例 12-1 中的代码允许你的 minigrep
程序读取传递给它的任何命令行参数,然后将这些值收集到一个 vector 中。
文件名: src/main.rs
use std::env; fn main() { let args: Vec<String> = env::args().collect(); dbg!(args); }
示例 12-1:将命令行参数收集到 vector 中并打印出来
首先,我们使用 use
语句将 std::env
模块引入范围,以便我们可以使用它的 args
函数。请注意,std::env::args
函数嵌套在两个级别的模块中。正如我们在本章中讨论的那样
7,在所需函数嵌套在多个模块中的情况下,我们选择将父模块而不是函数引入范围。通过这样做,我们可以轻松地使用 std::env
中的其他函数。它也比添加 use std::env::args
然后只用 args
调用函数要少一些歧义,因为 args
很容易被误认为是当前模块中定义的函数。
args
函数和无效的 Unicode
请注意,如果任何参数包含无效的 Unicode,std::env::args
将 panic。如果您的程序需要接受包含无效 Unicode 的参数,请改用 std::env::args_os
。该函数返回一个迭代器,该迭代器生成 OsString
值而不是 String
值。为简单起见,我们在此处选择使用 std::env::args
,因为 OsString
值因平台而异,并且比 String
值更复杂。
在 main
的第一行,我们调用 env::args
,然后立即使用
collect
将迭代器转换为包含迭代器生成的所有值的向量。我们可以使用 collect
函数来创建多种类型的集合,因此我们显式注释 args
的类型以指定我们想要一个字符串向量。虽然你很少需要在 Rust 中注释类型,但 collect
是你经常需要注释的一个函数,因为 Rust 无法推断你想要的集合类型。
最后,我们使用 debug 宏打印向量。让我们尝试先运行没有参数的代码,然后再运行有两个参数的代码:
$ cargo run
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.61s
Running `target/debug/minigrep`
[src/main.rs:5:5] args = [
"target/debug/minigrep",
]
$ cargo run -- needle haystack
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.57s
Running `target/debug/minigrep needle haystack`
[src/main.rs:5:5] args = [
"target/debug/minigrep",
"needle",
"haystack",
]
请注意,vector 中的第一个值是 “target/debug/minigrep”
,这是我们的二进制文件的名称。这与 C 语言中 arguments 列表的行为相匹配,允许程序在执行时使用调用它们的名称。如果要在消息中打印程序名称,或者根据用于调用程序的命令行别名更改程序的行为,访问程序名称通常很方便。但出于本章的目的,我们将忽略它,只保存我们需要的两个参数。
将参数值保存在变量中
该程序当前能够访问指定为命令行参数的值。现在我们需要将两个参数的值保存在变量中,以便我们可以在程序的其余部分使用这些值。我们在示例 12-2 中这样做。
文件名: src/main.rs
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let query = &args[1];
let file_path = &args[2];
println!("Searching for {query}");
println!("In file {file_path}");
}
示例 12-2:创建变量来保存 query 参数和 file path 参数
正如我们在打印 vector 时所看到的,程序的名称在 args[0]
处占据了 vector 中的第一个值,因此我们从索引 1 开始参数。minigrep
采用的第一个参数是我们正在搜索的字符串,因此我们在变量 query
中引用第一个参数。第二个参数将是文件路径,因此我们在变量 file_path
中引用第二个参数。
我们临时打印这些变量的值,以证明代码正在按预期工作。让我们使用参数 test
再次运行这个程序
和 sample.txt
:
$ cargo run -- test sample.txt
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
Running `target/debug/minigrep test sample.txt`
Searching for test
In file sample.txt
太好了,程序正在运行!我们需要的参数的值被保存到正确的变量中。稍后我们将添加一些错误处理来处理某些潜在的错误情况,例如当用户不提供任何参数时;现在,我们将忽略这种情况,改为添加文件读取功能。