Day 30: 实战项目:命令行工具
1. 项目概述
Section titled “1. 项目概述”今天是30天Rust学习的收官之日,我们将综合运用所学知识,构建一个完整的命令行工具:rusty-find——一个快速的文件搜索工具。
这个工具将展示:
- 命令行参数解析(clap)
- 文件和目录遍历
- 错误处理
- 并发搜索(使用Arc和thread)
- 发布到crates.io
2. 项目初始化
Section titled “2. 项目初始化”首先创建一个新项目:
cargo new rusty-findcd rusty-find添加依赖到Cargo.toml:
[dependencies]clap = { version = "4", features = ["derive"] }anyhow = "1"walkdir = "2"rayon = "1"- clap:命令行参数解析
- anyhow:灵活的错误处理
- walkdir:目录遍历
- rayon:数据并行库,简化并发
3. 定义命令行接口
Section titled “3. 定义命令行接口”使用clap的derive模式定义参数:
use clap::Parser;
#[derive(Parser, Debug)]#[command(author, version, about, long_about = None)]struct Args { /// 搜索的起始目录 #[arg(default_value = ".")] path: String,
/// 要搜索的文件名 pattern: String,
/// 是否大小写敏感 #[arg(short, long, default_value = "false")] case_sensitive: bool,
/// 最大搜索深度 #[arg(short, long, default_value_t = 10)] max_depth: usize,}
fn main() { let args = Args::parse(); println!("搜索参数: {:?}", args);}运行cargo run -- "src" "clap"测试基本参数解析。
4. 实现文件搜索逻辑
Section titled “4. 实现文件搜索逻辑”use anyhow::{Context, Result};use std::path::Path;use walkdir::WalkDir;
fn search_files( root: &Path, pattern: &str, case_sensitive: bool, max_depth: usize,) -> Result<Vec<String>> { let mut matches = Vec::new();
for entry in WalkDir::new(root) .max_depth(max_depth) .follow_links(true) .into_iter() .filter_map(|e| e.ok()) { let path = entry.path();
// 只处理文件,跳过目录 if !path.is_file() { continue; }
// 获取文件名 let file_name = match path.file_name() { Some(name) => name.to_string_lossy(), None => continue, };
// 检查是否匹配 let file_name_str = file_name.as_ref(); let matches_pattern = if case_sensitive { file_name_str.contains(pattern) } else { file_name_str.to_lowercase().contains(&pattern.to_lowercase()) };
if matches_pattern { matches.push(path.display().to_string()); } }
Ok(matches)}5. 添加错误处理
Section titled “5. 添加错误处理”使用anyhow进行链式错误处理:
use anyhow::{Context, Result};
fn read_config() -> Result<()> { let content = std::fs::read_to_string("config.toml") .with_context(|| format!("Failed to read config.toml"))?;
println!("配置内容: {}", content); Ok(())}6. 并发搜索
Section titled “6. 并发搜索”使用rayon实现并行搜索:
use rayon::prelude::*;use std::path::Path;
fn parallel_search( root: &Path, pattern: &str, case_sensitive: bool, max_depth: usize,) -> Result<Vec<String>> { // 收集所有文件路径 let files: Vec<_> = WalkDir::new(root) .max_depth(max_depth) .into_iter() .filter_map(|e| e.ok()) .filter(|e| e.path().is_file()) .map(|e| e.path().to_path_buf()) .collect();
// 并行搜索 let matches = files .par_iter() .filter_map(|path| { let file_name = path.file_name()?.to_string_lossy(); let file_name_str = file_name.as_ref(); let matches_pattern = if case_sensitive { file_name_str.contains(pattern) } else { file_name_str.to_lowercase().contains(&pattern.to_lowercase()) };
if matches_pattern { Some(path.display().to_string()) } else { None } }) .collect();
Ok(matches)}rayon的.par_iter()自动将工作分配到多个线程,非常适合IO密集型任务。
7. 整合main函数
Section titled “7. 整合main函数”use anyhow::Result;use clap::Parser;use std::path::Path;use std::time::Instant;
#[derive(Parser, Debug)]#[command(author, version, about, long_about = None)]struct Args { #[arg(default_value = ".")] path: String,
pattern: String,
#[arg(short, long, default_value = "false")] case_sensitive: bool,
#[arg(short, long, default_value_t = 10)] max_depth: usize,
#[arg(long, default_value = "false")] parallel: bool,}
fn main() -> Result<()> { let args = Args::parse(); let path = Path::new(&args.path);
if !path.exists() { anyhow::bail!("路径不存在: {}", args.path); }
let start = Instant::now();
let matches = if args.parallel { println!("使用并行搜索..."); parallel_search(path, &args.pattern, args.case_sensitive, args.max_depth)? } else { println!("使用单线程搜索..."); search_files(path, &args.pattern, args.case_sensitive, args.max_depth)? };
let elapsed = start.elapsed();
if matches.is_empty() { println!("没有找到匹配的文件"); } else { println!("\n找到 {} 个匹配:", matches.len()); for m in &matches { println!(" {}", m); } }
println!("\n耗时: {:?}", elapsed);
Ok(())}8. 添加单元测试
Section titled “8. 添加单元测试”#[cfg(test)]mod tests { use super::*;
#[test] fn test_case_sensitive_match() { let result = "Hello.txt".contains("Hello"); assert!(result); }
#[test] fn test_case_insensitive_match() { let file = "Hello.txt".to_lowercase(); let pattern = "hello".to_lowercase(); assert!(file.contains(&pattern)); }}运行cargo test确保测试通过。
9. 发布到crates.io
Section titled “9. 发布到crates.io”发布前需要:
- 在crates.io注册账号
- 获取API token
- 配置cargo登录
# 登录cargo login <your-api-token>
# 检查代码cargo publish --dry-run
# 发布cargo publish10. 总结与展望
Section titled “10. 总结与展望”今天我们构建了一个功能完整的命令行工具,涵盖了:
- clap:优雅的命令行参数解析
- walkdir:可靠的目录遍历
- rayon:简洁的并发搜索
- anyhow:链式错误处理
- 测试与发布:完整的开发生态
这只是Rust能力的冰山一角。接下来你可以:
- 添加正则表达式支持
- 实现文件内容搜索
- 添加进度条
- 支持多平台
30天的学习让你掌握了Rust的核心技能。继续实践,探索更深的领域!