【Rust自学】12.7. 使用环境变量

【Rust自学】12.7. 使用环境变量
12.7 使用环境变量12.7.0. 写在正文之前第12章要做一个实例的项目——一个命令行程序。这个程序是一个grep(Global Regular Expression Print)是一个全局正则搜索和输出的工具。它的功能是在指定的文件中搜索出指定的文字。这个项目分为这么几步- 接收命令行参数- 读取文件- 重构改进模块和错误处理- 使用TDD测试驱动开发开发库功能-使用环境变量本文- 将错误信息写入标准错误而不是标准输出12.7.1. 回顾以下是截止到上一篇文章为止所写出的全部代码。lib.rs:use std::error::Error; use std::fs; pub struct Config { pub query: String, pub filename: String, } impl Config { pub fn new(args: [String]) - ResultConfig, static str { if args.len() 3 { return Err(not enough arguments); } let query args[1].clone(); let filename args[2].clone(); Ok(Config { query, filename, }) } } pub fn run(config: Config) - Result(), Boxdyn Error { let contents fs::read_to_string(config.filename)?; for line in search(config.query, contents) { println!({}, line); } Ok(()) } pub fn searcha(query: str, contents: a str) - Veca 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 one_result() { let query duct; let contents \ Rust: safe, fast, productive. Pick three.; assert_eq!(vec![safe, fast, productive.], search(query, contents)); } }main.rs:use std::env; use std::process; use minigrep::Config; fn main() { let args: VecString env::args().collect(); let config Config::new(args).unwrap_or_else(|err| { println!(Problem parsing arguments: {}, err); process::exit(1); }); if let Err(e) minigrep::run(config) { println!(Application error: {}, e); process::exit(1); } }本文我们将通过添加一个额外的功能来改进minigrep用户可以通过环境变量打开不区分大小写搜索的选项。我们可以将此功能设置为命令行选项并要求用户每次希望应用时输入它但通过将其设置为环境变量我们允许用户设置环境变量一次并在那个终端会话中使所有搜索都不区分大小写。12.7.2. 继续用TDD不区分大小写搜索我们再次遵循TDD的步骤- 编写一个会失败的测试运行该测试并确保它是按照预期的原因失败- 编写或修改刚好足够的代码让新测试通过- 重构刚刚添加或修改的代码确保测试会通过- 返回步骤1继续本篇文章会继续用TDD来实现不区分大小写的搜索。新函数会被命名为search_case_insensitive之后再用环境变量来控制是否启用它。12.7.3. 编写会失败的测试这个对大小写不敏感的函数先给它起个名叫做search_case_insensitive。先把测试模块改一下改出一个对大小写敏感的测试函数和不敏感的测试函数#[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) ); } }然后再写search_case_insensitive函数的具体内容pub fn search_case_insensitivea(query: str, contents: a str) - Veca str { vec![] }为了让这个函数能被外部函数调用得使用pub来声明为公共的这个函数得加生命周期标志因为有多个非self的参数Rust无法判断哪个参数的生命周期跟返回值的生命周期相同返回值Vector内的元素是字符串切片是从contents截取的所以返回值应和contents的生命周期相同所以给它们两个标注了一样的生命周期a而query则不需要生命周期标注函数内容只需要确保能通过编译即可因为TDD的第一步是编写一个会出错的测试所以出错才是想要的结果这时候跑测试肯定会失败但没关系这正是TDD第一步想要的。12.7.4. 编写或修改刚好足够的代码让新测试通过其实search_case_insensitive的代码与search的大部分都差不多只需要做一些小修改即可。逻辑很好想就是把关键词变成小写再与文本各行的小写形式进行比较pub fn search_case_insensitivea(query: str, contents: a str) - Veca str { let mut results Vec::new(); let query query.to_lowercase(); for line in contents.lines() { if line.to_lowercase().contains(query) { results.push(line); } } results }to_lowercase方法可以把字符串变成全小写to_lowercase转换后的结果是String变量拥有所有权也就是新的query是String而不是str。在循环中的if语句使用的是query因为contains方法不接受String所以得传引用进去再跑一下测试$ cargo test Compiling minigrep v0.1.0 (/tmp/minigrep) Finished test profile [unoptimized debuginfo] target(s) in 0.15s Running unittests src/lib.rs (target/debug/deps/minigrep-dfdfbb86b622af32) running 2 tests test tests::case_sensitive ... ok test tests::case_insensitive ... 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-4c31ade9c6771135) 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两个测试都通过了。12.7.5. 在run函数中使用此函数这个函数没问题了就可以在run函数中调用了。但是首先得先为Config结构体添加一个字段来作为使用普通的search还是对大小写不敏感的search_case_insensitive的依据pub struct Config { pub query: String, pub filename: String, pub case_sensitive: bool, }修改run函数让它判断配置pub fn run(config: Config) - Result(), Boxdyn 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(()) }Config上的new这个构造器也得改根据环境变量给case_sensitive这个字段赋值impl Config { pub fn new(args: [String]) - ResultConfig, static str { if args.len() 3 { return Err(not enough arguments); } let query args[1].clone(); let filename args[2].clone(); let case_sensitive std::env::var(IGNORE_CASE).is_err(); Ok(Config { query, filename, case_sensitive, }) } }这里使用了std::env::var这个函数当然也可以先把std::env导入作用域再使用env::var它的参数是这个环境变量的名称按惯例全大写这里我写的是IGNORE_CASE中文翻译过来就是忽略大小写。这种环境变量只要出现就认为不区分大小写不出现就认为区分大小写。std::env::var的返回值是Result类型的如果这个IGNORE_CASE环境变量被设置了返回包含环境变量值的Ok(String)反之返回Err(std::env::VarError)。这里std::env::var后面还跟了is_err这个方法如果返回值是Err变体is_err就返回true于是case_sensitive被赋为true反之环境变量已设置则赋false。PS说实话这个小程序写成这个B样也是为了教学的无奈之举我看到一半我都被这个代码量气笑了真正写的时候没必要写得这么一板一眼的12.7.6. 整体代码与试运行写了这么多看看截止到目前的所有代码。lib.rs:use std::error::Error; use std::fs; pub struct Config { pub query: String, pub filename: String, pub case_sensitive: bool, } impl Config { pub fn new(args: [String]) - ResultConfig, static str { if args.len() 3 { return Err(not enough arguments); } let query args[1].clone(); let filename args[2].clone(); let case_sensitive std::env::var(IGNORE_CASE).is_err(); Ok(Config { query, filename, case_sensitive, }) } } pub fn run(config: Config) - Result(), Boxdyn 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(()) } pub fn searcha(query: str, contents: a str) - Veca str { let mut results Vec::new(); for line in contents.lines() { if line.contains(query) { results.push(line); } } results } pub fn search_case_insensitivea(query: str, contents: a str) - Veca str { let mut results Vec::new(); let query query.to_lowercase(); 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) ); } }main.rs:use std::env; use std::process; use minigrep::Config; fn main() { let args: VecString env::args().collect(); let config Config::new(args).unwrap_or_else(|err| { println!(Problem parsing arguments: {}, err); process::exit(1); }); if let Err(e) minigrep::run(config) { println!(Application error: {}, e); process::exit(1); } }来试运行一下首先我们将在不设置环境变量的情况下运行程序并使用查询to该查询应与包含全部小写单词to的任何行匹配$ cargo run -- to poem.txt Compiling minigrep v0.1.0 (/tmp/minigrep) Finished dev profile [unoptimized debuginfo] target(s) in 0.07s Running target/debug/minigrep to poem.txt Are you nobody, too? How dreary to be somebody!这次将IGNORE_CASE设置为1其它不变$ IGNORE_CASE1 cargo run -- to poem.txt会得到Are you nobody, too? How dreary to be somebody! To tell your name the livelong day To an admiring bog!没有任何问题。注意如果你在powershell中设置环境变量得这么写PS $Env:IGNORE_CASE1; cargo run -- to poem.txt这会使这个环境变量在这个会话中一直存在如果要去掉这个环境变量写PS Remove-Item Env:IGNORE_CASE