原创

用 Rust 写命令行工具:高效、可靠且快乐编程

为什么选择 Rust?

Rust 不是“又一个编程语言”。它带来了一些独特的东西:在保证内存安全的同时,不损失性能。对于命令行工具来说,这意味着你写的程序既快速又稳定,不会莫名其妙地崩溃。

当你的工具需要处理大量数据、并发任务或长时间运行时,Rust 的优势就显现出来了。它像 C++ 一样快,但排除了整类内存相关的错误。

环境准备

安装 Rust 很简单:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

安装完成后,创建一个新项目:

cargo new cli-tool
cd cli-tool

看看生成的项目结构:

cli-tool/
├── Cargo.toml
└── src/
    └── main.rs

第一个实用工具:文件行数统计

我们来写个实际有用的东西——统计文件行数的工具,比 wc -l 更好用一点。

1. 解析命令行参数

编辑 Cargo.toml,添加依赖:

[dependencies]
clap = { version = "4.0", features = ["derive"] }

现在写代码。替换 src/main.rs 的内容:

use clap::Parser;
use std::path::PathBuf;

/// 统计文件行数的工具
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Args {
    /// 要统计的文件路径
    file: PathBuf,
    
    /// 显示详细统计信息
    #[arg(short, long)]
    verbose: bool,
    
    /// 忽略空行
    #[arg(short, long)]
    skip_empty: bool,
}

2. 实现核心功能

main 函数上面添加:

use std::fs::File;
use std::io::{self, BufRead, BufReader};

fn count_lines(path: &PathBuf, skip_empty: bool) -> io::Result<(usize, usize)> {
    let file = File::open(path)?;
    let reader = BufReader::new(file);
    
    let mut total = 0;
    let mut non_empty = 0;
    
    for line in reader.lines() {
        let line = line?;
        total += 1;
        if !line.trim().is_empty() {
            non_empty += 1;
        }
    }
    
    Ok((total, non_empty))
}

3. 完善主函数

fn main() -> io::Result<()> {
    let args = Args::parse();
    
    match count_lines(&args.file, args.skip_empty) {
        Ok((total, non_empty)) => {
            if args.verbose {
                println!("文件: {}", args.file.display());
                println!("总行数: {}", total);
                println!("非空行: {}", non_empty);
                println!("空行数: {}", total - non_empty);
            } else if args.skip_empty {
                println!("{}", non_empty);
            } else {
                println!("{}", total);
            }
        }
        Err(e) => {
            eprintln!("错误: 无法读取文件 '{}': {}", args.file.display(), e);
            std::process::exit(1);
        }
    }
    
    Ok(())
}

4. 测试运行

# 构建
cargo build --release

# 测试
./target/release/cli-tool src/main.rs
./target/release/cli-tool src/main.rs --verbose
./target/release/cli-tool src/main.rs --skip-empty

让工具更专业

处理多个文件

修改参数结构:

/// 要统计的文件路径(可多个)
files: Vec<PathBuf>,

更新统计函数以处理多个文件:

fn process_files(files: &[PathBuf], skip_empty: bool, verbose: bool) -> io::Result<()> {
    let mut grand_total = 0;
    let mut grand_non_empty = 0;
    
    for file in files {
        match count_lines(file, skip_empty) {
            Ok((total, non_empty)) => {
                if verbose {
                    println!("{:12} {:12} {}", total, non_empty, file.display());
                } else if files.len() > 1 {
                    println!("{}: {}", file.display(), if skip_empty { non_empty } else { total });
                } else {
                    println!("{}", if skip_empty { non_empty } else { total });
                }
                
                grand_total += total;
                grand_non_empty += non_empty;
            }
            Err(e) => {
                eprintln!("{}: {}", file.display(), e);
            }
        }
    }
    
    if files.len() > 1 && verbose {
        println!("{}", "-".repeat(40));
        println!("{:12} {:12} 总计", grand_total, grand_non_empty);
    }
    
    Ok(())
}

添加进度指示

对于大文件,用户想知道进度。添加依赖:

indicatif = "0.17"

添加进度条:

use indicatif::{ProgressBar, ProgressStyle};

fn count_lines_with_progress(path: &PathBuf, skip_empty: bool) -> io::Result<(usize, usize)> {
    let metadata = std::fs::metadata(path)?;
    let file_size = metadata.len();
    
    let pb = ProgressBar::new(file_size);
    pb.set_style(
        ProgressStyle::default_bar()
            .template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})")
            .unwrap()
            .progress_chars("#>-"),
    );
    
    let file = File::open(path)?;
    let mut reader = BufReader::new(file);
    let mut buffer = String::new();
    
    let mut total = 0;
    let mut non_empty = 0;
    let mut bytes_read = 0;
    
    while reader.read_line(&mut buffer)? > 0 {
        bytes_read += buffer.len();
        pb.set_position(bytes_read as u64);
        
        total += 1;
        if !skip_empty || !buffer.trim().is_empty() {
            non_empty += 1;
        }
        buffer.clear();
    }
    
    pb.finish_and_clear();
    Ok((total, non_empty))
}

错误处理的最佳实践

Rust 的 Result 类型让错误处理变得清晰。为你的工具定义自定义错误类型:

use thiserror::Error;

#[derive(Error, Debug)]
pub enum CliError {
    #[error("文件错误: {0}")]
    Io(#[from] io::Error),
    
    #[error("文件不是 UTF-8 编码: {0}")]
    Encoding(String),
    
    #[error("参数错误: {0}")]
    Argument(String),
}

// 在 Cargo.toml 中添加:thiserror = "1.0"

性能优化技巧

1. 使用内存映射处理大文件

memmap = "0.7"
use memmap::Mmap;
use std::fs::File;

fn count_lines_mmap(path: &PathBuf) -> io::Result<usize> {
    let file = File::open(path)?;
    let mmap = unsafe { Mmap::map(&file)? };
    
    Ok(mmap.iter().filter(|&&b| b == b'\n').count())
}

2. 并行处理多个文件

rayon = "1.7"
use rayon::prelude::*;

fn process_files_parallel(files: &[PathBuf]) -> io::Result<()> {
    files.par_iter()
        .map(|file| {
            count_lines(file, false)
                .map(|(total, _)| (file, total))
                .map_err(|e| (file, e))
        })
        .for_each(|result| {
            match result {
                Ok((file, count)) => println!("{}: {}", file.display(), count),
                Err((file, error)) => eprintln!("{}: {}", file.display(), error),
            }
        });
    
    Ok(())
}

发布你的工具

1. 优化编译

# 使用 LTO 和优化
# 在 Cargo.toml 中添加:
[profile.release]
lto = true
codegen-units = 1
opt-level = 3

# 构建
cargo build --release

2. 跨平台编译

# 安装目标平台
rustup target add x86_64-pc-windows-gnu
rustup target add x86_64-apple-darwin

# 交叉编译
cargo build --release --target x86_64-pc-windows-gnu

3. 减少二进制大小

[profile.release]
strip = true  # 移除调试符号
panic = "abort"  # 减少 panic 处理代码

实际项目结构

成熟的项目通常这样组织:

cli-tool/
├── src/
│   ├── main.rs          # 入口点,参数解析
│   ├── lib.rs           # 核心逻辑
│   └── commands/        # 子命令模块
│       ├── count.rs     # 计数功能
│       └── search.rs    # 搜索功能
├── tests/               # 集成测试
├── examples/            # 示例代码
└── benchmarks/          # 性能测试


正文到此结束
本文目录