当前位置: 首页 > news >正文

【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]) -> Result<Config, &'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<(), Box<dyn Error>> { let contents = fs::read_to_string(config.filename)?; 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 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: Vec<String> = 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_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { vec![] }
  • 为了让这个函数能被外部函数调用,得使用pub来声明为公共的
  • 这个函数得加生命周期标志,因为有多个非self的参数,Rust无法判断哪个参数的生命周期跟返回值的生命周期相同
  • 返回值Vector内的元素是字符串切片,是从contents截取的,所以返回值应和contents的生命周期相同,所以给它们两个标注了一样的生命周期'a,而query则不需要生命周期标注
  • 函数内容只需要确保能通过编译即可,因为TDD的第一步是编写一个会出错的测试,所以出错才是想要的结果

这时候跑测试肯定会失败,但没关系,这正是TDD第一步想要的。

12.7.4. 编写或修改刚好足够的代码,让新测试通过

其实search_case_insensitive的代码与search的大部分都差不多,只需要做一些小修改即可。逻辑很好想,就是把关键词变成小写,再与文本各行的小写形式进行比较:

pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a 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,变量拥有所有权,也就是新的queryString而不是&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<(), 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(()) }

Config上的new这个构造器也得改,根据环境变量给case_sensitive这个字段赋值:

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 = 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]) -> 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 = std::env::var("IGNORE_CASE").is_err(); Ok(Config { query, filename, case_sensitive, }) } } 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(()) } 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 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: Vec<String> = 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_CASE=1 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_CASE=1; cargo run -- to poem.txt

这会使这个环境变量在这个会话中一直存在,如果要去掉这个环境变量,写:

PS> Remove-Item Env:IGNORE_CASE
http://www.jsqmd.com/news/1249492/

相关文章:

  • 【Rust自学】2.2. 猜数游戏Pt.2 生成随机数
  • 非接触式激光雪深监测站,气象积雪观测新方案
  • 【爱马仕】新手友好|Hermes Agent 轻量化部署方案,轻松搭建桌面数字助手(含安装包)
  • AI原生应用中的上下文窗口优化与压缩技术
  • 西平装修亲测:2026年7月选这家省心
  • AI数字人口播视频生成技术解析与应用实践
  • 【AI写作多语言翻译终极指南】:20年技术专家亲授5大避坑法则与实时落地框架
  • mysql多条查询结果纵向拼接
  • 揭秘 GitHub 最火的开源 Skills 仓库,夯爆了!30 秒带你用上,让 AI 效率起飞
  • 高效办公 AI 智能体搭建,OpenClaw 整合包极简部署方案(含安装包)
  • AI语义风险防御:认知稳定性测试框架与实践
  • 惠州2026年中央空调回收实测:五星推荐本土老牌商家 - 广东再生资源回收
  • 2026昆明文创商店亚克力定制靠谱厂家推荐 深度选型指南 - 全域品牌推荐
  • 办公自动化专用 OpenClaw 部署教程,内置全套运行组件(含安装包)
  • 海外商标注册成功后,企业还需要做好哪些监测与管理? - 客啦啦视界
  • 可再生能源与电动汽车协同调度的Python实现与优化
  • TI MCU引脚复用与IO控制:DCAN与MibSPI配置实战
  • 怎么刷入Openwrt软路由到了笔记本电脑上并作为旁路由?旁路由应该怎么设置?
  • RAG技术中结构化数据处理五大实战技巧
  • 丹摩平台 | 如何在丹摩平台创建一个自己的GPU云实例
  • 自然式风格花园庭院设计施工选哪家?杭州美村美户园林|原生野趣自然庭院一站式营造全解析 - 品牌测评网
  • fastapi: 多个子应用时判断全局异常的处理形式
  • PSO-XGBoost组合模型在工业预测中的优化与应用
  • 【Rust自学】11.4. 用should_panic检查恐慌
  • 联邦学习通信效率优化:模型压缩与异步协议实践
  • VS Code 远程部署 claude code 插件
  • 杭州 2026 旧腕表回收避坑,正规门店交易细节拆解 - 每日生活报
  • 基于ddddocr与Hu矩的验证码识别技术实践
  • 2026年联想代理商怎么找:联想授权代理商选择推荐指南 - 全域品牌推荐
  • 收藏!10个企业级AI高频应用场景,小白也能轻松落地实践