用 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 --release2. 跨平台编译
# 安装目标平台
rustup target add x86_64-pc-windows-gnu
rustup target add x86_64-apple-darwin
# 交叉编译
cargo build --release --target x86_64-pc-windows-gnu3. 减少二进制大小
[profile.release]
strip = true # 移除调试符号
panic = "abort" # 减少 panic 处理代码实际项目结构
成熟的项目通常这样组织:
cli-tool/
├── src/
│ ├── main.rs # 入口点,参数解析
│ ├── lib.rs # 核心逻辑
│ └── commands/ # 子命令模块
│ ├── count.rs # 计数功能
│ └── search.rs # 搜索功能
├── tests/ # 集成测试
├── examples/ # 示例代码
└── benchmarks/ # 性能测试正文到此结束
- 本文标签: rust语言 编写命令行工具
- 本文链接: https://www.15102.com/article/8
- 版权声明: 本文由原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权