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

embedded/2025/1/17 23:54:45/

12.7.0. 写在正文之前

第12章要做一个实例的项目——一个命令行程序。这个程序是一个grep(Global Regular Expression Print),是一个全局正则搜索和输出的工具。它的功能是在指定的文件中搜索出指定的文字。

这个项目分为这么几步:

  • 接收命令行参数
  • 读取文件
  • 重构:改进模块和错误处理
  • 使用TDD(测试驱动开发)开发库功能
  • 使用环境变量(本文)
  • 将错误信息写入标准错误而不是标准输出

喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
请添加图片描述

12.7.1. 回顾

以下是截止到上一篇文章为止所写出的全部代码。

lib.rs:

rust">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:

rust">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. 编写不区分大小写的search函数

这里不区分大小写的功能是通过环境变量来实现的,当然也可使用参数来实现,但使用环境变量的好处是只需要配置一次就可以在整个终端的会话中一直保持有效。

对于这个功能,我们也使用TDD(测试驱动开发)流程来开发:

  • 编写一个会失败的测试,运行该测试,并确保它是按照预期的原因失败
  • 编写或修改刚好足够的代码,让新测试通过
  • 重构刚刚添加或修改的代码,确保测试会通过
  • 返回步骤1,继续

1. 编写一个会失败的测试

这个对大小写不敏感的函数先给它起个名叫做search_case_insensitive

先把测试模块改一下,改出一个对大小写敏感的测试函数和不敏感的测试函数:

rust">#[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函数的具体内容:

rust">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第一步想要的

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

其实search_case_insensitive的代码与search的大部分都差不多,只需要做一些小修改即可。逻辑很好想,就是把关键词和文本内容都变成全小写即可:

rust">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.to_lowercase().lines() {  if line.contains(&query) {  results.push(line);  }  }  results  
}
  • to_lowercase方法可以把字符串变成全小写
  • to_lowercase转换后的结果是String,变量拥有所有权,也就是新的queryString而不是&str。在循环中的if语句使用的是&query,因为contains方法不接受String所以得传引用进去。

再跑一下测试:

$ cargo testCompiling minigrep v0.1.0 (file:///projects/minigrep)Finished `test` profile [unoptimized + debuginfo] target(s) in 1.33sRunning unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)running 2 tests
test tests::case_insensitive ... ok
test tests::case_sensitive ... oktest result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sRunning unittests src/main.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)running 0 teststest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00sDoc-tests minigreprunning 0 teststest result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

两个测试都通过了

3. 在run函数中使用此函数

这个函数没问题了,就可以在run函数中调用了。

但是首先得先为Config结构体添加一个字段来作为使用普通的search还是对大小写不敏感的search_case_insensitive的依据:

rust">pub struct Config {  pub query: String,  pub filename: String,  pub case_sensitive: bool,  
}

修改run函数让它判断配置:

rust">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这个字段赋值:

rust">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("CASE_INSENSITIVE").is_err();  Ok(Config { query, filename, case_sensitive})  }  
}

这里使用了std::env::var这个函数(当然也可以先把std::env导入作用域,再使用env::var),它的参数是这个环境变量的名称(按惯例全大写),这里我写的是CASE_INSENSITIVE,中文翻译过来就是大小写不敏感。这种环境变量只要出现就认为不区分大小写,不出现就认为区分大小写。

std::env::var的返回值是Result类型的,如果这个CASE_INSENSITIVE环境变量被设置了,返回包含环境变量值的Ok(String),反之返回Err(std::env::VarError)

这里std::env::var后面还跟了is_err这个方法,如果is_errErr变体,就会返回赋true给变量case_sensitive,反之就是赋false

PS:说实话,这个小程序写成这个B样也是为了教学的无奈之举,我看到一半我都被这个代码量气笑了,真正写的时候没必要写得这么一板一眼的

12.7.3. 整体代码与试运行

写了这么多,看看截止到目前的所有代码。

lib.rs:

rust">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("CASE_INSENSITIVE").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.to_lowercase().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)  );  }  
}

main.rs:

rust">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.txtCompiling minigrep v0.1.0 (file:///projects/minigrep)Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0sRunning `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.ppmy.cn/embedded/154796.html

相关文章

Kotlin函数类型探索:T.()->Unit的扩展函数、无参函数()->Unit与类型参数函数(T)->Unit

在Kotlin编程语言的丰富特性中&#xff0c;函数类型扮演着至关重要的角色。它们不仅定义了代码的行为&#xff0c;还通过灵活的类型系统促进了代码的重用和模块化。本文将深入探讨Kotlin中的三种核心函数类型&#xff1a;T.()->Unit的扩展函数、无参函数()->Unit以及类型…

C# OpenCV机器视觉:极大值抑制

在一个阳光有些慵懒的午后&#xff0c;阿强像往常一样窝在他那被各种电子元件和线路堆满的实验室里&#xff0c;周围的电脑屏幕闪烁着神秘的代码和复杂的图像&#xff0c;仿佛在诉说着一个个未被解开的科技谜题。阿强最近痴迷于机器视觉领域&#xff0c;而今天&#xff0c;他将…

什么是docker?关于docker容器的全面详细介绍

什么是 Docker Docker 是一种开源的容器化平台&#xff0c;旨在简化应用程序的开发、部署和运行过程。它提供了一种轻量级、可移植且自包含的容器化环境&#xff0c;使开发人员能够以一致的方式在不同的计算机上构建、打包和分发应用程序。 Docker 的出现改变了传统软件开发和…

webpack 4 升级 webpack 5

升级至最新的 webpack 和 webpack-cli npm run build 报错&#xff0c; unknown option -p 解决方案&#xff1a; 改成 --mode production npm run build 报错 unknown option --hide-modules 解决方案&#xff1a;直接移除 npm run build 报错&#xff1a;TypeError: Cannot a…

@Scope(“prototype“)

Scope("prototype") 是 Spring 框架中用于定义 Bean 作用域的注解之一&#xff0c;它的主要作用是将一个 Bean 定义成 原型作用域&#xff08;Prototype Scope&#xff09;。在原型作用域下&#xff0c;每次从 Spring 容器中请求这个 Bean 时&#xff0c;都会创建一个…

【云岚到家】-day03-门户缓存方案选择

【云岚到家】-day03-门户缓存方案选择 1.门户常用的技术方案 什么是门户 说到门户马上会想到门户网站&#xff0c;中国比较早的门户网站有新浪、网易、搜狐、腾讯等&#xff0c;门户网站为用户提供一个集中的、易于访问的平台&#xff0c;使他们能够方便地获取各种信息和服务…

java day04-面向对象基础(内存 封装 继承 修饰符 工具类 )

1.对象内存图 1.1 Java 内存分配 1.2 堆和栈 栈:所有局部变量都会在栈内存中创建 局部变量&#xff1a;定义在方法中的变量或者方法声明上的变量 方法执行都会加载到栈中进行 -----------------------------------------------------------------------------------------…

工业视觉5-工业视觉选型

工业视觉5-工业视觉选型 任务分析三、知识准备问答四、相机选型五、总结 任务分析 重点明确任务要求 例子&#xff1a; 检测任务类型 外观检测&#xff1a;检查产品表面是否有划痕、污渍、缺陷等。例如&#xff0c;在电子元件生产中&#xff0c;需要检测芯片表面的瑕疵&…