【一起学Rust · 项目实战】命令行IO项目minigrep——接收命令行参数与读取文件内容

简介: 【一起学Rust · 项目实战】命令行IO项目minigrep——接收命令行参数与读取文件内容



前言

本系列文章章将构建一个与文件和命令行输入/输出交互的命令行工具来练习现在一些你已经掌握的 Rust 技能。

Rust 的运行速度、安全性、单二进制文件输出和跨平台支持使其成为创建命令行程序的绝佳选择,因此我们的项目将创建一个我们自己版本的经典命令行工具:grepgrep 是 “Globally search a Regular Expression and Print.” 的首字母缩写。grep 最简单的使用场景是在特定文件中搜索指定字符串。为此,grep 获取一个文件名和一个字符串作为参数,接着读取文件并找到其中包含字符串参数的行,然后打印出这些行。


一、任务目的

使用rust制作一个简化版本的grep工具,我们希望在使用我们的程序的时候是下面这样的

minigrep searchstring example-filename.txt

其中minigrep是程序名字,searchstring 是要搜索的字符串,example-filename.txt是被搜索的文件名。在开发过程中,不能每次编译都要去build目录去找这个程序然后运行,我们一般采用的运行方式是cargo run,因此我们采用以下方式来运行上面这条命令,即使用cargo run‘来代替原来的程序名字,按照运行规则,会自动把后面的参数传递给rust

cargo run searchstring example-filename.txt

传入一个要搜索的字符串searchstring ,和一个文件名example-filename.txt,并且接收这两个参数。现在crates.io已经有很多现成的库,可以很简单的实现获取程序的参数,可以直接调用,但是我们这里正在学习这部分内容,因此这里我们自己手动实现。在后续的开发过程中,我们在优化程序时,也会将我们的代码替换成creats.io中的库来实现,用来提高程序的效率。

本期我们要实现的是读取传入的这两个参数,并且读取传入文件名的文件内容,输出到屏幕上。

我们需要实现的效果如下,在poem.txt中写入文件内容,我们写入一首诗

中山孺子妾,特以色见珍。虽然不如延年妹,亦是当时绝世人。
桃李出深井,花艳惊上春。一贵复一贱,关天岂由身。
芙蓉老秋霜,团扇羞网尘。戚姬髡发入舂市,万古共悲辛。

运行以下命令

cargo run abc poem.txt

然后屏幕会输出在poem.txt中写入的文件内容

中山孺子妾,特以色见珍。虽然不如延年妹,亦是当时绝世人。
桃李出深井,花艳惊上春。一贵复一贱,关天岂由身。
芙蓉老秋霜,团扇羞网尘。戚姬髡发入舂市,万古共悲辛。

这样就意味着我们取到了传递的命令行参数,并且通过io来读取到了文件的内容。


二、创建新项目

和以前一样,我们找个地方,打开 终端 运行以下新建项目的命令,

cargo new minigrep

然后使用vscode打开minigrep文件夹,里面就是cargo为我们创建好的基础项目。

三、读取参数值

Rust 标准库提供了一个函数std::env::args,返回一个传递给程序的命令行参数的 迭代器迭代器我们在后面会详细介绍,现在你只需记住两件事:

  • 迭代器生成一系列的值
  • 可以在迭代器上调用 collect 方法将其转换为一个集合

我们将传递给程序的命令行参数读取并收集到 vector 中,先导入标准库

use std::env;

然后在 main 中接收该参数,并且调用 collect 方法接收参数,并且存入变量 args

let args: Vec<String> = env::args().collect();

完整代码如下

use std::env;
fn main() {
    let args: Vec<String> = env::args().collect();
    println!("{:#?}", args);
}

接下来我们使用 cargo run 传入参数运行程序看看效果,

cargo run needle haystack

运行效果如下图所示,可以看到,接收到的参数一共有三个,

第一个参数是:二进制文件的名称,也是位置。后面的参数就是我们输入的参数,我们传入的参数一共有两个,但是这里给出了三个参数,这是没有问题的,传入的参数第一个是文件名,接下来才我们传入的参数


四、将参数保存至变量

由于我们将参数存入了 Vector 的变量 args 中,并且我们已知参数的数量和含义是什么,利用以前学过的知识,取到这两个参数

let query = &args[1];
    let filename = &args[2];

完整代码

use std::env;
fn main() {
    let args: Vec<String> = env::args().collect();
    println!("{:#?}", args);
    let query = &args[1];
    let filename = &args[2];
    println!("Searching for {}", query);
    println!("In file {}", filename);
}

现在运行试试效果,如下图示

五、读取文件

1. 新建文本文件

在项目目录新建文本文件,如下图所示,实际情况任意,本次仅作为例子,

文件内容如下:

中山孺子妾,特以色见珍。虽然不如延年妹,亦是当时绝世人。
桃李出深井,花艳惊上春。一贵复一贱,关天岂由身。
芙蓉老秋霜,团扇羞网尘。戚姬髡发入舂市,万古共悲辛。

2. 导入文件操作函数

文件操作是在标准库中的std::fs下提供的,使用之前我们需要将这些函数导入进来,代码如下:

use std::fs;

3. 读取文件内容

fs::read_to_string 是一个读取文件内容的函数,需要接收 filename ,返回 Result<String>,其中包含文件内容。

在上一小节的代码基础之上,我们进行修改。上节代码

use std::env;
fn main() {
    let args: Vec<String> = env::args().collect();
    println!("{:#?}", args);
    let query = &args[1];
    let filename = &args[2];
    println!("搜索 {}", query);
    println!("在文件 {}", filename);
}

我们已经从命令行参数中读取到了 filename,因此我们只需要将这个参数传递给 fs::read_to_string 就得以得到我们要的文件内容,拿到文件内容以后输出文件内容。

现在我们加入以下代码

let contents = fs::read_to_string(filename)
        .expect("读取文件出错");
    println!("读取到的文本为:\n{}", contents);

完整代码:

use std::env;
use std::fs;
fn main() {
    let args: Vec<String> = env::args().collect();
    println!("{:#?}", args);
    let query = &args[1];
    let filename = &args[2];
    println!("搜索 {}", query);
    println!("在文件 {}", filename);
    let contents = fs::read_to_string(filename)
        .expect("读取文件出错");
    println!("读取到的文本为:\n{}", contents);
}

4. 运行效果

现在我们在终端执行命令,

cargo run the poem.txt

效果如下,


总结

以上就是本节的所有内容。

通过本节学到了:

  • 如何接收命令行参数
  • 将命令行参数拆解开来

了解了怎么利用 Rust 来读取一个文件的内容,即利用标准库fs来读取文件内容,仅需传入一个 filename 即可读出文件的内容。


作业

正常来说是可以读取到两个参数的,但是怎么确保我们真的能够读到两个参数,而且程序应该给予一定的提示,需要做一些错误的处理,请你对这里的问题进行处理。

你已学会了读取文件内容的操作。现在你可以考虑以下内容:

  • 如何按行来读取文件内容
  • 如何删除一个文件
  • 如何新建文件
  • 如何给文件追加内容
  • 如何获取文件的详细信息(可选)
目录
相关文章
|
2月前
|
Rust 开发者 索引
30天拿下Rust之命令行参数
30天拿下Rust之命令行参数
50 0
|
6天前
|
存储 弹性计算 固态存储
阿里云服务器ESSD Entry系统盘测评IOPS、IO读写和时延性能参数
ESSD Entry云盘是阿里云推出的新一代云盘,具备高IOPS、低延迟和企业级数据保护能力。适用于开发与测试场景,支持按量付费和包年包月计费模式。99元和199元的ECS经济型e实例和通用算力型u1实例均采用ESSD Entry系统盘,性价比高。详细性能参数和价格请参考阿里云官方页面。
36 0
|
1月前
|
搜索推荐 索引
【文件IO】实现:查找文件并删除、文件复制、递归遍历目录查找文件
【文件IO】实现:查找文件并删除、文件复制、递归遍历目录查找文件
34 2
|
1月前
|
编解码 Java 程序员
【文件IO】文件内容操作
【文件IO】文件内容操作
42 2
|
1月前
|
存储 Java API
【文件IO】文件系统操作
【文件IO】文件系统操作
40 1
|
2月前
|
Java 大数据 API
Java 流(Stream)、文件(File)和IO的区别
Java中的流(Stream)、文件(File)和输入/输出(I/O)是处理数据的关键概念。`File`类用于基本文件操作,如创建、删除和检查文件;流则提供了数据读写的抽象机制,适用于文件、内存和网络等多种数据源;I/O涵盖更广泛的输入输出操作,包括文件I/O、网络通信等,并支持异常处理和缓冲等功能。实际开发中,这三者常结合使用,以实现高效的数据处理。例如,`File`用于管理文件路径,`Stream`用于读写数据,I/O则处理复杂的输入输出需求。
|
1月前
|
存储 Java 程序员
【Java】文件IO
【Java】文件IO
35 0
|
2月前
|
Linux C语言
C语言 文件IO (系统调用)
本文介绍了Linux系统调用中的文件I/O操作,包括文件描述符、`open`、`read`、`write`、`lseek`、`close`、`dup`、`dup2`等函数,以及如何获取文件属性信息(`stat`)、用户信息(`getpwuid`)和组信息(`getgrgid`)。此外还介绍了目录操作函数如`opendir`、`readdir`、`rewinddir`和`closedir`,并提供了相关示例代码。系统调用直接与内核交互,没有缓冲机制,效率相对较低,但实时性更高。
|
3月前
|
存储 监控 Linux
性能分析之从 IO 高定位到具体文件
【8月更文挑战第21天】性能分析之从 IO 高定位到具体文件
40 0
性能分析之从 IO 高定位到具体文件
|
3月前
IO流拷贝文件的几种方式
IO流拷贝文件的几种方式
36 1