本文內容,基本同原書本,只是稍作了一些改動:
- 可以輸入 --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){
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章節的大部分知識:
- 型別-主要是Vec,String,列舉,struct
- 結果處理Result,錯誤處理
- 程式組織,包括模組,use等等
- i/o-讀取程式引數、讀取檔案,分行讀取
必須得承認,rust的程式碼不好寫,也不好看,幸好有優異的編譯器和優秀的ide幫了大忙!
這些語法對於初學者而言,還是比較彆扭,充斥著怪異的符號和奇特的語法。也許rust的發明者需要花費一些心思再最佳化下編譯器,讓工程師能夠
以更自然優雅的方式寫出不違背初心的程式碼。工程效率也是很重要的!
在編寫的過程中,對引用借用有了更具體的體會!