rust学习十二、一个I/O程序练习

本文内容,基本同原书本,只是稍作了一些改动:

  1. 可以输入 --help展示用法
  2. 如果文件超过1MB,则会采用逐行查找的方法,避免内存不足

整个程序包含个文件:main.rs,lib.rs

一、代码

闲话少叙,直接上代码。

main.rs

/**
 * 完全模仿书本的例子
 * 
 * 运行示例 
 * cargo run 宋 E:\learning\gitee\rust-org\test-1\minigrep\src\古诗.txt  -- ok
 * cargo run 宋 古诗.txt  -- ok
 * cargo run 宋   -- 错误,因为参数太少了
 */
use std::env;
use minigrep::{config,search};

fn main() {
    //1.0 args() 返回的字符是unicode编码的 ,args()本身返回迭代器,collect()方法将迭代器中的元素收集到Vec<String>中                                                                           
    let args: Vec<String> = env::args().collect();

    //2.0 解析参数,并构建配置
    // unwrap_or_else()方法,如果Config::build(&args)返回Ok(config),则执行闭包中的内容,否则执行|err|{...}中的内容
    let con=config::build(&args).unwrap_or_else(|err|{
        eprintln!("程序异常:{}",err);
        std::process::exit(1);
    });    

    if con.show_help {
        config::show_help();
        std::process::exit(0);
    }
    
    println!("配置信息:{:?}",&con);

    //3.0 根据配置查找文件,并打印查找到的结果
    //如果文件比较小,那么就直接在内存处理;否则直接在search中逐行处理
    if config::is_too_big(&con.file_name){
print!("正在查找大文件...");
       let result= search::searh_big_file(&con.file_name,&con.target);
       match result{
        Ok(_)=>println!("查找完成!"),
        Err(e)=>println!("读取文件失败: {:?}", e)
       }
    }
    else{
print!("正在查找小文件...");
        let result=search::search_small_file(&con);
        for line in result.iter() {
            println!("{}",line);
        }
    }
}

 

 

lib.rs

/**
 * 创建两个子模块
 */
#[derive(Debug)]
pub struct Config {
    pub file_name: String,
    pub target: String,
    pub show_help: bool,
}
/**
 * 模块执行配置
 */
pub mod config {
    use super::Config;
    use std::env;
    use std::fs;
    use std::path::Path;
    /**
     * 第二个参数: 被查找的字符串
     * 第三个参数: 文件路径,可以是全路径或者不包含路径的文件名
     */
    pub fn build(args: &[String]) -> Result<Config, &str> {
        if args.len() < 3 {
            if args.len() == 2 {
                //如果target_name=="--help"则打印帮助信息,否则执行文件
                let target_name = args[1].clone();
                if target_name == "--help" {
                    return Ok(Config {
                        target: target_name,
                        file_name: "".to_string(),
                        show_help: true,
                    });
                }
            }
            return Err("必须有两个参数");
        }
        println!("rust返回的程序路径:{}", args[0]);
        let target_name = args[1].clone();
        let file_path = args[2].clone();

        if !file_exists(&file_path) {
            //用户输入的是文件名
            let exec_path = get_current_exec_path();
            println!("当前执行路径:{}", exec_path);
            let mut tmp_path = exec_path.clone() + &file_path;
            if !file_exists(&tmp_path) {
                tmp_path = exec_path.clone().replace("\\target\\debug\\", "\\src\\") + &file_path;
                if !file_exists(&tmp_path) {
                    tmp_path =
                        exec_path.clone().replace("\\target\\release\\", "\\src\\") + &file_path;
                    if !file_exists(&tmp_path) {
                        return Err("文件不存在");
                    }
                }
                //在src下
                Ok(Config {
                    target: target_name,
                    file_name: tmp_path,
                    show_help: false,
                })
            } else {
                //在当前目录下=执行路径+文件名
                Ok(Config {
                    target: target_name,
                    file_name: tmp_path,
                    show_help: false,
                })
            }
        } else {
            //在当前目录下 -- 即用户输入的是全路径
            println!("{}", file_path);
            Ok(Config {
                target: target_name,
                file_name: file_path,
                show_help: false,
            })
        }
    }

    /**
     * 判断文件是否存在,如果存在则返回true,否则返回false
     */
    fn file_exists(file_name: &String) -> bool {
        Path::new(&file_name).exists()
    }

    /**
     * 获取当前执行路径,最后会包含分隔符号\
     * 例如 d:\soft\rust-test\
     * 注意:仅限用于windows平台
     */
    fn get_current_exec_path() -> String {
        let cur_dir = match env::current_exe() {
            Ok(exe_path) => exe_path.display().to_string(),
            Err(e) => panic!("获取当前路径失败:{}", e),
        };
        // 获取cur_dir的目录,不需要包含文件名称
        let path = Path::new(&cur_dir);
        let result = path.parent().unwrap().to_str().unwrap().to_string() + "\\";
        result
    }

    pub fn is_too_big(file_name: &String) -> bool {
        //判断file_name是否超过1Mb,如果是,则返回true,否则返回false
        let file_size = fs::metadata(file_name).unwrap().len();
        if file_size > 1 * 1024 * 1024 {
            return true;
        } else {
            false
        }
    }

    pub fn show_help() {
        println!("用法:minigrep target path");
        println!("     minigrep --help");
        println!("参数:");
        println!("  target  需要查找的目标字符串");
        println!("  path    包含target的文件,可以是全路径,也可以是文件名");
        println!("          如果是文件名,则会尝试在当前目录下,或者是在相对路径..\\..\\src");
        println!("  --help  查看帮助");
        println!("示例:");
        println!("  minigrep 宋 E:\\learning\\gitee\\rust-org\\test-1\\minigrep\\src\\古诗.txt ");
        println!("  minigrep 宋 古诗.txt");
        println!("版本 1.0 for windows, 作者->lzfto");
    }
}

/**
 * 模块执行搜索
 */
pub mod search {
    use super::Config;
    use std::fs;
    use std::fs::File;
    use std::io::{BufRead, BufReader};
    use std::path::Path;
    /**
     * 搜索小于1M的文件,并返回查找的结果
     * @return  返回查找的结果 Vec<String>
     */
    pub fn search_small_file(con: &Config) -> Vec<String> {
        let contents = fs::read_to_string(con.file_name.clone()).expect("文件不存在");
        println!("文章内容:\n{}", contents);
        //把contents根据换行符切割成向量
        let mut result: Vec<String> = vec![];
        let lines = contents.lines();
        for line in lines {
            if line.find(&con.target).is_some() {
                result.push(line.to_string());
            }
        }
        return result;
    }
    /**
     * 搜索大于1M的文件
     * @return  表示成功与否的信息 Result<()>
     */
    pub fn searh_big_file(file_name: &String, target: &String) -> std::io::Result<()> {
        //打开文件file_name,逐行查找,找到则直接打印
        let path = Path::new(file_name);
        let file = File::open(&path)?;
        let reader = BufReader::new(file);
        for line in reader.lines() {
            match line {
                Ok(line) => {
                    if line.find(target).is_some() {
                        println!("{}", line);
                    }
                }
                Err(e) => println!("Error reading line: {}", e),
            }
        }
        Ok(())
    }
}

 

 

二、测试

为了便于测试,在src目录下放了两个文件:

古诗.txt  1kb

武林外史_utf8.txt 2120kb

显示帮助

 

查找小文件

 

查找大文件

 

三、小结

本例用到了截止12章节的大部分知识:

  1. 类型-主要是Vec,String,枚举,struct
  2. 结果处理Result,错误处理
  3. 程序组织,包括模块,use等等
  4. i/o-读取程序参数、读取文件,分行读取

必须得承认,rust的代码不好写,也不好看,幸好有优异的编译器和优秀的ide帮了大忙!

这些语法对于初学者而言,还是比较别扭,充斥着怪异的符号和奇特的语法。也许rust的发明者需要花费一些心思再优化下编译器,让工程师能够

以更自然优雅的方式写出不违背初心的代码。工程效率也是很重要的!

在编写的过程中,对引用借用有了更具体的体会!

 

posted @ 2024-12-03 18:06  正在战斗中  阅读(15)  评论(0编辑  收藏  举报