使用环境变量

RustRustBeginner
立即练习

This tutorial is from open-source community. Access the source code

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

欢迎来到「使用环境变量」实验。本实验是 Rust 程序设计语言 的一部分。你可以在 LabEx 中练习 Rust 技能。

在本实验中,我们将增强 minigrep,允许用户通过环境变量启用不区分大小写的搜索,从而提供一种便捷的方式,以便在终端会话中的所有搜索中应用此功能。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL rust(("Rust")) -.-> rust/BasicConceptsGroup(["Basic Concepts"]) rust(("Rust")) -.-> rust/DataTypesGroup(["Data Types"]) rust(("Rust")) -.-> rust/ControlStructuresGroup(["Control Structures"]) rust(("Rust")) -.-> rust/FunctionsandClosuresGroup(["Functions and Closures"]) rust(("Rust")) -.-> rust/DataStructuresandEnumsGroup(["Data Structures and Enums"]) rust(("Rust")) -.-> rust/AdvancedTopicsGroup(["Advanced Topics"]) rust/BasicConceptsGroup -.-> rust/variable_declarations("Variable Declarations") rust/BasicConceptsGroup -.-> rust/mutable_variables("Mutable Variables") rust/DataTypesGroup -.-> rust/string_type("String Type") rust/ControlStructuresGroup -.-> rust/for_loop("for Loop") rust/FunctionsandClosuresGroup -.-> rust/function_syntax("Function Syntax") rust/FunctionsandClosuresGroup -.-> rust/expressions_statements("Expressions and Statements") rust/DataStructuresandEnumsGroup -.-> rust/method_syntax("Method Syntax") rust/AdvancedTopicsGroup -.-> rust/traits("Traits") rust/AdvancedTopicsGroup -.-> rust/operator_overloading("Traits for Operator Overloading") subgraph Lab Skills rust/variable_declarations -.-> lab-100422{{"使用环境变量"}} rust/mutable_variables -.-> lab-100422{{"使用环境变量"}} rust/string_type -.-> lab-100422{{"使用环境变量"}} rust/for_loop -.-> lab-100422{{"使用环境变量"}} rust/function_syntax -.-> lab-100422{{"使用环境变量"}} rust/expressions_statements -.-> lab-100422{{"使用环境变量"}} rust/method_syntax -.-> lab-100422{{"使用环境变量"}} rust/traits -.-> lab-100422{{"使用环境变量"}} rust/operator_overloading -.-> lab-100422{{"使用环境变量"}} end

使用环境变量

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

为不区分大小写的搜索功能编写失败的测试

我们首先添加一个新的 search_case_insensitive 函数,当环境变量有值时会调用该函数。我们将继续遵循测试驱动开发(TDD)流程,所以第一步还是编写一个会失败的测试。我们将为新的 search_case_insensitive 函数添加一个新测试,并将旧测试从 one_result 重命名为 case_sensitive,以明确这两个测试之间的差异,如清单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:为我们即将添加的不区分大小写的函数添加一个新的失败测试

注意,我们也编辑了旧测试的 contents。我们添加了一行文本 "Duct tape.",其中字母 D 是大写的,当我们以区分大小写的方式进行搜索时,它不应与查询 "duct" 匹配。以这种方式更改旧测试有助于确保我们不会意外破坏已经实现的区分大小写的搜索功能。这个测试现在应该会通过,并且在我们处理不区分大小写的搜索时应该会继续通过。

针对不区分大小写搜索的新测试使用 "rUsT" 作为查询。在我们即将添加的 search_case_insensitive 函数中,查询 "rUsT" 应该与包含大写字母 R"Rust:" 行匹配,并且应该与 "Trust me." 行匹配,即使这两行的大小写与查询不同。这就是我们的失败测试,它将无法编译,因为我们尚未定义 search_case_insensitive 函数。你可以随意添加一个总是返回空向量的框架实现,类似于我们在清单12 - 16中为 search 函数所做的那样,看看测试编译并失败的情况。

实现search_case_insensitive函数

清单12 - 21中展示的search_case_insensitive函数与search函数几乎相同。唯一的区别在于,我们会将query和每一行line都转换为小写形式,这样无论输入参数的大小写如何,在检查该行是否包含查询内容时,它们的大小写都是一致的。

文件名:src/lib.rs

pub fn search_case_insensitive<'a>(
    query: &str,
    contents: &'a str,
) -> Vec<&'a str> {
  1 let query = query.to_lowercase();
    let mut results = Vec::new();

    for line in contents.lines() {
        if 2 line.to_lowercase().contains(3 &query) {
            results.push(line);
        }
    }

    results
}

清单12 - 21:定义search_case_insensitive函数,在比较之前将查询和行转换为小写

首先,我们将query字符串转换为小写形式,并将其存储在一个同名的遮蔽变量中[1]。对查询调用to_lowercase是必要的,这样无论用户的查询是"rust""RUST""Rust"还是"rUsT",我们都将把查询视为"rust",而不区分大小写。虽然to_lowercase可以处理基本的Unicode,但它并不完全准确。如果我们编写一个实际应用程序,在这里需要做更多工作,但本节是关于环境变量的,而不是Unicode,所以我们就先这样处理。

注意,query现在是一个String而不是字符串切片,因为调用to_lowercase会创建新的数据,而不是引用现有数据。例如,假设查询是"rUsT":那个字符串切片中不包含小写的ut供我们使用,所以我们必须分配一个新的包含"rust"String。当我们现在将query作为参数传递给contains方法时,我们需要添加一个&符号[3],因为contains的签名定义为接受一个字符串切片。

接下来,我们对每一行line调用to_lowercase,将所有字符转换为小写[2]。现在我们已经将linequery都转换为小写,无论查询的大小写如何,我们都能找到匹配项。

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

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

太棒了!测试通过了。现在,让我们从run函数中调用新的search_case_insensitive函数。首先,我们将在Config结构体中添加一个配置选项,用于在区分大小写和不区分大小写的搜索之间进行切换。添加这个字段会导致编译器错误,因为我们还没有在任何地方初始化这个字段:

文件名:src/lib.rs

pub struct Config {
    pub query: String,
    pub file_path: String,
    pub ignore_case: bool,
}

我们添加了一个存储布尔值的ignore_case字段。接下来,我们需要run函数检查ignore_case字段的值,并据此决定调用search函数还是search_case_insensitive函数,如清单12 - 22所示。不过,这仍然无法编译。

文件名:src/lib.rs

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(())
}

清单12 - 22:根据config.ignore_case中的值调用searchsearch_case_insensitive

最后,我们需要检查环境变量。处理环境变量的函数在标准库的env模块中,所以我们在src/lib.rs的顶部将该模块引入作用域。然后,我们将使用env模块中的var函数来检查是否为名为IGNORE_CASE的环境变量设置了任何值,如清单12 - 23所示。

文件名:src/lib.rs

use std::env;
--snip--

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,
        })
    }
}

清单12 - 23:检查名为IGNORE_CASE的环境变量中是否有任何值

在这里,我们创建了一个新变量ignore_case。为了设置它的值,我们调用env::var函数,并将IGNORE_CASE环境变量的名称传递给它。env::var函数返回一个Result,如果环境变量设置了任何值,它将是包含环境变量值的成功的Ok变体。如果环境变量未设置,它将返回Err变体。

我们使用Result上的is_ok方法来检查环境变量是否已设置,这意味着程序应该进行不区分大小写的搜索。如果IGNORE_CASE环境变量未设置任何值,is_ok将返回false,程序将进行区分大小写的搜索。我们不关心环境变量的值,只关心它是否已设置,所以我们检查is_ok,而不是使用unwrapexpect或我们在Result上看到的任何其他方法。

我们将ignore_case变量中的值传递给Config实例,这样run函数就可以读取该值,并决定是调用search_case_insensitive还是search,就像我们在清单12 - 22中实现的那样。

让我们试试看!首先,我们在未设置环境变量的情况下运行程序,并使用查询to,它应该匹配任何包含全小写单词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!

看起来仍然有效!现在,我们将IGNORE_CASE设置为1并使用相同的查询to来运行程序:

IGNORE_CASE=1 cargo run -- to poem.txt

如果你使用的是PowerShell,你需要分别设置环境变量并运行程序:

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

这将使IGNORE_CASE在你的 shell 会话的剩余时间内持续存在。可以使用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程序现在可以通过环境变量控制进行不区分大小写的搜索了。现在你知道如何管理通过命令行参数或环境变量设置的选项了。

有些程序允许对相同的配置同时使用参数和环境变量。在这种情况下,程序会决定其中一个优先。作为另一个练习,你可以尝试通过命令行参数或环境变量来控制大小写敏感性。如果程序在一个设置为区分大小写而另一个设置为不区分大小写的情况下运行,决定命令行参数或环境变量应该优先。

std::env模块包含许多处理环境变量的更有用的功能:查看其文档以了解可用的功能。

总结

恭喜你!你已经完成了「使用环境变量」实验。你可以在 LabEx 中练习更多实验来提升你的技能。