你好,我是陈天。
现在 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!
如果这个也可以实现,那进一步放宽限制,允许用户提供一个正则表达式,来查找满足文件通配符的所有文件(你可以使用 globset 或者 glob 来处理通配符),比如:
$ rgrep Hel[^\\s]+ a*.txt
a.txt
55:1 Hello world. This is an exmaple text
89:1 Help me! I need assistant!
5:6 Use `Help` to get help.
abc.txt:
100:1 Hello Tyr!
其中,冒号前面的数字是行号,后面的数字是字符在这一行的位置。
给你一点小提示。
- 对于命令行的部分,你可以使用 clap3 或者 structopt,也可以就用 env.args()。
- 对于正则表达式的支持,可以使用 regex。
- 至于文件的读取,可以使用 std::fs 或者 tokio::fs。你可以顺序对所有满足通配符的文件进行处理,也可以用 rayon 或者 tokio 来并行处理。
- 对于输出的结果,最好能把匹配的文字用不同颜色展示。

如果你有余力,可以看看 grep 的文档,尝试实现更多的功能。
祝你好运!
加油,我们下节课作业讲解见。
精选留言
2021-10-15 21:22:51
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-17 23:38:55
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(())
}
2021-10-14 17:55:24
https://github.com/LgnMs/rgrep
2021-10-15 16:51:32
为了练习之前学过的内容,试了各种写法,应该会有很多不合理的地方。
而且没有做并行化,希望以后有时间可以加上,并把代码重构得更好。
https://github.com/imag1ne/grepr
2021-10-13 15:45:19
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]);
}
}
2024-04-08 20:53:14
let mut result = Vec::new();
let file = std::fs::File::open(file_path)?;
let reader = std::io::BufReader::new(file);
for (no, line) in reader.lines().enumerate() {
let line = line?;
// consider k as a regular expression
let r = Regex::new(k)?;
if let Some(mat) = r.find(&line) {
let start = mat.start();
let colored_line =
r.replace_all(&line, |caps: &Captures| format!("{}", &caps[0].blue()));
result.push(Match {
line: colored_line.to_string(),
line_number: no + 1,
start,
});
}
}
Ok(result)
}
2023-10-29 20:22:43
loop {
print!("> ");
let mut line = String::new();
io::stdin().read_line(&mut line)
.expect("Failed to read line");
println!("{}", line);
}
为什么实际执行中,>不会第一时间显示,会显示在回车之后回显的第一个字符,输入行是空白,回显的时候是>+刚刚输入的字符
用println!就能正常第一行显示>,然后输入,回显
2022-06-09 11:54:56
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-08 14:15:26
<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-03-19 20:47:25
把整个程序分成了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-08 20:57:49