add some contents to 入门实战 chapter

pull/1149/head
sunface 2 years ago
parent ed01a0a39e
commit 14ecc2bac9

@ -71,7 +71,8 @@
- [注释和文档](basic/comment.md)
- [类型转换](basic/converse.md)
- [格式化输出](basic/formatted-output.md)
<!-- - [入门实战:构建一个简单命令行程序 Doing](basic-practice/intro.md) -->
- [入门实战:构建一个简单命令行程序](basic-practice/intro.md)
- [基本功能](basic-practice/base-features.md)
- [Rust 高级进阶](advance/intro.md)
- [生命周期](advance/lifetime/intro.md)
- [深入生命周期](advance/lifetime/advance.md)

@ -0,0 +1,173 @@
# 实现基本功能
无论设计的再怎么花里胡哨或无与伦比,一个文件查找命令,首先得指定文件和待查找的字符串,它们需要用户从命令行给予输入,然后我们在程序内读取相应的输入。
## 接收命令行参数
国际惯例,先创建一个新的项目 `minigrep` ,该名字充分体现了我们的自信:就是不如 `grep`
```shell
cargo new minigrep
Created binary (application) `minigrep` project
$ cd minigrep
```
首先来思考下,如果要传入文件路径和待搜索的字符串,那这个命令该长啥样,我觉得大概率是这样:
```shell
cargo run -- searchstring example-filename.txt
```
`--` 告诉 `cargo` 后面的参数是给我们的程序使用的,而不是给 `cargo` 自己使用,例如 `--` 前的 `run` 就是给它用的。
接下来就是在程序中读取传入的参数,这个很简单,下面代码就可以:
```rust
// in main.rs
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
dbg!(args);
}
```
首先通过 `use` 引入标准库中的 `env` 包,然后 `env::args` 方法会读取并分析传入的命令行参数,最终通过 `collect` 方法输出一个集合类型 `Vector`
可能有同学疑惑,为啥不直接引入 `args` ,例如 `use std::env::args` ,这样就无需 `env::args` 来繁琐调用,直接`args.collect()` 即可。原因很简单,`args` 方法只会使用一次,啰嗦就啰嗦点吧,把相同的好名字让给 `let args..` 这位大哥不好吗?毕竟人家要出场多次的。
> ### 不可信的输入
> 所有的用户输入都不可信!不可信!不可信!
>
> 重要的话说三遍,我们的命令行程序也是,用户会输入什么你根本就不知道,例如他输入了一个非 Unicode 字符,你能阻止吗?显然不能,但是这种输入会直接让我们的程序崩溃!
>
> 原因是当传入的命令行参数包含非 Unicode 字符时, `std::env::args` 会直接崩溃,如果有这种特殊需求,建议大家使用 `std::env::args_os`,该方法产生的数组将包含 `OsString` 类型,而不是之前的 `String` 类型,前者对于非 Unicode 字符会有更好的处理。
>
> 至于为啥我们不用两个理由你信哪个1. 用户爱输入啥输入啥,反正崩溃了,他就知道自己错了 2. `args_os` 会引入额外的跨平台复杂性
`collect` 方法其实并不是`std::env`包提供的,而是迭代器自带的方法(`env::args()` 会返回一个迭代器),它会将迭代器消费后转换成我们想要的集合类型,关于迭代器和 `collect` 的具体介绍,请参加[这里](https://course.rs/advance/functional-programing/iterator.html)。
最后,代码中使用 `dbg!` 宏来输出读取到的数组内容,来看看长啥样:
```shell
$ cargo run
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 0.61s
Running `target/debug/minigrep`
[src/main.rs:5] args = [
"target/debug/minigrep",
]
```
```shell
$ cargo run -- needle haystack
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 1.57s
Running `target/debug/minigrep needle haystack`
[src/main.rs:5] args = [
"target/debug/minigrep",
"needle",
"haystack",
]
```
上面两个版本分别是无参数和两个参数其中无参数版本实际上也会读取到一个字符串仔细看是不是长得很像我们的程序名Bingo! `env::args` 读取到的参数中第一个就是程序的可执行路径名。
## 存储读取到的参数
在编程中,给予清晰合理的变量名是一项基本功,咱总不能到处都是 `args[1]` 、`args[2]` 这样的糟糕代码吧。
因此我们需要两个变量来存储文件路径和待搜索的字符串:
```rust
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let query = &args[1];
let file_path = &args[2];
println!("Searching for {}", query);
println!("In file {}", file_path);
}
```
很简单的代码,来运行下:
```shell
$ cargo run -- test sample.txt
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 0.0s
Running `target/debug/minigrep test sample.txt`
Searching for test
In file sample.txt
```
输出结果很清晰的说明了我们的目标:在文件 `sample.txt` 中搜索包含 `test` 字符串的内容。
事实上,就算作为一个简单的程序,它也太过于简单了,例如用户不提供任何参数怎么办?因此,错误处理显然是不可少的,但是在添加之前,先来看看如何读取文件内容。
## 文件读取
既然读取文件,那么首先我们需要创建一个文件并给予一些内容,来首诗歌如何?"我啥也不是,你呢?"
```text
I'm nobody! Who are you?
我啥也不是,你呢?
Are you nobody, too?
牛逼如你也是无名之辈吗?
Then there's a pair of us - don't tell!
那我们就是天生一对,嘘!别说话!
They'd banish us, you know.
你知道,我们不属于这里。
How dreary to be somebody!
因为这里属于没劲的大人物!
How public, like a frog
他们就像青蛙一样呱噪,
To tell your name the livelong day
成天将自己的大名
To an admiring bog!
传播到一整个无聊的沼泽!
```
在项目根目录创建 `poem.txt` 文件,并写入如上的优美诗歌(可能翻译的很烂,别打我,哈哈,事实上大家写入英文内容就够了)。
接下来修改 `main.rs` 来读取文件内容:
```rust
use std::env;
use std::fs;
fn main() {
// --省略之前的内容--
println!("In file {}", file_path);
let contents = fs::read_to_string(file_path)
.expect("Should have been able to read the file");
println!("With text:\n{contents}");
}
```
首先,通过 `use std::fs` 引入文件操作包,然后通过 `fs::read_to_string` 读取指定的文件内容,最后返回的 `contents``std::io::Result<String>` 类型。
运行下试试,这里无需输入第二个参数,因为我们还没有实现查询功能:
```shell
$ cargo run -- the poem.txt
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished dev [unoptimized + debuginfo] target(s) in 0.0s
Running `target/debug/minigrep the poem.txt`
Searching for the
In file poem.txt
With text:
I'm nobody! Who are you?
Are you nobody, too?
Then there's a pair of us - don't tell!
They'd banish us, you know.
How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!
```
完美,虽然代码还有很多瑕疵,例如所有内容都在 `main` 函数,这个不符合软件工程,没有错误处理,功能不完善等。不过没关系,万事开头难,好歹我们成功迈开了第一步。
好了,是时候重构赚波 KPI 了读者are you serious? 这就开始重构了?

@ -0,0 +1,9 @@
# 构建一个简单命令行程序
在前往更高的山峰前,我们应该驻足欣赏下身后的风景,虽然是半览众山小,但总比身在此山中无法窥全貌要强一丢丢。
在本章中,我们将一起构建一个命令行程序,目标是尽可能帮大家融会贯通之前的学到的知识。
linux 系统中的 `grep` 命令很强大,可以完成各种文件搜索任务,我们肯定做不了那么强大,但是假冒一个伪劣的版本还是可以的,它将从命令行参数中读取指定的文件名和字符串,然后在相应的文件中找到包含该字符串的内容,最终打印出来。
> 这里推荐一位大神写的知名 Rust 项目 [ripgrep](https://github.com/BurntSushi/ripgrep) ,绝对是 `grep` 真正的高替品,值得学习和使用
Loading…
Cancel
Save