接收命令行参数
现在来与往常一样,使用 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 上的一些既有库,可以帮助编写除接收命令行参数的程序,而由于咱们才开始了解这个概念,因此就要咱们自己来实现这项功能。
读取参数值
为开启 minigrep
对传给他的命令行参数值的读取,这里将需要在 Rust 标准库中所提供的 std::env::args
函数。该函数返回的是那些传递给 minigrep
命令行参数的一个迭代器。后面的 第 13 章 就会讲到迭代器。而现在,就只需要知道迭代器的两个细节:迭代器会产生出一些列值,而在某个迭代器上调用 collect
方法,就可以将其转换成比如矢量这这样的一个、包含着迭代器产生的全部元素的集合。
下面清单 12-1 中的代码,实现了minigrep
程序读取全部传递给他的命令行参数,并于随后将这些值收集到一个矢量中。
文件名:src/main.rs
use std::env; fn main() { let args: Vec<String> = env::args().collect(); dbg! (args); }
清单 12-1:将命令行参数,收集到一个矢量中并把他们打印出来
这里首先使用了一个 use
语句,将那个 std::env
模组带入到了作用域,如此就可以使用他的 args
函数了。请注意这个 std::env::args
函数,是嵌套在两个层级的模组中的。如同在 第 7 章 处所讨论过的,在那些所需函数是嵌套于多个模组中的情形下,那里就选择将其中的父模组带入到作用域,而非该函数本身。经由这样做,就可以轻易地使用到 std::env
中的其他函数了。同时相比于添加 use std::env::args
并在随后只使用 args
调用这个函数,这样做也不那么含糊其辞,这是由于 args
这个名字,可能稍不留意就会被误用为定义在当前模组中的某个函数。
args
函数与无效 Unicode 字符请注意
std::env::args
在由任何参数包含了无效 Unicode 字符时,将会中止运行。在程序需要接收包含了无效 Unicode 字符的参数时,就要使用std::env::args_os
。那个函数返回的是一个产生出OsString
值,而非String
值的迭代器。由于各个平台上的OsString
值有所区别,且相比使用String
值,OsString
使用起来要更为复杂,因此为简化起见,这里使用的是std::env::args
。
在 main
函数的第一行,这里调用了 env::args
,并立即使用 collect
来将其所返回的那个迭代器,转换为一个包含由该迭代器所产生全部值的矢量值。由于使用这个 collect
函数,即可创建出许多类别的集合来,因此这里就显示地对 args
变量的类型进行了注解,来指明这里要的是一个字符串的矢量。尽管在 Rust 中,极少需要对类型加以注解,不过这个 collect
函数就是一个通常需要注解的函数,这是由于 Rust 无法推断出,代码编写者想要的集合类别来。
最后,这里使用了调试宏(dbg!
),打印出那个矢量。下面就来尝试先不带参数运行该代码,尔后再带上两个参数:
$ cargo run lennyp@vm-manjaro
Compiling minigrep v0.1.0 (/home/lennyp/rust-lang/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 0.39s
Running `target/debug/minigrep`
[src/main.rs:6] args = [
"target/debug/minigrep",
]
$ cargo run -- 检索字符串😀 demo.txt lennyp@vm-manjaro
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/minigrep '检索字符串😀' demo.txt`
[src/main.rs:6] args = [
"target/debug/minigrep",
"检索字符串😀",
"demo.txt",
]
请注意这个矢量中的首个值,即 "target/debug/minigrep"
,就是这里二进制程序文件的名字。这一点符合了 C 语言中参数清单的行为,让程序运用到其被触发执行的那个名字(this matches the behavior of the arguments list in C, letting programms use the name by which they were invoked in their execution)。在要于消息中打印出程序名字,或根据用于触发该程序运行的何种命令行别名,而对程序行为加以改变这样的情形下,有着对程序名字的访问,通常就比较便利。而对于本章的目的,这里就会忽略这首个参数,而只保存这里所需的那两个参数。
将参数值保存在变量中
这个程序此刻就可以访问到被指定为命令行参数的那些值了。现在这里需要将这两个参数的值,保存在变量中,如此就可以在这个程序的整个其余部分,使用到这些值了。在下面清单 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! ("在文件 {} 中检索:{}", file_path, query); }
清单 12-2:创建两个变量来保存查询参数与文件路径参数
与在打印这个矢量时所看到的一样,该程序的名字,占据了那个矢量中 args[0]
处的首个值,因此这里是于索引 1
处开始参数的。minigrep
取的第一个参数,即为这里正检索的字符串,因此这里把到这首个参数的索引,放在了变量 query
中。第二个参数将是那个文件路径,因此这里把到那第二个参数的索引,放在了变量 file_path
中。
这里临时性地将这两个变量的值打印出来,以证实该代码是如打算那样运行。下面就来以参数 test
和 sample.txt
,再次运行这个程序:
$ cargo run -- test sample.txt lennyp@vm-manjaro
Compiling minigrep v0.1.0 (/home/lennyp/rust-lang/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 0.35s
Running `target/debug/minigrep test sample.txt`
在文件 sample.txt 中检索:test
很好,这个程序工作了!所需参数的那些值正被保存到恰当的变量中。后面就要添加一些错误处理,来处理某些潜在的错误情形,诸如在用户未提供参数这样的情况;现在,这里将忽略那样的情况,而是会编写添加文件读取能力的代码。