吴旭华 发表于 2024-12-3 16:24:56

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

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

[*]可以输入 --help展示用法
[*]如果文件凌驾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){<br> print!("正在查找大文件...");       let result= search::searh_big_file(&con.file_name,&con.target);
       match result{
      Ok(_)=>println!("查找完成!"),
      Err(e)=>println!("读取文件失败: {:?}", e)
       }
    }
    else{<br> print!("正在查找小文件...");      let result=search::search_small_file(&con);
      for line in result.iter() {
            println!("{}",line);
      }
    }

 
lib.rs
/**
* 创建两个子模块
*/
#
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: &) -> Result<Config, &str> {
      if args.len() < 3 {
            if args.len() == 2 {
                //如果target_name=="--help"则打印帮助信息,否则执行文件
                let target_name = args.clone();
                if target_name == "--help" {
                  return Ok(Config {
                        target: target_name,
                        file_name: "".to_string(),
                        show_help: true,
                  });
                }
            }
            return Err("必须有两个参数");
      }
      println!("rust返回的程序路径:{}", args);
      let target_name = args.clone();
      let file_path = args.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目录下放了两个文件:
https://img2024.cnblogs.com/blog/1177268/202412/1177268-20241203175113690-891026886.png
古诗.txt  1kb
武林外史_utf8.txt 2120kb
表现帮助
https://img2024.cnblogs.com/blog/1177268/202412/1177268-20241203175441446-365272074.png
 
查找小文件
https://img2024.cnblogs.com/blog/1177268/202412/1177268-20241203175524016-626062743.png
 
查找大文件
https://img2024.cnblogs.com/blog/1177268/202412/1177268-20241203175606944-101967849.png
 
三、小结

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

[*]范例-重要是Vec,String,枚举,struct
[*]结果处理Result,错误处理
[*]程序组织,包括模块,use等等
[*]i/o-读取程序参数、读取文件,分行读取
必须得承认,rust的代码不好写,也不好看,幸好有优秀的编译器和优秀的ide帮了大忙!
这些语法对于初学者而言,还是比较别扭,充斥着怪异的符号和奇特的语法。也许rust的发明者需要泯灭一些心思再优化下编译器,让工程师能够
以更自然优雅的方式写出不违反初心的代码。工程服从也是很重要的!
在编写的过程中,对引用借用有了更具体的体会!
 

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: rust学习十二、一个I/O程序练习