使用环境变量

我们将通过添加一个额外的功能来改进:一个 不区分大小写的搜索,用户可以通过环境开启 变量。我们可以将此功能设为命令行选项,并要求 用户每次希望应用它时都输入它,但将其设为 环境变量,我们允许用户设置一次环境变量 并在该终端会话中让他们的所有搜索不区分大小写。minigrep

为不区分大小写的搜索函数编写失败的测试

我们首先添加一个新函数,该函数将在 环境变量具有值。我们将继续遵循 TDD 流程, 所以第一步是再次编写一个失败的测试。我们将为 new 函数并将我们的旧测试从 重命名为 to 以阐明两者之间的区别 测试,如示例 12-20 所示。search_case_insensitivesearch_case_insensitiveone_resultcase_sensitive

文件名: src/lib.rs
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)
        );
    }
}
示例 12-20:为我们将要添加的不区分大小写的函数添加新的失败测试

请注意,我们也编辑了旧测试。我们添加了一条新线路 文本使用大写 D,当我们以区分大小写的方式搜索时,该大写字母 D 不应与查询匹配。更改旧测试 这样有助于确保我们不会意外地断开区分大小写的 我们已经实现的搜索功能。此测试现在应该通过 并且在我们进行不区分大小写的搜索时应该继续传递。contents"Duct tape.""duct"

不区分大小写的搜索的新测试用作其查询。在 我们要添加的函数,查询应该匹配包含大写 R 的行,并匹配 行,即使两者的大小写与查询不同。这 是我们失败的测试,并且它不会编译,因为我们还没有定义 函数。随意添加骨架 实现总是返回一个空 vector,类似于我们所做的 对于示例 12-16 中的函数,可以看到测试 compile 和 fail。"rUsT"search_case_insensitive"rUsT""Rust:""Trust me."search_case_insensitivesearch

实现 search_case_insensitive 函数

示例 12-21 所示的函数将几乎是 与函数相同。唯一的区别是我们将小写 和 each 的 API 中,以便无论输入参数的情况如何, 当我们检查该行是否包含查询时,它们的情况相同。search_case_insensitivesearchqueryline

文件名: src/lib.rs
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)
        );
    }
}
示例 12-21:定义函数以在比较 query 和 line 之前将其小写search_case_insensitive

首先,我们将字符串小写并将其存储在阴影变量中,使用 相同的名称。调用查询是必要的,因此没有 无论用户的查询是 、 、 还是 、 我们将查询视为 true,并且对大小写不敏感。 虽然可以处理基本的 Unicode,但它不会 100% 准确。如果 我们正在编写一个真实的应用程序,我们想在这里做更多的工作,但是 本节是关于环境变量的,而不是 Unicode,所以我们把它留在 那个在这里。queryto_lowercase"rust""RUST""Rust""rUsT""rust"to_lowercase

请注意,它现在是 a 而不是 string slice,因为调用 create new data 而不是引用现有数据。说出 query 是 ,例如:该字符串 slice 不包含小写 or 供我们使用,因此我们必须分配一个新的包含 .现在,当我们作为参数传递给该方法时,我们会 需要添加 & 符号,因为 的签名被定义为采用 字符串切片。queryStringto_lowercase"rUsT"utString"rust"querycontainscontains

接下来,我们添加对 each 的调用,将 all 小写 字符。现在我们已经转换并转换为小写,我们将 无论查询的情况如何,都可以查找匹配项。to_lowercaselinelinequery

让我们看看这个实现是否通过了测试:

$ 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

伟大!他们通过了。现在,让我们调用新函数 从函数。首先,我们将向结构体添加一个配置选项,以便在区分大小写和不区分大小写的搜索之间切换。添加 此字段将导致编译器错误,因为我们没有初始化此字段 ANYWHERE 尚未:search_case_insensitiverunConfig

文件名: 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)
        );
    }
}

我们添加了包含布尔值的字段。接下来,我们需要该函数来检查字段的值并使用它来决定 是调用 function 还是 function,如示例 12-22 所示。这仍然不会编译。ignore_caserunignore_casesearchsearch_case_insensitive

文件名: 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)
        );
    }
}
示例 12-22:根据searchsearch_case_insensitiveconfig.ignore_case

最后,我们需要检查环境变量。的函数 使用环境变量位于标准的 库,因此我们将该模块引入 src/lib.rs 顶部的范围。然后 我们将使用模块中的函数来检查是否有任何值 已为名为 的环境变量设置,如 示例 12-23.envvarenvIGNORE_CASE

文件名: src/lib.rs
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)
        );
    }
}
示例 12-23:检查名为IGNORE_CASE

在这里,我们创建一个新变量 。要设置其值,我们调用该函数并向其传递环境名称 变量。该函数返回一个 包含环境变量的值的 successful 变体 if 环境变量设置为任何值。它将返回 variant 如果未设置环境变量。ignore_caseenv::varIGNORE_CASEenv::varResultOkErr

我们在 上使用 上 的方法 来检查环境 变量,这意味着程序应该执行不区分大小写的搜索。 如果环境变量未设置为任何内容,则 will return 返回,程序将执行区分大小写的搜索。我们没有 关心环境变量的值,只是它是 set 还是 unset 的 URL 中,因此我们检查而不是使用 、 、 或任何 我们在 上看到的其他方法。is_okResultIGNORE_CASEis_okfalseis_okunwrapexpectResult

我们将变量中的值传递给实例,以便函数可以读取该值并决定是调用 还是 ,如示例 12-22 中实现的那样。ignore_caseConfigrunsearch_case_insensitivesearch

让我们试一试吧!首先,我们将在没有环境的情况下运行我们的程序 变量集,并使用 query ,它应该匹配包含 单词 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!

看起来这仍然有效!现在让我们使用 set 运行程序 to 但使用相同的查询 toIGNORE_CASE1

$ IGNORE_CASE=1 cargo run -- to poem.txt

如果您使用的是 PowerShell,则需要设置环境变量和 将程序作为单独的命令运行:

PS> $Env:IGNORE_CASE=1; cargo run -- to poem.txt

这将使 shell 会话的其余部分持续存在。 可以使用 cmdlet 取消设置它:IGNORE_CASERemove-Item

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/)为准