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

【Rust自学】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. 编写不区分大小写的search函数

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

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

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

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

这个对大小写不敏感的函数先给它起个名叫做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第一步想要的

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

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

再跑一下测试:

$ cargo test
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 1.33s
     Running unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)

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

     Running unittests src/main.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)

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

两个测试都通过了

3. 在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("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:

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:

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 (file:///projects/minigrep)
    Finished `dev` profile [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 ,其它不变:

$ 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.kler.cn/a/505761.html

相关文章:

  • 算法每日双题精讲 —— 二分查找(二分查找,在排序数组中查找元素的第一个和最后一个位置)
  • 麦田物语学习笔记:构建游戏的时间系统
  • Android中下载 HAXM 报错 HAXM installation failed,如何解决?
  • FPGA工程师成长四阶段
  • linux环境使用docker部署多个war项目
  • WORD转PDF脚本文件
  • SpringBoot开发——Spring Boot 自动化测试框架的高效运用
  • Java并发编程——线程池(基础,使用,拒绝策略,命名,提交方式,状态)
  • Mybatis-底层是如何解决sql注入增删改查操作--删除操作
  • VUE请求返回二进制文件流下载文件例子
  • doc、pdf转markdown
  • STM32H7通过CUBEMX初始化移植LWIP,DHCP建立RAW TCP服务器,不停发成功
  • Spring MVC复杂数据绑定-绑定集合
  • VUE3 + Ant Design Vue4 开发笔记
  • MySQL表的增删改查(进阶)-下篇
  • 【Qt】QThread总结
  • flutter R库对图片资源进行自动管理
  • c#删除文件和目录到回收站
  • 【Linux系统编程】—— 自动化构建工具Makefile指南
  • rtthread学习笔记系列(3) -- FINSH模块
  • 寄存器 reg
  • 【学习笔记】GitLab 使用技巧和说明和配置和使用方法
  • [操作系统] 深入理解约翰·冯·诺伊曼体系
  • DNS介绍(1):基本概念
  • 如何确保API调用安全
  • Flink (三):核心概念(并行度、算子链、任务槽)