陈天 · Rust 编程第一课
陈天
Tubi TV 研发副总裁
23195 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 65 讲
基础篇 (21讲)
陈天 · Rust 编程第一课
15
15
1.0x
00:00/00:00
登录|注册

加餐|期中测试:来写一个简单的grep命令行

允许用户提供一个正则表达式来查找满足文件通配符的所有文件
允许用户提供一个正则表达式来查找文件中所有包含该字符串的行
给定一个字符串和一个文件,打印出文件中所有包含该字符串的行
鼓励尝试实现更多功能
提供了命令行部分、正则表达式支持、文件读取和输出结果的提示
rgrep支持三种使用场景
rgrep工具类似grep的工具
期中测试:来写一个简单的grep命令行
准备应对简单的开发任务
Rust 基础篇学习完成
上次总结后续的文章
上次总结的结果
总结的主题知识关系脑图

该思维导图由 AI 生成,仅供参考

你好,我是陈天。
现在 Rust 基础篇已经学完了,相信你已经有足够的信心去应对一些简单的开发任务。今天我们就来个期中测试,实际考察一下你对 Rust 语言的理解以及对所学知识的应用情况。
我们要做的小工具是 rgrep,它是一个类似 grep 的工具。如果你是一个 *nix 用户,那大概率使用过 grep 或者 ag 这样的文本查找工具。
grep 命令用于查找文件里符合条件的字符串。如果发现某个文件的内容符合所指定的字符串,grep 命令会把含有字符串的那一行显示出;若不指定任何文件名称,或是所给予的文件名为  -,grep 命令会从标准输入设备读取数据。
我们的 rgrep 要稍微简单一些,它可以支持以下三种使用场景:
首先是最简单的,给定一个字符串以及一个文件,打印出文件中所有包含该字符串的行:
$ rgrep Hello a.txt
55: Hello world. This is an exmaple text
然后放宽限制,允许用户提供一个正则表达式,来查找文件中所有包含该字符串的行:
$ rgrep Hel[^\\s]+ a.txt
55: Hello world. This is an exmaple text
89: Help me! I need assistant!
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文介绍了如何使用Rust语言编写一个简单的grep命令行工具rgrep。作者提到了rgrep的三种使用场景:首先是给定一个字符串和一个文件,打印出文件中所有包含该字符串的行;其次是允许用户提供一个正则表达式来查找文件中所有包含该字符串的行;最后是允许用户提供一个正则表达式来查找满足文件通配符的所有文件。作者还给出了一些小提示,包括使用clap库处理命令行、使用regex库支持正则表达式、使用Rayon库并行处理文件读取,并建议将匹配的文字用不同颜色展示。此外,作者鼓励读者可以尝试查看grep的文档,尝试实现更多的功能。整体而言,本文是一篇针对Rust语言初学者的期中测试,通过实际编码来考察读者对Rust语言的理解以及对所学知识的应用情况。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《陈天 · Rust 编程第一课》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(10)

  • 最新
  • 精选
  • Quincy
    1. 最简单的 ```rust use std::error::Error; use clap::{AppSettings, Clap}; use colored::Colorize; use tokio::fs; #[derive(Clap)] #[clap(version = "1.0", author = "Custer<custer@email.cn>")] #[clap(setting = AppSettings::ColoredHelp)] struct Opts { find: String, path: String, } #[tokio::main] async fn main() -> Result<(), Box<dyn Error>> { // 1. 解析参数 let opts: Opts = Opts::parse(); let find = opts.find; let path = opts.path; let length = find.len(); // 2. 读取文件 let contents = fs::read_to_string(path).await?; // 3. 匹配字符串 for (row, line) in contents.lines().enumerate() { if let Some(col) = line.find(&find) { println!( "{}:{} {}{}{}", row + 1, col + 1, &line[..col], &line[col..col + length].red().bold(), &line[col + length..] ); } } Ok(()) } ``` 2. 允许用户提供一个正则表达式,来查找文件中所有包含该字符串的行 ```rust // 3. 匹配字符串 for (row, line) in contents.lines().enumerate() { if let Some(re) = Regex::new(find.as_str()).unwrap().find(line) { let start = re.start(); let end = re.end(); println!( "{}:{} {}{}{}", row + 1, start + 1, &line[..start], &line[start..end].red().bold(), &line[end..] ); } } ``` 3. 允许用户提供一个正则表达式,来查找满足文件通配符的所有文件(好像并不需要使用globset 或者 glob 就可以处理通配符?) ```rust ... struct Opts { find: String, #[clap(multiple_values = true)] paths: Vec<String>, } #[tokio::main] async fn main() -> Result<(), Box<dyn Error>> { // 1. 解析参数 let opts: Opts = Opts::parse(); let find = opts.find.as_str(); let paths = opts.paths; // 2. 循环读取匹配到的文件 for path in paths { println!("{:?}", path); let contents = fs::read_to_string(path).await?; // 3. 匹配字符串 ... } Ok(()) } ```

    作者回复: 👍 非常好!

    2021-10-15
    3
  • 余泽锋
    时间比较紧,先写个初始版本: extern crate clap; use std::path::Path; use std::ffi::OsStr; use std::error::Error; use clap::{Arg, App}; use regex::Regex; use tokio::fs::{File, read_dir}; use tokio::io::AsyncReadExt; #[tokio::main] async fn main() -> Result<(), Box<dyn Error>> { let matches = App::new("rgrep") .version("1.0") .about("Does awesome things") .arg(Arg::with_name("key_word") .index(1)) .arg(Arg::with_name("file") .multiple(true) .index(2)) .get_matches(); println!("{:?}", matches); let key_word = matches.value_of("key_word").unwrap(); println!("{}", key_word); let file_path = matches.values_of_lossy("file").unwrap(); println!("{:?}", file_path); let re_key_word = format!(r"{}", &key_word); println!("re_key_word: {}", &re_key_word); let re = Regex::new(&re_key_word).unwrap(); for file_path in file_path { let mut file = File::open(&file_path).await?; // let mut contents = vec![]; let result = tokio::fs::read_to_string(&file_path).await?; if let Some(caps) = re.captures(&result) { println!("file_path: {:?}", &file_path); println!("file: {:?}", &file); println!("caps: {:?}", &caps); println!("result: {:?}", &result); } } Ok(()) }

    作者回复: 嗯,不错。可以进一步优化一下性能以及可测试性。建议看看我的参考代码:https://github.com/tyrchen/geektime-rust/tree/master/mid_term_rgrep

    2021-10-17
    2
  • 夏洛克Moriaty
    磕磕盼盼搞了一天终于实现了这一讲的需求,期中测试算是通过了。自己动手实现的过程中收获了非常多的东西。代码结构前前后后改了许多次,还达不到开发过程中接口不变只是实现变的能力。我把代码仓库链接贴在下面算是献丑了,说实话有点不好意思拿出来哈哈。 https://github.com/LgnMs/rgrep

    作者回复: 挺不错的!流程图画的很好啊,可以放到 Readme.md 里

    2021-10-14
    2
  • D. D
    试着写了一下,实现得比较匆忙。 为了练习之前学过的内容,试了各种写法,应该会有很多不合理的地方。 而且没有做并行化,希望以后有时间可以加上,并把代码重构得更好。 https://github.com/imag1ne/grepr

    作者回复: 嗯,写的很不错,尤其是 Display trait 的使用。比我用一个函数处理更好。 impl Display for MatchLine<'_>

    2021-10-15
    1
  • 记事本
    let filename = std::env::args().nth(2).unwrap(); let query = std::env::args().nth(1).unwrap(); let case_sensitive = std::env::var("is_sens").is_err(); let contents = std::fs::read_to_string(filename).unwrap(); if case_sensitive { let mut i = 1; for v in contents.lines(){ if v.contains(&query){ println!("{}:{}",i,v); } i+=1; } }else { let c =contents.lines().filter(|item|item.contains(&query)).collect::<Vec<_>>(); for i in 1..=c.len(){ println!("{}:{}",i,c[i]); } }

    作者回复: 嗯,你可以用 regex 处理,更方便一些。你也可以看看 github 仓库里的代码:https://github.com/tyrchen/geektime-rust/tree/master/mid_term_rgrep

    2021-10-13
    1
  • 支离益
    我碰到一个问题,loop中的print!: loop { print!("> "); let mut line = String::new(); io::stdin().read_line(&mut line) .expect("Failed to read line"); println!("{}", line); } 为什么实际执行中,>不会第一时间显示,会显示在回车之后回显的第一个字符,输入行是空白,回显的时候是>+刚刚输入的字符 用println!就能正常第一行显示>,然后输入,回显
    2023-10-29归属地:吉林
  • forever 蒙
    error: The following required arguments were not provided: <PATTERN> <GLOB> USAGE: rgrep.exe <PATTERN> <GLOB> For more information try --help error: process didn't exit successfully: `E:\geektime-Rust-master\geektime-rust-master\target\debug\rgrep.exe` (exit code: 2) Process finished with exit code 2 求助。。。不知道为什么总输出这个
    2022-06-09
  • forever 蒙
    error: The following required arguments were not provided: <PATTERN> <GLOB> USAGE: rgrep.exe <PATTERN> <GLOB> For more information try --help error: process didn't exit successfully: `E:\geektime-Rust-master\geektime-rust-master\target\debug\rgrep.exe` (exit code: 2)求助
    2022-06-08
  • gt
    交个作业:https://github.com/ForInfinity/rgrep 把整个程序分成了fs、pattern、formatter三个部分,分别负责文件读写、匹配和高亮及输出console。先分别敲定了trait,然后实现。以后可以扩展使用不同的fs来源、更多的匹配模式、不同的formatter。 不过在编写泛型的时候遇到了个问题: 首先存在一个trait MatchOutput: ``` pub trait MatchOutput<T> where T: Display ``` 当我想实现另一个trait Printer时: `` pub struct Printer<M: Display, T: MatchOutput<M>> { pub formatter: T, } ``` rust会编译不通过,提示存在未使用的泛型M: ``` error[E0392]: parameter `M` is never used ``` 对此不太理解,也不知道是不是因为这不是最佳实践。 现在临时的解决方案是添加一个私有的变量_m:M,并在写new方法的时候将其初始化为None: ``` pub struct Printer<M: Display, T: MatchOutput<M>> { // To pass the compiler // Otherwise: error[E0392]: parameter `M` is never used _m: Option<M>, pub formatter: T, } ``` 蹲个老师的解答。
    2022-03-19
  • Geek_994f3b
    也写了个:https://github.com/startdusk/rgrep,欢迎老师指正
    2022-03-08
收起评论
显示
设置
留言
10
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部