Rust实战七 |基于带 colored 颜色文字控制台的批量文件删除工具

这里写自定义目录标题

需求背景

一个简单的指定文件删除工具,程序运行流程如下:

  1. 输出欢迎信息
  2. 获取本机IP地址(取第一个)
  3. 读取同目录下的files.txt文件(每行制表符分割的两列,第一列为IP、第二列为文件路径)
  4. 匹配出对应 IP 的文件清单
  5. 控制台输出待删除的文件总数
  6. 用户敲回车后对文件清单进行遍历删除,并输出删除信息
  7. 作业完成后,显示成功删除的文件总数(部分可能不存在)及总耗时

技术选型

  1. 使用 Rust(edition=2024) 开发,纯控制台工具,在 windows 平台打包为 exe 可执行文件。
  2. 控制台颜色字符使用colored库
  3. 应用图标使用icon.ico(来自 iconfont
  4. 兼容 windows 11 及 windows 10 操作系统

核心代码

使用 Trae CN 搭建基础项目框架(用的免费模型),然后手工调整才出来最终的效果。

rust 复制代码
use colored::*;
use local_ip_address::local_ip;
use std::fs::File;
use std::io::{self, BufRead, Read};
use std::path::Path;
use std::time::Instant;

#[cfg(windows)]
fn enable_ansi() {
     let _ = colored::control::set_virtual_terminal(true);
}

#[cfg(not(windows))]
fn enable_ansi() {}

// 从 Cargo.toml 中获取版本号
const VERSION: &str = env!("CARGO_PKG_VERSION");
const AUTHOR: &str = env!("CARGO_PKG_AUTHORS");
/**
 * 显示中灰色文字
 * 这样在 Windows 10/11 的 PowerShell、Conhost 都能显示灰色,而不依赖 dimmed()
 */
const GRAY: (u8, u8, u8) = (128, 128, 128);

/// 显示欢迎信息
fn show_welcome() {
    let line = "-------------------------------------------" .red();
    let title = format!("欢迎使用文件批量删除工具(当前版本:{})", VERSION).red();
    let copyright = format!("版权所有 © {}", AUTHOR) .red();
    
    println!("{}", line);
    println!("{}", title);
    println!("{}", copyright);
    println!("{}", line);
    println!();
}

/// 获取本机IP地址
fn get_local_ip() -> String {
    match local_ip() {
        Ok(ip) => ip.to_string(),
        Err(e) => {
            eprintln!("获取IP地址失败: {}", e);
            String::from("127.0.0.1")
        }
    }
}

/// 读取files.txt文件
fn read_files_file() -> io::Result<Vec<(String, String)>> {
    let path = Path::new("files.txt");
    let file = File::open(path)?;
    let reader = io::BufReader::new(file);
    
    let mut entries = Vec::new();
    
    for line in reader.lines() {
        let line = line?;
        // 使用制表符分割,支持实际的TAB字符
        let parts: Vec<&str> = line.split_terminator('\t').collect();
        if parts.len() == 2 {
            let ip = parts[0].trim();
            let path = parts[1].trim();
            if !ip.is_empty() && !path.is_empty() {
                entries.push((ip.to_string(), path.to_string()));
            }
        }
    }
    
    Ok(entries)
}

/// 删除文件
fn delete_file(path: &str) -> bool {
    match std::fs::remove_file(path) {
        Ok(_) => {
            println!("{} {}", "删除成功: ".bright_green(), path);
            true
        }
        Err(e) => {
            println!("{} {} {}", "删除失败: ".bright_red(), path, e.to_string().truecolor(GRAY.0, GRAY.1, GRAY.2));
            false
        }
    }
}

#[tokio::main]
async fn main() {
    enable_ansi();

    // 显示欢迎信息
    show_welcome();
    
    // 获取本机IP
    let local_ip = get_local_ip();
    
    // 读取文件列表
    let entries = match read_files_file() {
        Ok(entries) => entries,
        Err(e) => {
            println!();
            eprintln!("{} 读取 files.txt 失败: {}", "错误:".bright_red().bold(), e);
            println!();
            println!("{} 请检查以下问题:", "提示:".bright_yellow());
            println!("  1. 文件 files.txt 是否存在于程序同目录下");
            println!("  2. 文件格式是否为每行制表符分隔的IP和文件路径");
            println!("  3. 程序是否有读取该文件的权限");
            println!();
            println!("{} 按任意键退出...", "提示:".bright_cyan());
            io::stdin().read_exact(&mut [0]).unwrap_or(());
            return;
        }
    };
    
    // 匹配对应IP的文件
    let mut files_to_delete = Vec::new();
    for (ip, path) in entries {
        if ip == local_ip {
            files_to_delete.push(path);
        }
    }
    
    let total_files = files_to_delete.len();
    
    // 等待用户确认
    println!("{} 当前 IP 地址 {} 匹配到 {} 个文件,请回车执行删除操作",
        "提示:".bright_cyan(),
        local_ip.bright_blue(),
        total_files.to_string().bright_white()
    );
    io::stdin().read_line(&mut String::new()).unwrap();
    
    // 执行删除操作
    let start_time = Instant::now();
    let mut deleted_count = 0;
    
    for path in files_to_delete {
        if delete_file(&path) {
            deleted_count += 1;
        }
    }
    
    // 显示结果
    let duration = start_time.elapsed();
    let seconds = duration.as_secs_f32();
    println!();
    println!("{} 共处理 {} 个文件,删除 {} 个,耗时 {:.2} 秒",
        "完成:".bright_green().bold(),
        total_files.to_string().bright_blue(),
        deleted_count.to_string().bright_green(),
        seconds
    );
    
    // 程序结束后等待用户按任意键退出
    println!();
    println!("{} 按任意键退出...", "提示:".bright_cyan());
    io::stdin().read_exact(&mut [0]).unwrap_or(());
}

效果图

相关推荐
jeffer_liu2 小时前
Spring AI 生产级实战:工具调用
java·人工智能·后端·spring·ai编程
比昨天多敲两行2 小时前
linux 线程概念与控制
java·开发语言·jvm
huaweichenai2 小时前
php 根据每个类型的抽签范围实现抽签功能
开发语言·php
Cosolar3 小时前
AutoGen 精通教程:从零到企业级多 Agent 系统架构师
人工智能·后端·面试
codeejun4 小时前
每日一Go-73、云原生成本优化 —— 资源限制 & 指标驱动扩容
开发语言·云原生·golang
就叫_这个吧4 小时前
Java注解、元注解、自定义注解定义及应用
java·开发语言·注解
狂炫冰美式4 小时前
你还在古法PPT吗,试试HTML呢?免费编辑导出工具给 xdm 放这了
前端·后端·github
Sam_Deep_Thinking4 小时前
聊聊Java中的of
java·开发语言·架构