用Rust编写CLI工具:如何在一晚内创建你的第一个工具

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

为什么Rust是CLI工具的理想选择?

\n\n

你是否曾想过,为什么所有现代DevOps工具——从bat到ripgrep和fd——都是用Rust编写的?这不仅仅是因为潮流。用Rust编写的CLI工具速度极快、安全,而且最棒的是,它们可以编译成一个没有依赖的小型二进制文件。再也不需要“你安装Java了吗?”或者“请等待Node.js加载node_modules”。只需下载,即可使用。

\n\n

在本文中,我们将从零开始用Rust创建一个CLI工具。这不会是一个抽象的“Hello, World!”,而是一个真正有用的工具——一个日志分析器,用于查找错误、统计错误数量,并以漂亮的彩色格式输出结果。没错,我们将在今晚完成它。

\n\n

我们需要什么?

\n\n

开始所需的最少条件:

\n
    \n
  • Rust(如果尚未安装,请通过rustup安装)
  • \n
  • 任意代码编辑器(带有rust-analyzer插件的VS Code是理想选择)
  • \n
  • 几小时的空闲时间
  • \n
\n\n

如果你曾用Python或Go编写过代码,那么Rust可能会让你觉得不寻常,但很有逻辑。最重要的是不要害怕编译器。它并不邪恶,它只是很贴心。

\n\n

第1步:创建项目

\n\n

打开终端并输入:

\n
cargo new log-scanner\ncd log-scanner
\n\n

Cargo 是我们的瑞士军刀。它创建了包含Cargo.toml(依赖文件)和src/main.rs(入口点)的基本结构。现在就可以运行cargo run并看到“Hello, world!”。但我们要更进一步。

\n\n

打开Cargo.toml并添加依赖项,这些依赖项将把我们的代码变成一个真正的Rust CLI工具:

\n
[dependencies]\nclap = { version = "4.5", features = ["derive"] }\ncolored = "2.1"\nanyhow = "1.0"
\n\n
    \n
  • clap — 命令行参数解析(必不可少)
  • \n
  • colored — 终端彩色输出
  • \n
  • anyhow — 便捷的错误处理
  • \n
\n\n

第2步:定义参数结构

\n\n

clap最酷的地方在于它的derive宏。你只需描述结构,魔法就会自动发生。如下所示:

\n\n
use clap::Parser;\n\n#[derive(Parser)]\n#[command(name = "log-scanner")]\n#[command(about = "扫描日志文件并查找错误")]\nstruct Cli {\n    /// 日志文件的路径\n    file: String,\n\n    /// 要搜索的字符串(默认为"ERROR")\n    #[arg(short, long, default_value = "ERROR")]\n    pattern: String,\n\n    /// 仅显示找到的行数\n    #[arg(short, long)]\n    count: bool,\n}
\n\n

注意:三个斜杠///的注释会自动成为--help中的提示。运行cargo run -- --help——你会看到一个整洁生成的帮助信息。这就是为什么用Rust创建CLI如此令人愉快的原因之一:文档会自动生成。

\n\n

第3步:编写搜索逻辑

\n\n

现在是最有趣的部分——读取文件并查找匹配项。Rust以其安全的文件和内存操作而闻名,但在实践中,它只是很方便:

\n\n
use std::fs::File;\nuse std::io::{BufRead, BufReader};\nuse colored::*;\nuse anyhow::Result;\n\nfn scan_log(file_path: &str, pattern: &str, count_only: bool) -> Result<()> {\n    let file = File::open(file_path)?;\n    let reader = BufReader::new(file);\n    let mut total_matches = 0;\n\n    for (line_num, line) in reader.lines().enumerate() {\n        let line = line?;\n        if line.contains(pattern) {\n            total_matches += 1;\n            if !count_only {\n                println!(\n                    "{}:{} {}",\n                    format!("{:>4}", line_num + 1).blue(),\n                    patt                

博客

书籍推荐