Rust是开发CLI工具的绝佳选择——编译为单个二进制、启动快、跨平台。本课将用clap库构建一个功能完整的命令行工具。
// 使用clap库: cargo add clap --features derive
// 这里用手动解析演示核心概念
use std::env;
use std::fs;
use std::io::{self, BufRead, Write};
fn print_help() {
println!("rtool - Rust命令行工具集 v1.0");
println!();
println!("用法: rtool <command> [options]");
println!();
println!("命令:");
println!(" count <file> 统计文件行数/字数/字符数");
println!(" grep <pattern> <file> 在文件中搜索模式");
println!(" reverse <file> 反转文件每行");
println!(" upper <file> 转为大写");
println!(" lower <file> 转为小写");
println!(" head <n> <file> 显示前n行");
println!(" help 显示帮助");
}
fn count_file(filename: &str) -> Result<(), String> {
let content = fs::read_to_string(filename)
.map_err(|e| format!("无法读取 {}: {}", filename, e))?;
let lines = content.lines().count();
let words = content.split_whitespace().count();
let chars = content.chars().count();
let bytes = content.len();
println!(" 行数: {}", lines);
println!(" 字数: {}", words);
println!(" 字符数: {}", chars);
println!(" 字节数: {}", bytes);
Ok(())
}
fn grep_file(pattern: &str, filename: &str) -> Result<(), String> {
let file = fs::File::open(filename)
.map_err(|e| format!("无法打开 {}: {}", filename, e))?;
let reader = io::BufReader::new(file);
let mut found = 0;
for (i, line) in reader.lines().enumerate() {
let line = line.map_err(|e| format!("读取错误: {}", e))?;
if line.contains(pattern) {
found += 1;
println!("{}:{}: {}", filename, i + 1, line);
}
}
if found == 0 { println!("未找到匹配"); }
Ok(())
}
fn process_file(filename: &str, op: fn(&str) -> String) -> Result<(), String> {
let content = fs::read_to_string(filename)
.map_err(|e| format!("无法读取 {}: {}", filename, e))?;
for line in content.lines() {
println!("{}", op(line));
}
Ok(())
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 { print_help(); return; }
match args[1].as_str() {
"count" => {
if args.len() < 3 { println!("用法: rtool count <file>"); return; }
if let Err(e) = count_file(&args[2]) { eprintln!("错误: {}", e); }
}
"grep" => {
if args.len() < 4 { println!("用法: rtool grep <pattern> <file>"); return; }
if let Err(e) = grep_file(&args[2], &args[3]) { eprintln!("错误: {}", e); }
}
"reverse" => {
if args.len() < 3 { println!("用法: rtool reverse <file>"); return; }
let _ = process_file(&args[2], |s| s.chars().rev().collect());
}
"upper" => {
if args.len() < 3 { println!("用法: rtool upper <file>"); return; }
let _ = process_file(&args[2], |s| s.to_uppercase());
}
"help" | "--help" | "-h" => print_help(),
_ => println!("未知命令: {},运行 rtool help 查看帮助", args[1]),
}
}
✅ 验证通过
fn main() {
// ANSI颜色
println!("\x1b[31m红色文字\x1b[0m");
println!("\x1b[32m绿色文字\x1b[0m");
println!("\x1b[33m黄色文字\x1b[0m");
println!("\x1b[1;34m粗体蓝色\x1b[0m");
// 进度条
let total = 50;
for i in 0..=total {
let pct = i * 100 / total;
let filled = i;
let empty = total - i;
print!("\r[{}{}] {}%", "█".repeat(filled), "░".repeat(empty), pct);
std::io::Write::flush(&mut std::io::stdout()).unwrap();
std::thread::sleep(std::time::Duration::from_millis(30));
}
println!("\n完成!✅");
}
✅ 验证通过
用clap实现TODO列表CLI:add/list/done/delete命令,数据保存到JSON文件。
实现简单的XOR文件加密CLI工具,支持加密和解密。
use std::io::{self, Write};
fn progress_bar(current: usize, total: usize, width: usize) -> String {
let pct = current * 100 / total;
let filled = current * width / total;
format!("[{}{}] {}%", "█".repeat(filled), "░".repeat(width - filled), pct)
}
fn main() {
// 进度条
for i in 0..=20 {
print!("
{}", progress_bar(i, 20, 30));
io::stdout().flush().unwrap();
std::thread::sleep(std::time::Duration::from_millis(50));
}
println!("
完成!");
// clap库概念
println!("
使用clap库:");
println!(" #[derive(Parser)]");
println!(" struct Cli {{");
println!(" #[arg(short, long)]");
println!(" verbose: bool,");
println!(" #[arg(short, long)]");
println!(" output: Option,");
println!(" }}");
}
✅ 验证通过
fn main() {
println!("CLI工具完整发布流程:");
println!();
println!("1. 开发阶段:");
println!(" cargo new mytool");
println!(" // 添加clap, anyhow等依赖");
println!(" cargo run -- --help");
println!();
println!("2. 测试阶段:");
println!(" cargo test");
println!(" cargo clippy");
println!(" cargo fmt --check");
println!();
println!("3. 构建Release:");
println!(" cargo build --release");
println!(" // 二进制在 target/release/mytool");
println!();
println!("4. 交叉编译:");
println!(" rustup target add x86_64-unknown-linux-musl");
println!(" cargo build --release --target x86_64-unknown-linux-musl");
println!(" rustup target add x86_64-pc-windows-gnu");
println!(" cargo build --release --target x86_64-pc-windows-gnu");
println!();
println!("5. 发布:");
println!(" cargo publish # 发布到crates.io");
println!(" # 或上传GitHub Release");
println!();
println!("6. 安装:");
println!(" cargo install mytool");
println!(" cargo install --git https://github.com/you/mytool");
println!("
推荐CLI工具参考:");
println!(" ripgrep - 快速搜索");
println!(" fd - 查找文件");
println!(" bat - cat增强");
println!(" exa/eza - ls增强");
println!(" delta - diff增强");
println!(" starship - 终端提示符");
}
use std::env;
use std::fs;
use std::path::Path;
fn search_files(dir: &Path, pattern: &str, max_depth: usize) -> Vec {
let mut results = Vec::new();
if max_depth == 0 { return results; }
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
let name = path.file_name().unwrap().to_string_lossy();
if name.contains(pattern) {
results.push(path.display().to_string());
}
if path.is_dir() {
results.extend(search_files(&path, pattern, max_depth - 1));
}
}
}
results
}
fn main() {
let dir = env::current_dir().unwrap_or_else(|_| Path::new(".").to_path_buf());
println!("搜索目录: {}", dir.display());
// 搜索.rs文件
let rust_files = search_files(&dir, ".rs", 3);
println!("找到{}个.rs文件:", rust_files.len());
for f in rust_files.iter().take(10) { println!(" {}", f); }
if rust_files.len() > 10 { println!(" ...还有{}个", rust_files.len() - 10); }
// 搜索.toml文件
let toml_files = search_files(&dir, ".toml", 2);
println!("找到{}个.toml文件", toml_files.len());
// 统计
let all_files = search_files(&dir, "", 3);
println!("共扫描{}个文件/目录", all_files.len());
}
✅ 验证通过
// Cargo.toml
// [package]
// name = "mytool"
// version = "0.1.0"
// edition = "2024"
// [dependencies]
// clap = { version = "4", features = ["derive"] }
// anyhow = "1"
// serde = { version = "1", features = ["derive"] }
// serde_json = "1"
//
// src/main.rs:
// use clap::Parser;
// use anyhow::Result;
//
// #[derive(Parser)]
// #[command(name = "mytool", about = "A Rust CLI tool")]
// struct Cli {
// #[arg(short, long)] verbose: bool,
// #[arg(short, long)] output: Option,
// #[command(subcommand)] command: Commands,
// }
//
// #[derive(clap::Subcommand)]
// enum Commands {
// Add { name: String },
// List,
// Remove { id: u64 },
// }
fn main() {
println!("CLI项目结构:");
println!(" mytool/");
println!(" ├── Cargo.toml");
println!(" ├── README.md");
println!(" ├── LICENSE");
println!(" ├── .github/workflows/ci.yml");
println!(" └── src/");
println!(" └── main.rs");
println!("
参考项目:");
println!(" ripgrep - 搜索工具");
println!(" fd - 文件查找");
println!(" bat - 文件查看");
println!(" exa - ls替代");
println!(" delta - diff查看");
}
use std::collections::HashMap;
// 简易JSON值
#[derive(Debug, Clone)]
enum JsonValue {
Null,
Bool(bool),
Number(f64),
String(String),
Array(Vec),
Object(HashMap),
}
impl JsonValue {
fn get(&self, key: &str) -> Option<&JsonValue> {
match self {
JsonValue::Object(map) => map.get(key),
_ => None,
}
}
fn as_str(&self) -> Option<&str> {
match self { JsonValue::String(s) => Some(s), _ => None }
}
fn as_f64(&self) -> Option {
match self { JsonValue::Number(n) => Some(*n), _ => None }
}
fn pretty_print(&self, indent: usize) -> String {
let pad = " ".repeat(indent);
match self {
JsonValue::Null => "null".to_string(),
JsonValue::Bool(b) => b.to_string(),
JsonValue::Number(n) => n.to_string(),
JsonValue::String(s) => format!(""{}"", s),
JsonValue::Array(arr) => {
if arr.is_empty() { "[]".to_string() }
else {
let items: Vec = arr.iter()
.map(|v| format!("{}{}", " ".repeat(indent+1), v.pretty_print(indent+1)))
.collect();
format!("{}]", items.join(","))
}
}
JsonValue::Object(map) => {
if map.is_empty() { "{}".to_string() }
else {
let items: Vec = map.iter()
.map(|(k,v)| format!("{}"{}": {}", " ".repeat(indent+1), k, v.pretty_print(indent+1)))
.collect();
format!("{}", items.join(","))
}
}
}
}
}
fn main() {
let mut person = HashMap::new();
person.insert("name".into(), JsonValue::String("Alice".into()));
person.insert("age".into(), JsonValue::Number(30.0));
person.insert("active".into(), JsonValue::Bool(true));
let json = JsonValue::Object(person);
println!("姓名: {}", json.get("name").and_then(|v| v.as_str()).unwrap_or("?"));
println!("年龄: {}", json.get("age").and_then(|v| v.as_f64()).unwrap_or(0.0));
println!("调试: {:?}", json);
}
✅ 验证通过