处理环境变量

ch12-05-working-with-environment-variables.md
commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56

让我们再增加一个功能:大小写不敏感搜索。另外,这个设定将不是一个命令行参数:相反它将是一个环境变量。当然可以选择创建一个大小写不敏感的命令行参数,不过用户要求提供一个环境变量这样设置一次之后在整个终端会话中所有的搜索都将是大小写不敏感的了。

实现并测试一个大小写不敏感grep函数

首先,让我们增加一个新函数,当设置了环境变量时会调用它。增加一个新测试并重命名已经存在的那个:

#[cfg(test)]
mod test {
    use {grep, grep_case_insensitive};

    #[test]
    fn case_sensitive() {
        let search = "duct";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";

        assert_eq!(
            vec!["safe, fast, productive."],
            grep(search, contents)
        );
    }

    #[test]
    fn case_insensitive() {
        let search = "rust";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";

        assert_eq!(
            vec!["Rust:", "Trust me."],
            grep_case_insensitive(search, contents)
        );
    }
}

我们将定义一个叫做grep_case_insensitive的新函数。它的实现与grep函数大体上相似,不过列表 12-16 展示了一些小的区别:

Filename: src/lib.rs

fn grep_case_insensitive<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
    let search = search.to_lowercase();
    let mut results = Vec::new();

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

    results
}

Listing 12-16: Implementing a grep_case_insensitive function by changing the search string and the lines of the contents to lowercase before comparing them

首先,将search字符串转换为小写,并存放于一个同名的覆盖变量中。注意现在search是一个String而不是字符串 slice,所以在将search传递给contains时需要加上 &,因为contains获取一个字符串 slice。

接着在检查每个line是否包含search之前增加了一个to_lowercase调用。因为将linesearch都转换为小写,我们就可以无视大小写的匹配文件和命令行参数了。看看测试是否通过了:

    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
     Running target\debug\deps\greprs-e58e9b12d35dc861.exe

running 2 tests
test test::case_insensitive ... ok
test test::case_sensitive ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured

     Running target\debug\greprs-8a7faa2662b5030a.exe

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

   Doc-tests greprs

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

好的!现在,我们必须真正的使用新的grep_case_insensitive函数。首先,在Config结构体中增加一个配置项:

Filename: src/lib.rs

pub struct Config {
    pub search: String,
    pub filename: String,
    pub case_sensitive: bool,
}

接着在run函数中检查这个选项,并根据case_sensitive函数的值来决定调用哪个函数:

Filename: src/lib.rs

pub fn run(config: Config) -> Result<(), Box<Error>>{
    let mut f = File::open(config.filename)?;

    let mut contents = String::new();
    f.read_to_string(&mut contents)?;

    let results = if config.case_sensitive {
        grep(&config.search, &contents)
    } else {
        grep_case_insensitive(&config.search, &contents)
    };

    for line in results {
        println!("{}", line);
    }

    Ok(())
}

最后需要真正的检查环境变量。为了将标准库中的env模块引入作用域,在 src/lib.rs 开头增加一个use行:

Filename: src/lib.rs

use std::env;

并接着在Config::new中使用env模块的vars方法:

Filename: src/lib.rs

# use std::env;
#
# struct Config {
#     search: String,
#     filename: String,
#     case_sensitive: bool,
# }
#
impl Config {
    pub fn new(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("not enough arguments");
        }

        let search = args[1].clone();
        let filename = args[2].clone();

        let mut case_sensitive = true;

        for (name, _) in env::vars() {
            if name == "CASE_INSENSITIVE" {
                case_sensitive = false;
            }
        }

        Ok(Config {
            search: search,
            filename: filename,
            case_sensitive: case_sensitive,
        })
    }
}

这里我们调用了env::vars,它与env::args的工作方式类似。区别是env::vars返回一个环境变量而不是命令行参数的迭代器。不同于使用collect来创建一个所有环境变量的 vector,我们使用for循环。env::vars返回一系列元组:环境变量的名称和其值。我们从来也不关心它的值,只关心它是否被设置了,所以可以使用_占位符来取代变量名来让 Rust 知道它不应该警告一个未使用的变量。最后,有一个默认为真的变量case_sensitive。如果我们找到了一个CASE_INSENSITIVE环境变量,就将case_sensitive设置为假。接着将其作为Config的一部分返回。

尝试运行几次吧!

$ cargo run to poem.txt
    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target\debug\greprs.exe to poem.txt`
Are you nobody, too?
How dreary to be somebody!
$ CASE_INSENSITIVE=1 cargo run to poem.txt
    Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target\debug\greprs.exe to poem.txt`
Are you nobody, too?
How dreary to be somebody!
To tell your name the livelong day
To an admiring bog!

好极了!greprs现在可以通过环境变量的控制来进行大小写不敏感搜索了。现在你已经知道如何处理命令行参数或环境变量了!

一些程序允许对相同配置同时使用参数_和_环境变量。在这种情况下,程序来决定参数和环境变量的优先级。作为一个留给你的测试,尝试同时通过一个命令行参数来控制大小写不敏感搜索,并在程序遇到矛盾值时决定其优先级。

std::env模块还包含了更多处理环境变量的实用功能;请查看官方文档来了解其可用的功能。