使用环境变量
我们将不断改进minigrep
通过添加额外的功能:用于
不区分大小写的搜索,用户可以通过环境开启
变量。我们可以将此功能设为命令行选项,并要求
用户每次希望应用它时都输入它,但将其设为
环境变量,我们允许用户设置一次环境变量
并在该终端会话中让他们的所有搜索不区分大小写。
为不区分大小写的测试编写失败的测试search
功能
我们首先添加一个新的search_case_insensitive
函数,该函数将在
环境变量具有值。我们将继续遵循 TDD 流程,
所以第一步是再次编写一个失败的测试。我们将为
新的search_case_insensitive
函数,并将旧测试从one_result
自case_sensitive
澄清两者之间的区别
测试,如示例 12-20 所示。
use std::error::Error;
use std::fs;
pub struct Config {
pub query: String,
pub file_path: String,
}
impl Config {
pub fn build(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let file_path = args[2].clone();
Ok(Config { query, file_path })
}
}
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.file_path)?;
for line in search(&config.query, &contents) {
println!("{line}");
}
Ok(())
}
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}
results
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn case_sensitive() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";
assert_eq!(vec!["safe, fast, productive."], search(query, contents));
}
#[test]
fn case_insensitive() {
let query = "rUsT";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";
assert_eq!(
vec!["Rust:", "Trust me."],
search_case_insensitive(query, contents)
);
}
}
请注意,我们编辑了旧测试的contents
太。我们添加了一条新线路
替换为文本"Duct tape."
使用不应与查询匹配的大写 D"duct"
当我们以区分大小写的方式进行搜索时。更改旧测试
这样有助于确保我们不会意外地断开区分大小写的
我们已经实现的搜索功能。此测试现在应该通过
并且在我们进行不区分大小写的搜索时应该继续传递。
不区分大小写的搜索的新测试使用"rUsT"
作为其查询。在
这search_case_insensitive
函数中,查询"rUsT"
应匹配包含"Rust:"
替换为大写字母 R,并将
线"Trust me."
即使两者的大小写与查询不同。这
是我们失败的测试,并且它不会编译,因为我们还没有定义
这search_case_insensitive
功能。随意添加骨架
实现总是返回一个空 vector,类似于我们所做的
对于search
示例 12-16 中的函数来查看测试 compile 和 fail。
实施search_case_insensitive
功能
这search_case_insensitive
函数,如示例 12-21 所示,将几乎是
与search
功能。唯一的区别是我们将小写
这query
和每个line
因此,无论 input 参数的情况如何,
当我们检查该行是否包含查询时,它们的情况相同。
use std::error::Error;
use std::fs;
pub struct Config {
pub query: String,
pub file_path: String,
}
impl Config {
pub fn build(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let file_path = args[2].clone();
Ok(Config { query, file_path })
}
}
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.file_path)?;
for line in search(&config.query, &contents) {
println!("{line}");
}
Ok(())
}
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}
results
}
pub fn search_case_insensitive<'a>(
query: &str,
contents: &'a str,
) -> Vec<&'a str> {
let query = query.to_lowercase();
let mut results = Vec::new();
for line in contents.lines() {
if line.to_lowercase().contains(&query) {
results.push(line);
}
}
results
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn case_sensitive() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";
assert_eq!(vec!["safe, fast, productive."], search(query, contents));
}
#[test]
fn case_insensitive() {
let query = "rUsT";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";
assert_eq!(
vec!["Rust:", "Trust me."],
search_case_insensitive(query, contents)
);
}
}
search_case_insensitive
函数将查询和行小写,然后再比较它们首先,我们将query
string 并将其存储在一个带有
相同的名称。叫to_lowercase
上查询是必需的,因此没有
无论用户的查询是否为"rust"
,"RUST"
,"Rust"
或"rUsT"
,
我们将查询视为"rust"
并且对案件不敏感。
而to_lowercase
将处理基本的 Unicode,它不会是 100% 准确的。如果
我们正在编写一个真实的应用程序,我们想在这里做更多的工作,但是
本节是关于环境变量的,而不是 Unicode,所以我们把它留在
那个在这里。
请注意,query
现在是String
而不是字符串 slice,因为调用to_lowercase
创建新数据,而不是引用现有数据。说出
query 为"rUsT"
,例如:该字符串 slice 不包含小写u
或t
供我们使用,因此我们必须分配一个新的String
含"rust"
.当我们通过query
作为contains
方法,我们
需要添加 & 符号,因为contains
定义为采用
字符串切片。
接下来,我们添加对to_lowercase
在每个line
全部小写
字符。现在我们已经转换了line
和query
改为小写,我们将
无论查询的情况如何,都可以查找匹配项。
让我们看看这个实现是否通过了测试:
$ cargo test
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished `test` profile [unoptimized + debuginfo] target(s) in 1.33s
Running unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)
running 2 tests
test tests::case_insensitive ... ok
test tests::case_sensitive ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests minigrep
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
伟大!他们通过了。现在,让我们调用新的search_case_insensitive
功能
从run
功能。首先,我们将向Config
struct 在区分大小写和不区分大小写的搜索之间切换。添加
此字段将导致编译器错误,因为我们没有初始化此字段
ANYWHERE 尚未:
文件名: src/lib.rs
use std::error::Error;
use std::fs;
pub struct Config {
pub query: String,
pub file_path: String,
pub ignore_case: bool,
}
impl Config {
pub fn build(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let file_path = args[2].clone();
Ok(Config { query, file_path })
}
}
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.file_path)?;
let results = if config.ignore_case {
search_case_insensitive(&config.query, &contents)
} else {
search(&config.query, &contents)
};
for line in results {
println!("{line}");
}
Ok(())
}
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}
results
}
pub fn search_case_insensitive<'a>(
query: &str,
contents: &'a str,
) -> Vec<&'a str> {
let query = query.to_lowercase();
let mut results = Vec::new();
for line in contents.lines() {
if line.to_lowercase().contains(&query) {
results.push(line);
}
}
results
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn case_sensitive() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";
assert_eq!(vec!["safe, fast, productive."], search(query, contents));
}
#[test]
fn case_insensitive() {
let query = "rUsT";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";
assert_eq!(
vec!["Rust:", "Trust me."],
search_case_insensitive(query, contents)
);
}
}
我们添加了ignore_case
字段。接下来,我们需要run
函数检查ignore_case
field 的值,并使用它来决定
是否调用search
函数或search_case_insensitive
函数,如示例 12-22 所示。这仍然不会编译。
use std::error::Error;
use std::fs;
pub struct Config {
pub query: String,
pub file_path: String,
pub ignore_case: bool,
}
impl Config {
pub fn build(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let file_path = args[2].clone();
Ok(Config { query, file_path })
}
}
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.file_path)?;
let results = if config.ignore_case {
search_case_insensitive(&config.query, &contents)
} else {
search(&config.query, &contents)
};
for line in results {
println!("{line}");
}
Ok(())
}
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}
results
}
pub fn search_case_insensitive<'a>(
query: &str,
contents: &'a str,
) -> Vec<&'a str> {
let query = query.to_lowercase();
let mut results = Vec::new();
for line in contents.lines() {
if line.to_lowercase().contains(&query) {
results.push(line);
}
}
results
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn case_sensitive() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";
assert_eq!(vec!["safe, fast, productive."], search(query, contents));
}
#[test]
fn case_insensitive() {
let query = "rUsT";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";
assert_eq!(
vec!["Rust:", "Trust me."],
search_case_insensitive(query, contents)
);
}
}
search
或search_case_insensitive
基于config.ignore_case
最后,我们需要检查环境变量。的函数
使用环境变量位于env
标准
库,因此我们将该模块放入 src/lib.rs 顶部的范围中。然后
我们将使用var
函数env
module 来检查是否有任何值
已为名为IGNORE_CASE
,如
示例 12-23.
use std::env;
// --snip--
use std::error::Error;
use std::fs;
pub struct Config {
pub query: String,
pub file_path: String,
pub ignore_case: bool,
}
impl Config {
pub fn build(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let file_path = args[2].clone();
let ignore_case = env::var("IGNORE_CASE").is_ok();
Ok(Config {
query,
file_path,
ignore_case,
})
}
}
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.file_path)?;
let results = if config.ignore_case {
search_case_insensitive(&config.query, &contents)
} else {
search(&config.query, &contents)
};
for line in results {
println!("{line}");
}
Ok(())
}
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}
results
}
pub fn search_case_insensitive<'a>(
query: &str,
contents: &'a str,
) -> Vec<&'a str> {
let query = query.to_lowercase();
let mut results = Vec::new();
for line in contents.lines() {
if line.to_lowercase().contains(&query) {
results.push(line);
}
}
results
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn case_sensitive() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";
assert_eq!(vec!["safe, fast, productive."], search(query, contents));
}
#[test]
fn case_insensitive() {
let query = "rUsT";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";
assert_eq!(
vec!["Rust:", "Trust me."],
search_case_insensitive(query, contents)
);
}
}
IGNORE_CASE
在这里,我们创建一个新变量ignore_case
.为了设置它的值,我们调用env::var
函数,并向其传递IGNORE_CASE
环境
变量。这env::var
函数返回一个Result
那将是
成功的Ok
variant 中,如果
环境变量设置为任何值。它将返回Err
变体
如果未设置环境变量。
我们使用的是is_ok
方法上的Result
检查环境
变量,这意味着程序应该执行不区分大小写的搜索。
如果IGNORE_CASE
环境变量未设置为任何内容,则is_ok
将
返回false
程序将执行区分大小写的搜索。我们没有
关心环境变量的值,只是它是 set 还是
unset 的 URL 中,因此我们要检查is_ok
而不是使用unwrap
,expect
或任何
我们在Result
.
我们将ignore_case
变量添加到Config
实例,因此run
function 可以读取该值并决定是否调用search_case_insensitive
或search
,正如我们在示例 12-22 中实现的那样。
让我们试一试吧!首先,我们将在没有环境的情况下运行我们的程序
变量集,并使用查询to
,它应该匹配包含
单词 to 全部小写:
$ cargo run -- to poem.txt
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
Running `target/debug/minigrep to poem.txt`
Are you nobody, too?
How dreary to be somebody!
看起来这仍然有效!现在让我们使用IGNORE_CASE
设置
自1
但使用相同的查询来:
$ IGNORE_CASE=1 cargo run -- to poem.txt
如果您使用的是 PowerShell,则需要设置环境变量和 将程序作为单独的命令运行:
PS> $Env:IGNORE_CASE=1; cargo run -- to poem.txt
这将使IGNORE_CASE
persist for your shell session 的剩余时间。
可以使用Remove-Item
cmdlet 中:
PS> Remove-Item Env:IGNORE_CASE
我们应该获取包含 to 的行,其中可能包含大写字母:
Are you nobody, too?
How dreary to be somebody!
To tell your name the livelong day
To an admiring bog!
太好了,我们还收到了包含 To!我们minigrep
程序现在可以执行
由环境变量控制的不区分大小写的搜索。现在您知道了
如何使用命令行参数或环境管理选项集
变量。
一些程序允许使用相同的参数和环境变量 配置。在这些情况下,程序决定一个或另一个需要 优先。对于您自己的另一个练习,请尝试控制区分大小写 通过命令行参数或环境变量。决定 命令行参数还是环境变量应采用 优先级 (如果程序运行时 1 设置为 区分大小写),1 设置为 ignore 大小写。
这std::env
模块包含更多有用的功能来处理
环境变量:查看其文档以了解可用的内容。
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准