Rust之构建命令行程序(六):信息写入

用Rust创建可靠的命令行程序 2020年是技术领域的元年,编程语言也迎来了翻天覆地的变化。Java、Python、JavaScript等多种语言在这片广袤的市场上掀起了一阵股风,各个方向都有不少开发者投入其中,同时还有一些企业将更加重视其技术能力。而在这些语言中,Rust 则名扬天下。Rust 是一种新兴的编程语言,拥有强大的性能、安全、并发特性和方便开发的特点。本文就带你用 Rust 创建一个可靠的命令行程序,看看它到底有多强悍。Rust 是一门注重速度、零开销、内存安全性和高效开发的系统级编程语言。 阅读详情

开发环境

  • Windows 11
  • Rust 1.77.2 

  • VS Code 1.88.1

项目工程

这次创建了新的工程minigrep.

将错误信息写入标准错误而不是标准输出

此时,我们正在使用宏println!将所有输出写入终端。在大多数终端中,有两种输出:一般信息的标准输出(stdout)和错误消息的标准错误(stderr)。这种区别使用户能够选择将程序的成功输出指向文件,但仍将错误信息打印到屏幕上。

 println!宏只能打印到标准输出,所以我们必须使用其他东西打印到标准错误。

检查错误写入的位置

首先,让我们观察minigrep打印的内容当前是如何写入标准输出的,包括我们希望改为写入标准错误的任何错误消息。我们将通过将标准输出流重定向到一个文件来实现这一点,同时故意造成一个错误。我们不会重定向标准错误流,因此发送到标准错误的任何内容都将继续显示在屏幕上。 

命令行程序应该将错误消息发送到标准错误流,这样即使我们将标准输出流重定向到文件,我们仍然可以在屏幕上看到错误消息。我们的程序目前表现不佳:我们将看到它将错误消息输出保存到一个文件中!

为了演示这种行为,我们将使用》和文件路径output.txt运行程序,我们希望将标准输出流重定向到该文件路径。我们不会传递任何参数,这会导致一个错误: 

$ cargo run > output.txt

> 语法告诉系统将标准输出的内容写入output.txt而不是屏幕。我们没有看到我们期望的打印到屏幕上的错误消息,所以这意味着它一定在文件中结束了。以下是output.txt包含的内容:

Problem parsing arguments: not enough arguments

 是的,我们的错误信息被打印到标准输出。将这样的错误信息打印到标准错误中会更有用,这样只有成功运行的数据才会出现在文件中。我们会改变的。

打印误差到标准误差

我们将使用示例12-24中的代码来改变错误信息的打印方式。由于我们在本章前面所做的重构,所有打印错误信息的代码都在一个函数main中。标准库提供了eprintln!打印到标准错误流的宏,所以让我们更改我们调用println!的两个位置!要打印错误,请使用eprintln!相反。

文件名:src/main.rs 

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

    let config = cfg::Config::build(&args).unwrap_or_else(|err| {
        eprintln!("Problem parsing arguments: {err}");
        process::exit(1);
    });

    if let Err(e) = cfg::run(config) {
        eprintln!("Application error: {e}");
        process::exit(1);
    }
}

 示例12-24:使用eprintln!将错误消息写入标准错误而不是标准输出

 现在让我们以同样的方式再次运行程序,没有任何参数,并且使用>重定向标准输出:

$ cargo run > output.txt
Problem parsing arguments: not enough arguments

现在我们在屏幕上看到错误,output.txt不包含任何内容,这是我们期望命令行程序的行为。

让我们使用不会导致错误但仍将标准输出重定向到文件的参数再次运行程序,如下所示: 

$ cargo run -- to poem.txt > output.txt

我们不会在终端上看到任何输出,output.txt将包含我们的结果:

文件名:output.txt 

Are you nobody, too?
How dreary to be somebody!

运行结果:

这表明我们现在使用标准输出进行成功输出,并根据需要使用标准错误进行错误输出。

全部代码

文件名: main.rs

use std::env;
use std::process;
use minigrep::cfg;
 

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

    let config = cfg::Config::build(&args).unwrap_or_else(|err| {
        eprintln!("Problem parsing arguments: {err}");
        process::exit(1);
    });

    if let Err(e) = cfg::run(config) {
        eprintln!("Application error: {e}");
        process::exit(1);
    }
}

文件名:lib.rs

pub mod cfg {
    use std::error::Error;
    use std::env;
    use std::fs;

    pub struct Config {
        pub query: String,
        pub file_path: String,
        pub ignore_case: bool,
    }
    
    impl Config {
        pub fn build(args: &[String]) -> Result<Config, &'static str> {
            if args.len() < 3 {
                return Err("not enough arguments");
            }
    
            let query = args[1].clone();
            let file_path = args[2].clone();
    
            let ignore_case = env::var("IGNORE_CASE").is_ok();
    
            Ok(Config {
                query,
                file_path,
                ignore_case,
            })
        }
    }


#[cfg(test)]
mod tests {
    use super::*;
 
    #[test]
    fn case_sensitive() {
        let query = "duct";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";
 
        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
    }
 
    #[test]
    fn case_insensitive() {
        let query = "rUsT";
        let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";
 
        assert_eq!(
            vec!["Rust:", "Trust me."],
            search_case_insensitive(query, contents)
        );
    }
}

    pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
        let mut results = Vec::new();

        for line in contents.lines() {
            if line.contains(query) {
                results.push(line);
            }
        }

        results
    }


    pub fn search_case_insensitive<'a>(
        query: &str,
        contents: &'a str,
    ) -> Vec<&'a str> {
        let query = query.to_lowercase();
        let mut results = Vec::new();
    
        for line in contents.lines() {
            if line.to_lowercase().contains(&query) {
                results.push(line);
            }
        }
    
        results
    }

    pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
        let contents = fs::read_to_string(config.file_path)?;

        let results = if config.ignore_case {
            search_case_insensitive(&config.query, &contents)
        } else {
            search(&config.query, &contents)
        };

        for line in results {
            println!("{line}");
        }

        Ok(())
    }
}

总结 

本章概述了到目前为止您所学的一些主要概念,并介绍了如何在Rust中执行常见的I/O操作。通过使用命令行参数、文件、环境变量和eprintln!宏处理打印错误,现在您可以准备编写命令行应用程序了。结合前几章的概念,您的代码将组织良好,在适当的数据结构中有效地存储数据,很好地处理错误,并得到良好的测试。 

接下来,我们将探索一些受函数式语言影响的Rust特性:闭包和迭代器。 

本章重点

  • 区分标准错误输出println!
  • 标准误差宏eprintln!及使用方法
  • 如何将错误信息写入文件

 

rust-embed:Rust宏,它在发行时的编译时将文件加载到rust二进制文件中,并在开发期间从fs加载文件 立即下载

相关推荐

解决相同型号USB设备在Windows系统中重复枚举问题

本文详细解析了Windows系统中相同型号USB设备重复枚举问题的成因及解决方案。通过修改注册表、使用PsTools工具清除设备记录以及编程实现自动化处理等方法,有效解决设备识别混乱问题,特别适用于自动化测试和工业设备管理场景。

weixin_42561040的博客 565

[rust-007]rust的println!函数的各种用法

println!是宏。 println!的第一个参数,是格式化字符串,其他参数要放在这个字符串里打印出来。 第一种情况,格式化字符串,是纯粹的字符串: println!("Hello World"); // 输出结果如下: // Hello World 第二种情况,格式化字符串里有替换占位符"{}",从第二个参数开始,各参数逐个替换格式化字符串的占位符: println!("{} World {} {}", "Hello", true, 42); //输出结果: //Hello World t

大数据和机器学习研究 9140

大模型运维实战:从GPU选型到推理服务稳定落地

大模型服务进入生产环境后,运维对象从传统进程转向GPU显存、KV Cache、推理吞吐等动态资源。显存利用率、首token延迟、并发队列深度等指标,直接影响模型服务的质量与成本。理解推理框架的内存分配机制、掌握监控告警与自动化扩容手段,是保障服务稳定的关键。这类能力广泛应用于企业私有化部署、智能客服、知识库问答等场景,帮助运维团队从被动救火转向主动容量规划与性能调优。本文围绕大模型运维的完整路线,梳理从硬件选型、推理部署到监控排障的实战经验,并算清一份真实项目的长期成本账。

weixin_29054245的博客 238

rust println!格式化输出

格式 格式说明 样例代码 输出效果 {} 将任何实现了std::fmt::Display trait的类型格式化为字符串输出 println!("Hello, {}!", "world"); Hello, world! {:?} 以调试模式打印任何实现了std::fmt::Debug trait的类型 println!("Debug: {:?}", [1, 2, 3]); Debug: [1, 2, 3] {:#?} 在调试模式下将

小码农的博客 1449

ABAP BDC 处理踩坑笔记:把错误挡在录制之前,把稳定做进每一行 BDCDATA

本文总结了SAP BDC(批输入)开发中的常见问题和解决方案。重点包括:1)理解BDCDATA本质是模拟人工操作而非直接操作数据库;2)数据格式化技巧,如金额、日期、物料号等字段的标准化处理;3)通过CTU_PARAMS参数控制执行环境,确保屏幕尺寸和更新模式一致;4)不要依赖sy-subrc,应通过解析MESSTAB消息判断业务是否成功;5)录制阶段的常见陷阱及解决方法;6)疑难问题的系统性解决思路;7)建议建立完善的错误处理机制,包括日志记录和重跑功能。文章提供了实用的ABAP代码示例和配置建议,帮助开

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 265

JS逆向-B站评论数据w_rid参数和wts参数

本文是该专栏的第69篇,后面会持续分享python爬虫干货知识。本文以B站的评论数据为例,通过JS逆向获取其中的w_rid参数以及wts参数生成规律。具体的“逆向”思路逻辑,笔者将会详细介绍每个步骤,并且将在正文结合“完整代码”来详细介绍。废话不多说,具体细节部分以及详细思路逻辑,笔者将在正文结合完整代码进行详细说明。(附带完整代码)

Python进阶专栏《爬虫实战进阶》,《Pyppeteer从入门到精通》原创作者 2048

Rust eprintln

可以将错误消息输出到标准错误流中,并使其在终端上呈现出不同的颜色,以便用户能够更方便地区分错误消息和正常输出。同时,由于标准输出和标准错误是不同的输出流,因此将错误消息输出到标准错误流中不会混淆程序的正常输出。标准错误流和标准输出流是程序与外界交互的两个基本接口。标准错误流一般用于输出程序运行过程中的错误消息,而标准输出流则用于输出正常的结果信息。代表 error,表示将消息输出到标准错误流中,而不是标准输出流。是一个 Rust 的宏,用于将错误消息输出到标准错误流中。宏,用于将消息打印到标准输出流中。

醒途 413

pnpm 卸载教程:彻底移除 pnpm 及全局缓存的方法

卸载 pnpm = 全局包 → CLI → 环境变量 → store 缓存只要按这个顺序来,基本不会留下任何坑。如果你后面还打算重新安装 pnpm / 切换 npm、yarn / 多包管理器共存,这些细节反而更重要,环境一旦干净,后续问题会少很多。

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 928

Rust语言进阶之println!用法实例(八十七)

本篇目的:Rust语言进阶之println!用法实例基本概念println!是Rust语言中的一个宏,用于将输出打印到标准输出(通常是终端)。它属于Rust的标准库中的宏之一,广泛用于调试、日志记录和交互式程序中。println!的作用是格式化并打印一段字符串,同时在字符串的末尾自动添加换行符。作为宏,println!在编译时进行展开,因此它比常规函数调用具有更高的灵活性和效率。格式化输出println!不仅仅是输出一个简单的字符串,它还支持格式化输出。

Android系统攻城狮 1309

4D毫米波雷达:高度信息与微多普勒效应

4D毫米波雷达:高度信息与微多普勒效应​ ,人工智能,计算机视觉,大模型,AI,本文深入解析4D毫米波雷达的高度信息获取与微多普勒效应。4D毫米波雷达通过MIMO技术、垂直天线布局及相位差计算等获取高度信息,相较传统雷达更精准。微多普勒效应由目标微运动引发,可用于目标识别、分类和运动监测。文中结合自动驾驶、安防监控案例,分析技术挑战与未来趋势,如信号干扰、数据处理复杂,未来将向高精度、与其他传感器融合发展,展现出在多领域的巨大应用潜力。

不踩坑,不知道坑有多深;不总结,踩过的坑白踩。 9万+

34、Text2KGBench与GeoQuestions1089:知识图谱生成与地理空间问答的新进展

本文介绍了Text2KGBench和GeoQuestions1089在知识图谱生成与地理空间问答领域的最新进展。Text2KGBench提供了一个开放且可复现的基准,用于从文本生成知识图谱,并结合多个LLM模型和评估工具推动研究。GeoQuestions1089是目前最大的地理空间问答数据集,与GeoQA2系统结合,推动了地理空间问答系统的发展。文章还探讨了技术挑战和未来研究方向,包括多模态知识图谱生成和深度学习优化的地理空间问答系统。

css33的专栏 114

rust学习-rust中的格式化打印

可以通过实现 std::fmt::Display 和 std::fmt::Debug trait 来为自定义类型定义格式化行为x: i32,y: i32,write!println!}", p);Display trait 用于定义类型的字符串表示,适用于用户友好的输出Debug trait 用于调试目的,提供详细的内部结构信息。

weixin_42333247的博客 1543

FM1288回声消除芯片调试方案 - 综述

专栏文章概览

优快云mianfeixiazai001的博客 1714

Rust之构建命令行程序(一):接受命令行参数

Rust之构建命令行程序,接受命令行参数

wing的专栏 2000

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

Rust学习-构建命令行程序

Rust 的运行速度、安全性、单二进制文件输出和跨平台支持使其成为创建命令行程序的绝佳选择本文以实现一个minigrep为例,展开对之前学习的回归。

wangkai6666的博客 1458

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

【跟小嘉学 Rust 编程】十二、构建一个命令行程序

本章是一个目前所学的很多技能的应用,以及标准库的探索,我们讲构建一个命令行程序工具来练习现在已经学习过的一些Rust的技能。我们将构建自己的版本的命令行工具:grep(Globally search a Regular Expression and print)。主要教材参考 《The Rust Programming Language》

小嘉丶学长的博客 2987

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

Rust之构建命令行程序(三):重构改进模块化和错误处理

Rust之构建命令行程序(三):重构改进模块化和错误处理

wing的专栏 1652

【Rust】Rust学习 第十二章一个 I/O 项目:构建一个命令行程序

本章既是一个目前所学的很多技能的概括,也是一个更多标准库功能的探索。我们将构建一个与文件和命令行输入/输出交互的命令行工具来练习现在一些你已经掌握的 Rust 技能。grep。grep 是 “egularrint.” 的首字母缩写。grep最简单的使用场景是在特定文件中搜索指定字符串。为此,grep获取一个文件名和一个字符串作为参数,接着读取文件并找到其中包含字符串参数的行,然后打印出这些行。在这个过程中,我们会展示如何让我们的命令行工具利用很多命令行工具中用到的终端功能。

StudyWinter的博客 2065

11、Rust程序设计语言——构建一个命令行程序

Rust学习笔记

qq_51180928的博客 499

Rust学习记录--C12 实例:写一个命令行程序

要求:在一个文件中搜索字符串返回对应的行的列表。

weixin_44900096的博客 905

Rust从0到1实战系列篇 如何一个 I/O 项目:构建一个命令行程序

本章通过构建一个简化版grep命令行程序来练习Rust的关键功能。项目名为minigrep,能够接受文件路径和搜索字符串两个参数,读取文件内容并打印匹配行。首先使用std::env::args()获取命令行参数,将查询字符串和文件路径存入变量。然后通过std::fs::read_to_string读取文件内容并打印。虽然当前功能简单,但后续会处理错误并分离功能模块。这个项目展示了Rust在构建命令行工具方面的优势,并为理解更复杂的类似项目(如ripgrep)打下基础。完整代码可从示例12-1至12-4中查看

m0_68635815的博客 795

12-输入/输出项目构建命令行程序

测试驱动开发(TDD, test-driven development)是一种软件开发方法,即在实际代码执行之前编写测试。它是一种用于确保所开发的软件满足指定要求的技术,并有助于在开发周期的早期捕捉错误。该过程遵循一个短暂的重复周期,Red-Green-Refactor,即 "红-绿-重构":①. 红色编写一个定义函数或函数改进的测试,该测试最初失败的原因应该是函数不存在或不按预期执行。②. 绿色:编写通过测试所需的最少代码量。这一阶段的主要目标是在遵守指定要求的前提下尽快通过测试。

优快云爱好者的独到见解 1322

Rust 从零到精通:构建一个专业级命令行工具 greprs

欢迎来到 Rust 的世界!Rust 是一门现代化的系统编程语言,专注于性能、内存安全和并发性。它没有垃圾回收器,却能通过强大的编译器和所有权系统在编译时就保证内存安全。这使得 Rust 成为构建高性能、高可靠性软件的绝佳选择,从底层的操作系统、游戏引擎,到高性能 Web 后端和云原生应用,无处不见其身影。本教程将不仅仅是“Hello, world!我们的目标是带您走过一段完整的旅程:从在 Linux 环境下安装 Rust,到亲手构建一个功能完整、结构清晰、经过专业测试的命令行文本搜索工具——

2301_80863610的博客 1万+

Rust入门(八):构建一个命令行程序

Rust入门(八):构建一个命令行测试程序的例子

weixin_46463785的博客 1361

青少年编程与数学 02-019 Rust 编程基础 18课题、命令行程序

使用 Rust 编写一个处理 I/O 的命令行程序,以及涉及的关键知识点的讲解。

明月看潮生的博客 1667

Rust从入门到暴走(19)--- 如何写一个组织优雅的Rust命令行程序

main.rs 是命令行程序的入口,负责获取args以及核心业务逻辑的处理函数的调用。根据调用函数的返回结果决定程序是否正常运行下去或者终止。lib.rs 是核心业务逻辑的处理函数的定义,里面包括args的解析和验证函数,以及包括查询指定文件中是否包含指定关键词的函数。

Luca的博客, 网络安全Hacker,人工智能AIer,区块链Web3er,全栈软件开发er 747
上一篇: Rust之构建命令行程序(五):环境变量
下一篇: Rust之函数式语言特性:迭代器和闭包(一):概述
枫★曦
博客等级 码龄19年 251粉丝 153原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值