本章既是一个目前所学的很多技能的概括,也是一个更多标准库功能的探索。我们将构建一个与文件和命令行输入/输出交互的命令行工具来练习现在一些你已经掌握的 Rust 技能。
Rust 的运行速度、安全性、单二进制文件输出和跨平台支持使其成为创建命令行程序的绝佳选择,所以我们的项目将创建一个我们自己版本的经典命令行工具:grep
。grep 是 “Globally search a Regular Expression and Print.” 的首字母缩写。grep
最简单的使用场景是在特定文件中搜索指定字符串。为此,grep
获取一个文件名和一个字符串作为参数,接着读取文件并找到其中包含字符串参数的行,然后打印出这些行。
在这个过程中,我们会展示如何让我们的命令行工具利用很多命令行工具中用到的终端功能。读取环境变量来使得用户可以配置工具的行为。打印到标准错误控制流(stderr
) 而不是标准输出(stdout
),例如这样用户可以选择将成功输出重定向到文件中的同时仍然在屏幕上显示错误信息。
12.1 接受命令参数行
一如既往使用cargo new 新建一个项目,我们称之为minigrep 以便与可能已经安装在系统上的grep 工具相区别。
第一个任务是让 minigrep
能够接受两个命令行参数:文件名和要搜索的字符串。也就是说我们希望能够使用 cargo run
、要搜索的字符串和被搜索的文件的路径来运行程序,像这样:
cargo run searchstring example-filename.txt
读取参数值
为了确保 minigrep
能够获取传递给它的命令行参数的值,我们需要一个 Rust 标准库提供的函数,也就是 std::env::args
。这个函数返回一个传递给程序的命令行参数的 迭代器(iterator)。但是现在只需理解迭代器的两个细节:迭代器生成一系列的值,可以在迭代器上调用 collect
方法将其转换为一个集合,比如包含所有迭代器产生元素的 vector。
use std::env;
fn main() {
let args : Vec<String> = env::args().collect();
println!("{:?}", args);
}
首先使用 use
语句来将 std::env
模块引入作用域以便可以使用它的 args
函数。注意 std::env::args
函数被嵌套进了两层模块中。
注意
std::env::args
在其任何参数包含无效 Unicode 字符时会 panic。如果你需要接受包含无效 Unicode 字符的参数,使用std::env::args_os
代替。这个函数返回OsString
值而不是String
值。这里出于简单考虑使用了std::env::args
,因为OsString
值每个平台都不一样而且比String
值处理起来更为复杂。
在 main
函数的第一行,我们调用了 env::args
,并立即使用 collect
来创建了一个包含迭代器所有值的 vector。collect
可以被用来创建很多类型的集合,所以这里显式注明 args
的类型来指定我们需要一个字符串 vector。虽然在 Rust 中我们很少会需要注明类型,然而 collect
是一个经常需要注明类型的函数,因为 Rust 不能推断出你想要什么类型的集合。
最后,我们使用调试格式 :?
打印出 vector。让我们尝试分别用两种方式(不包含参数和包含参数)运行代码:
注意 vector 的第一个值是 "target/debug/minigrep"
,它是我们二进制文件的名称。这与 C 中的参数列表的行为相匹配,让程序使用在执行时调用它们的名称。如果要在消息中打印它或者根据用于调用程序的命令行别名更改程序的行为,通常可以方便地访问程序名称,不过考虑到本章的目的,我们将忽略它并只保存所需的两个参数。
将参数值保存进变量
打印出参数 vector 中的值展示了程序可以访问指定为命令行参数的值。现在需要将这两个参数的值保存进变量这样就可以在程序的余下部分使用这些值了。
use std::env;
fn main() {
let args : Vec<String> = env::args().collect();
let query = &args[1]; // 索引0是文件名,所以从1开始?
let file_name = &args[2];
println!("Searching for {}", query);
println!("In file {}", file_name);
}
结果
正如之前打印出 vector 时所所看到的,程序的名称占据了 vector 的第一个值 args[0]
,所以我们从索引 1
开始。minigrep
获取的第一个参数是需要搜索的字符串,所以将其将第一个参数的引用存放在变量 query
中。第二个参数将是文件名,所以将第二个参数的引用放入变量 filename
中。
12.2 读取文件
参考:一个 I/O 项目:构建命令行程序 - Rust 程序设计语言 简体中文版 (bootcss.com)