ch12-05-working-with-environment-variables.md
commit 9c0fa2714859738ff73cbbb829592e4c037d7e46
我們將增加一個(gè)額外的功能來改進(jìn) minigrep
:用戶可以通過設(shè)置環(huán)境變量來設(shè)置搜索是否是大小寫敏感的 。當(dāng)然,我們也可以將其設(shè)計(jì)為一個(gè)命令行參數(shù)并要求用戶每次需要時(shí)都加上它,不過在這里我們將使用環(huán)境變量。這允許用戶設(shè)置環(huán)境變量一次之后在整個(gè)終端會(huì)話中所有的搜索都將是大小寫不敏感的。
我們希望增加一個(gè)新函數(shù) search_case_insensitive
,并將會(huì)在設(shè)置了環(huán)境變量時(shí)調(diào)用它。這里將繼續(xù)遵循 TDD 過程,其第一步是再次編寫一個(gè)失敗測(cè)試。我們將為新的大小寫不敏感搜索函數(shù)新增一個(gè)測(cè)試函數(shù),并將老的測(cè)試函數(shù)從 one_result
改名為 case_sensitive
來更清楚的表明這兩個(gè)測(cè)試的區(qū)別,如示例 12-20 所示:
文件名: src/lib.rs
#[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)
);
}
}
示例 12-20:為準(zhǔn)備添加的大小寫不敏感函數(shù)新增失敗測(cè)試
注意我們也改變了老測(cè)試中 contents
的值。還新增了一個(gè)含有文本 "Duct tape."
的行,它有一個(gè)大寫的 D,這在大小寫敏感搜索時(shí)不應(yīng)該匹配 "duct"。我們修改這個(gè)測(cè)試以確保不會(huì)意外破壞已經(jīng)實(shí)現(xiàn)的大小寫敏感搜索功能;這個(gè)測(cè)試現(xiàn)在應(yīng)該能通過并在處理大小寫不敏感搜索時(shí)應(yīng)該能一直通過。
大小寫 不敏感 搜索的新測(cè)試使用 "rUsT"
作為其查詢字符串。在我們將要增加的 search_case_insensitive
函數(shù)中,"rUsT"
查詢應(yīng)該包含帶有一個(gè)大寫 R 的 "Rust:"
還有 "Trust me."
這兩行,即便他們與查詢的大小寫都不同。這個(gè)測(cè)試現(xiàn)在不能編譯,因?yàn)檫€沒有定義 search_case_insensitive
函數(shù)。請(qǐng)隨意增加一個(gè)總是返回空 vector 的骨架實(shí)現(xiàn),正如示例 12-16 中 search
函數(shù)為了使測(cè)試通過編譯并失敗時(shí)所做的那樣。
search_case_insensitive
函數(shù),如示例 12-21 所示,將與 search
函數(shù)基本相同。唯一的區(qū)別是它會(huì)將 query
變量和每一 line
都變?yōu)樾?,這樣不管輸入?yún)?shù)是大寫還是小寫,在檢查該行是否包含查詢字符串時(shí)都會(huì)是小寫。
文件名: src/lib.rs
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
}
示例 12-21:定義 search_case_insensitive
函數(shù),它在比較查詢和每一行之前將他們都轉(zhuǎn)換為小寫
首先我們將 query
字符串轉(zhuǎn)換為小寫,并將其覆蓋到同名的變量中。對(duì)查詢字符串調(diào)用 to_lowercase
是必需的,這樣不管用戶的查詢是 "rust"
、"RUST"
、"Rust"
或者 "rUsT"
,我們都將其當(dāng)作 "rust"
處理并對(duì)大小寫不敏感。雖然 to_lowercase
可以處理基本的 Unicode,但它不是 100% 準(zhǔn)確。如果編寫真實(shí)的程序的話,我們還需多做一些工作,不過這一部分是關(guān)于環(huán)境變量而不是 Unicode 的,所以這樣就夠了。
注意 query
現(xiàn)在是一個(gè) String
而不是字符串 slice,因?yàn)檎{(diào)用 to_lowercase
是在創(chuàng)建新數(shù)據(jù),而不是引用現(xiàn)有數(shù)據(jù)。如果查詢字符串是 "rUsT"
,這個(gè)字符串 slice 并不包含可供我們使用的小寫的 u
或 t
,所以必需分配一個(gè)包含 "rust"
的新 String
。現(xiàn)在當(dāng)我們將 query
作為一個(gè)參數(shù)傳遞給 contains
方法時(shí),需要增加一個(gè) & 因?yàn)?nbsp;contains
的簽名被定義為獲取一個(gè)字符串 slice。
接下來在檢查每個(gè) line
是否包含 search
之前增加了一個(gè) to_lowercase
調(diào)用將他們都變?yōu)樾憽,F(xiàn)在我們將 line
和 query
都轉(zhuǎn)換成了小寫,這樣就可以不管查詢的大小寫進(jìn)行匹配了。
讓我們看看這個(gè)實(shí)現(xiàn)能否通過測(cè)試:
$ cargo test
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished test [unoptimized + debuginfo] target(s) in 1.33s
Running unittests (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 (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
好的!現(xiàn)在,讓我們?cè)?nbsp;run
函數(shù)中實(shí)際調(diào)用新 search_case_insensitive
函數(shù)。首先,我們將在 Config
結(jié)構(gòu)體中增加一個(gè)配置項(xiàng)來切換大小寫敏感和大小寫不敏感搜索。增加這些字段會(huì)導(dǎo)致編譯錯(cuò)誤,因?yàn)槲覀冞€沒有在任何地方初始化這些字段:
文件名: src/lib.rs
pub struct Config {
pub query: String,
pub filename: String,
pub case_sensitive: bool,
}
這里增加了 case_sensitive
字符來存放一個(gè)布爾值。接著我們需要 run
函數(shù)檢查 case_sensitive
字段的值并使用它來決定是否調(diào)用 search
函數(shù)或 search_case_insensitive
函數(shù),如示例 12-22 所示。注意這還不能編譯:
文件名: src/lib.rs
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.filename)?;
let results = if config.case_sensitive {
search(&config.query, &contents)
} else {
search_case_insensitive(&config.query, &contents)
};
for line in results {
println!("{}", line);
}
Ok(())
}
示例 12-22:根據(jù) config.case_sensitive
的值調(diào)用 search
或 search_case_insensitive
最后需要實(shí)際檢查環(huán)境變量。處理環(huán)境變量的函數(shù)位于標(biāo)準(zhǔn)庫的 env
模塊中,所以我們需要在 src/lib.rs 的開頭增加一個(gè) use std::env;
行將這個(gè)模塊引入作用域中。接著在 Config::new
中使用 env
模塊的 var
方法來檢查一個(gè)叫做 CASE_INSENSITIVE
的環(huán)境變量,如示例 12-23 所示:
文件名: src/lib.rs
use std::env;
// --snip--
impl Config {
pub fn new(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let filename = args[2].clone();
let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
Ok(Config {
query,
filename,
case_sensitive,
})
}
}
示例 12-23:檢查叫做 CASE_INSENSITIVE
的環(huán)境變量
這里創(chuàng)建了一個(gè)新變量 case_sensitive
。為了設(shè)置它的值,需要調(diào)用 env::var
函數(shù)并傳遞我們需要尋找的環(huán)境變量名稱,CASE_INSENSITIVE
。env::var
返回一個(gè) Result
,它在環(huán)境變量被設(shè)置時(shí)返回包含其值的 Ok
成員,并在環(huán)境變量未被設(shè)置時(shí)返回 Err
成員。
我們使用 Result
的 is_err
方法來檢查其是否是一個(gè) error(也就是環(huán)境變量未被設(shè)置的情況),這也就意味著我們 需要 進(jìn)行一個(gè)大小寫敏感搜索。如果CASE_INSENSITIVE
環(huán)境變量被設(shè)置為任何值,is_err
會(huì)返回 false 并將進(jìn)行大小寫不敏感搜索。我們并不關(guān)心環(huán)境變量所設(shè)置的 值,只關(guān)心它是否被設(shè)置了,所以檢查 is_err
而不是 unwrap
、expect
或任何我們已經(jīng)見過的 Result
的方法。
我們將變量 case_sensitive
的值傳遞給 Config
實(shí)例,這樣 run
函數(shù)可以讀取其值并決定是否調(diào)用 search
或者示例 12-22 中實(shí)現(xiàn)的 search_case_insensitive
。
讓我們?cè)囈辉嚢?!首先不設(shè)置環(huán)境變量并使用查詢 to
運(yùn)行程序,這應(yīng)該會(huì)匹配任何全小寫的單詞 “to” 的行:
$ cargo run to poem.txt
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 0.0s
Running `target/debug/minigrep to poem.txt`
Are you nobody, too?
How dreary to be somebody!
看起來程序仍然能夠工作!現(xiàn)在將 CASE_INSENSITIVE
設(shè)置為 1
并仍使用相同的查詢 to
。
如果你使用 PowerShell,則需要用兩個(gè)命令來分別設(shè)置環(huán)境變量并運(yùn)行程序:
PS> $Env:CASE_INSENSITIVE=1; cargo run to poem.txt
這回應(yīng)該得到包含可能有大寫字母的 “to” 的行:
$ (CASE_INSENSITIVE=1; cargo run to poem.txt)
Finished dev [unoptimized + debuginfo] target(s) in 0.0s
Running `target/debug/minigrep to poem.txt`
Are you nobody, too?
How dreary to be somebody!
To tell your name the livelong day
To an admiring bog!
好極了,我們也得到了包含 “To” 的行!現(xiàn)在 minigrep
程序可以通過環(huán)境變量控制進(jìn)行大小寫不敏感搜索了。現(xiàn)在你知道了如何管理由命令行參數(shù)或環(huán)境變量設(shè)置的選項(xiàng)了!
一些程序允許對(duì)相同配置同時(shí)使用參數(shù) 和 環(huán)境變量。在這種情況下,程序來決定參數(shù)和環(huán)境變量的優(yōu)先級(jí)。作為一個(gè)留給你的測(cè)試,嘗試通過一個(gè)命令行參數(shù)或一個(gè)環(huán)境變量來控制大小寫不敏感搜索。并在運(yùn)行程序時(shí)遇到矛盾值時(shí)決定命令行參數(shù)和環(huán)境變量的優(yōu)先級(jí)。
std::env
模塊還包含了更多處理環(huán)境變量的實(shí)用功能;請(qǐng)查看官方文檔來了解其可用的功能。
更多建議: