Rust 處理環(huán)境變量

2023-03-22 15:12 更新
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è)大小寫不敏感 search 函數(shù)的失敗測(cè)試

我們希望增加一個(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í)所做的那樣。

實(shí)現(xiàn) search_case_insensitive 函數(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)查看官方文檔來了解其可用的功能。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)