惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

SecWiki News
SecWiki News
I
InfoQ
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
博客园_首页
罗磊的独立博客
V
V2EX
李成银的技术随笔
大猫的无限游戏
大猫的无限游戏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
True Tiger Recordings
Vercel News
Vercel News
Cyberwarzone
Cyberwarzone
Cisco Talos Blog
Cisco Talos Blog
F
Fox-IT International blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
M
Microsoft Research Blog - Microsoft Research
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
The Register - Security
The Register - Security
G
Google Developers Blog
The Hacker News
The Hacker News
Malwarebytes
Malwarebytes
S
Securelist
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
博客园 - 叶小钗
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
宝玉的分享
宝玉的分享
博客园 - 聂微东
T
Threatpost
博客园 - 【当耐特】
D
Docker
P
Privacy & Cybersecurity Law Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
G
GRAHAM CLULEY
V
Visual Studio Blog
C
Cisco Blogs
IT之家
IT之家
S
Security Archives - TechRepublic
Latest news
Latest news
阮一峰的网络日志
阮一峰的网络日志

硅上观道・我的个人技术博客

函数式心法 (3):巧用柯里化和部分应用 函数式心法 (2):理解运用高阶函数 打造 NixOS 开发环境 (2):NixOS学习与配置入门指南 打造 NixOS 开发工作流 (1):为什么选择 NixOS Haskell 心法 (1):初识 - 为何学、学什么和怎么学 Clojure 笔记 (2):基本语法结构和 REPL 驱动编程 Clojure 笔记 (1):语言生态介绍和环境配置 前端笔记 (1):ES 标准和 ES6+ 变量定义方法 当我们发现毕生追求的知识在AI面前毫无价值时,如何重建存在的意义? 5 月 28 日 DeepSeek R1 模型完成小版本试升级并开源,具体有哪些提升? 信息闭塞和被信息洪流冲刷,哪个弊端更大?谈错误信息的危害性 Rust CLI 实战:手搓微型 grep (4) 交互式处理错误文件名问题 Rust CLI 实战:手搓微型 grep (3) 更优雅的错误处理 Rust CLI 实战:手搓微型 grep (1) 新建项目 学计算机有什么好的获取学习资料的方法?掌握高效的资料收集技巧 有哪些看起来很高端的技术其实原理很暴力很初级?浅析CDN内容分发网络原理 学习编程可以为自己带来什么?- 理论与实践的紧密结合 长文 - 大一技术成长复盘:课程、竞赛与开源之旅 推荐一个IDE中的AI工具 - CodeGeex插件和AI辅助编程 关于本站 - 一篇旧文章,我对本站内容更新计划的最初想法 读书笔记:读庄子《齐物论》,探寻真正的自我 读书笔记 - 单向度的人:核心思想,内容解析以及我的感悟 一篇时评写作练习:矛盾与救赎 - 关于行善
Rust CLI 实战:手搓微型 grep (2) 实现软件第一版
硅上观道 · 2025-05-17 · via 硅上观道・我的个人技术博客
技术教程

上篇文章讲完了项目的目标和准备流程,本文目标是实现`grrs`命令行软件的初始版本。我们将实现基本的命令行传参和文件内容搜索功能。

上篇文章讲完了项目的目标和准备流程,本文目标是实现grrs命令行软件的初始版本。

如果你还没有读过上文,欢迎查看

也欢迎关注这个系列专栏,我将更新更多精彩内容。


1. 安装clap

首先,我们在处理业务逻辑前,首先要解决命令行参数传入的问题。

换言之,我们最先要做的,就是正确接收用户传入的搜索模式和搜索文件参数。

rust生态中,一个名为clap的库基本已成为实现命令行软件的事实标准。这个库集成了大量命令行软件必备的功能,可以帮助我们快速搭建起一个命令行应用原型。

首先,为我们的项目安装clap库:

cargo add clap --features derive

注意在安装过程中需要传入参数--features derive,其原因是clap需要使用派生宏来方便开发者开发。

如果你目前不知道什么是宏,没关系,这不影响我们的代码实践,完全可以等到对rust更熟悉之后再研究宏的知识。

你只需要注意,在安装clap时同时安装了派生宏特性即可。当你运行完上述命令后,请检查项目根目录下的Cargo.toml文件。

其中应该有:

clap = { version = "4.5.38", features = ["derive"] }

当然,可能到你看到这个教程时clap已经有了新版本。但是总之,当你看到类似上面的内容时,你就成功为你的项目引入了第一个依赖了!


2. 命令行参数传入

现在,我们开始使用clap来完成命令行参数传入的工作。

请你将./src/main.rs修改成如下内容:

use clap::Parser;

/// Search for a pattern in a file and display the lines that contain it.
#[derive(Parser)]
struct Cli {
    /// The pattern to look for
    pattern: String,
    /// The path to the file to read
    path: std::path::PathBuf,
}

fn main() {
    let args = Cli::parse();

    println!("pattern: {:?}, path: {:?}", args.pattern, args.path)
}

这些代码是什么意思呢?

use clap::Parser;

这第一行的use就是引入了clap库的中的Parser。这个Parser很快就在下文用到:

#[derive(Parser)]
struct Cli {
    /// The pattern to look for
    pattern: String,
    /// The path to the file to read
    path: std::path::PathBuf,
}

这里,我们构造了一个名为Cli的结构体,并对这个结构体使用了Parser派生宏。这个派生宏的作用,很快我们就会看到。

结构体中有两个字段,patternpath

pattern保存的是用户需要搜索的样式,使用String结构;而对于查询文件路径,我们使用了标准库中的std::path::PathBuf来保存,你可以理解为这是一种特殊的字符串。

这里的PathBuf,在实现上可以兼容不同操作系统的路径名风格(正斜杠、反斜杠),同时还可以和其他标准库 API 进行集成,总之就是很好用。

最终,我们在main函数中有:

    let args = Cli::parse();

你可能会问,这就完了?

对,这就结束了,就是这么简单。我们所做的只是定义了一个结构体,剩下的由clap库中的Parser派生宏帮我们办完了。

现在,我们一起看看效果:

首先,我们尝试不传入任何参数,直接运行项目。

cargo run

结果是:

引入clap后直接运行cargo run的结果截图

你看这个错误提示,是不是有模有样?注意,这些都是clap直接替我们生成的哦。

如果我们尝试传入一下参数

cargo run some-pattern some-path

结果是:

传入参数后的结果截图

果然,运行结果 print 出了一行结果,这和我们刚刚代码中写的println!("pattern: {:?}, path: {:?}", args.pattern, args.path)预期相符。

到这里,我们就完成了传入命令行参数的准备工作。

其实,clap还有很多高级功能可以探索,可以帮助命令行软件实现丰富的功能。如果大家需要,欢迎评论区告诉我,我可能后续会在专栏更新更多clap库的进阶功能。


3. 实现grrs的第一个可用版本

现在将main.rs修改成如下状态:

use clap::Parser;

/// Search for a pattern in a file and display the lines that contain it.
#[derive(Parser)]
struct Cli {
    /// The pattern to look for
    pattern: String,
    /// The path to the file to read
    path: std::path::PathBuf,
}

fn main() {
    let args = Cli::parse();
    let content = std::fs::read_to_string(&args.path).expect("could not read file");

    for line in content.lines() {
        if line.contains(&args.pattern) {
            println!("{}", line);
        }
    }
}

很简单吧?我们只是稍微修改了main函数中的内容,加入了一些简单的业务逻辑。现在,让我们一起看看这段简单的代码效果如何。

此时,我们输入如下命令:

cargo run -- content ./src/main.rs

命令中的--用于隔开cargo自己的参数和我们创建的命令行软件的参数。

你应当看到我在第一篇文章开头,我们展示的结果:

搜索成功命令行界面示意

如果我们尝试传入一个错误的参数,比如传入一个不存在的文件名:

cargo run -- content false-file-path

此时你应该看到:

输入错误文件名后的截图

说实话,这不是一个很完美的实现,错误处理这块儿还是有瑕疵的。但是起码现在这个代码看起来能用,还要什么自行车!

你可能还是很好奇,这段代码是如何实现这样的功能的,我们在main函数中添加的代码细节具体有何作用,为什么我说现在的错误处理不够好。

受篇幅限制,我无法将所有细节全部放在这篇文章中。这些问题,我们下回再详细解释吧!


希望这篇博客能帮助到你,也欢迎关注交流。愿与君共勉!

点击阅读下一篇文章