diff --git a/Cargo.toml b/Cargo.toml index bb5829d..850eb37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,7 @@ edition = "2021" chrono = "0.4.38" colored = "2.1.0" filetime = "0.2.25" +regex = "1.11.0" +reqwest = { version = "0.12.8", features = ["blocking", "rustls-tls"] } +sysinfo = "0.32.0" tokio = { version = "1", features = ["full"] } diff --git a/README.md b/README.md new file mode 100644 index 0000000..d830242 --- /dev/null +++ b/README.md @@ -0,0 +1,134 @@ + +# WinShell + +WinShell 是一个使用 Rust 实现的命令行工具集合,旨在提供在 Windows 环境下类似于 Linux 系统常用命令的功能。这些命令包括 `ls`、`mv`、`mkdir`、`rm`、`cat`、`cp`、`tail`、`top` 等,方便在 Windows 平台上执行类似 Unix 风格的命令操作。 + +## 项目结构 + +项目按照不同类型的命令进行分类,使用子目录来组织各类操作。目录结构如下: + +``` +WinShell/ +├── src/ +│ ├── commands/ +│ │ ├── directory_ops/ # 文件目录相关命令 +│ │ │ ├── mod.rs +│ │ │ ├── mkdir.rs +│ │ ├── file_ops/ # 文件操作相关命令 +│ │ │ ├── mod.rs +│ │ │ ├── cp.rs +│ │ │ ├── ls.rs +│ │ │ ├── mv.rs +│ │ │ ├── mkdir.rs +│ │ │ ├── rm.rs +│ │ │ ├── cat.rs +│ │ │ ├── cp.rs +│ │ │ └── tail.rs +│ │ │ └── touch.rs +│ │ │ └── wget.rs +│ │ ├── system_ops/ # 系统操作相关命令 +│ │ │ └── top.rs +│ │ └── mod.rs # 命令模块定义 +│ ├── main.rs # 主程序入口 +├── Cargo.toml # Cargo 配置文件 +└── README.md # 项目说明文档 +``` + +## 安装与构建 + +### 前置条件 + +- 需要安装 [Rust 编译工具链](https://www.rust-lang.org/) + +### 构建步骤 + +1. 克隆项目到本地: + + ```bash + git clone https://gitee.com/zhub_dev/WinShell.git + cd WinShell + ``` + +2. 构建项目: + + ```bash + cargo build --release + ``` + +3. 运行项目: + + ```bash + cargo run -- [args...] + ``` + + 例如,列出当前目录的文件: + + ```bash + cargo run -- ls + ``` + +## 使用说明 + +WinShell 提供以下命令,支持常见的文件和系统操作: + +### 文件操作命令 + +- `cp`:复制文件或目录 +- `ls`:列出文件和目录 +- `mv`:移动或重命名文件/目录 +- `mkdir`:创建目录 +- `rm`:删除文件或目录 +- `cat`:显示文件内容 +- `tail`:查看文件末尾内容 +- `touch`:创建一个空文件 +- `wget`:下载文件 + +### 系统操作命令 + +- `top`:显示系统的进程列表 +- `kill`:终止一个进程 + +### 示例(命令调试模式) + +- 列出当前目录的文件(`ls` 命令): + + ```bash + cargo run -- ls + ``` + +- 删除文件(`rm` 命令): + + ```bash + cargo run -- rm <文件名> + ``` + +- 显示文件内容(`cat` 命令): + + ```bash + cargo run -- cat <文件名> + ``` + +- 复制文件(`cp` 命令): + + ```bash + cargo run -- cp <源文件> <目标文件> + ``` +### 安装 WinShell 命令到 windows +`(待补充)` + +## 参数说明 + +每个命令支持的参数有所不同,以下是一些常用的选项: + +- `ls -l`:以详细模式列出文件 +- `tail -n <行数>`:显示文件的最后几行 +- `top -n <显示进程数>`:显示指定数量的进程 + +## 注意事项 + +1. 某些命令可能需要管理员权限才能执行。 +2. 目前只支持在 Windows 系统上运行。 + +## 贡献 + +欢迎对 WinShell 项目进行贡献,提交 Pull Request 或 Issue。 diff --git a/src/commands/file_ops/cp.rs b/src/commands/file_ops/cp.rs new file mode 100644 index 0000000..ae6ef7e --- /dev/null +++ b/src/commands/file_ops/cp.rs @@ -0,0 +1,78 @@ +use std::fs; +use std::path::Path; + +/// 执行 cp 命令 +pub fn execute(args: &[String]) { + // 检查参数数量 + if args.len() != 2 { + eprintln!("用法: cp <源文件/目录> <目标文件/目录>"); + return; + } + + let source = &args[0]; + let target = &args[1]; + + // 检查源路径是否存在 + let source_path = Path::new(source); + if !source_path.exists() { + eprintln!("错误: 源 '{}' 不存在", source); + return; + } + + // 判断源是文件还是目录 + if source_path.is_dir() { + copy_directory(source_path, target); + } else { + copy_file(source_path, target); + } +} + +/// 复制文件 +fn copy_file(source: &Path, target: &str) { + let target_path = Path::new(target); + + // 检查目标是否已存在 + if target_path.exists() { + eprintln!("错误: 目标 '{}' 已存在", target); + return; + } + + // 复制文件 + match fs::copy(source, target_path) { + Ok(_) => println!("文件 '{}' 成功复制到 '{}'", source.display(), target), + Err(e) => eprintln!("复制文件时出错: {}", e), + } +} + +/// 递归复制目录 +fn copy_directory(source: &Path, target: &str) { + let target_path = Path::new(target).join(source.file_name().unwrap()); // 目标目录下的文件名 + + // 创建目标目录 + if let Err(e) = fs::create_dir_all(&target_path) { + eprintln!("创建目标目录时出错: {}", e); + return; + } + + // 遍历源目录中的所有文件和子目录 + if let Ok(entries) = fs::read_dir(source) { + for entry in entries { + match entry { + Ok(entry) => { + let entry_path = entry.path(); + let target_entry_path = target_path.join(entry.file_name()); + + // 如果是目录,递归复制 + if entry_path.is_dir() { + copy_directory(&entry_path, target_entry_path.to_str().unwrap()); + } else { + copy_file(&entry_path, target_entry_path.to_str().unwrap()); + } + } + Err(e) => eprintln!("读取目录时出错: {}", e), + } + } + } else { + eprintln!("读取源目录时出错: {}", source.display()); + } +} diff --git a/src/commands/file_ops/ls.rs b/src/commands/file_ops/ls.rs index 3b2792b..8214c0c 100644 --- a/src/commands/file_ops/ls.rs +++ b/src/commands/file_ops/ls.rs @@ -1,5 +1,5 @@ // 引入 colored 库以支持颜色输出 -use chrono::NaiveDateTime; +use chrono::DateTime; use colored::*; use std::fs::{self, DirEntry, Metadata}; use std::time::{SystemTime, UNIX_EPOCH}; @@ -183,13 +183,13 @@ fn print_file_color(entry: &DirEntry, file_name: &str) { } // 将文件名称做颜色格式化 -fn format_file_name(file_name: &str) -> String { +/*fn format_file_name(file_name: &str) -> String { if file_name.starts_with('.') { file_name.yellow().to_string() // 隐藏文件用黄色显示 } else { file_name.to_string() } -} +}*/ // 将系统时间格式化为可读的字符串 fn format_system_time(system_time: SystemTime) -> String { @@ -198,6 +198,6 @@ fn format_system_time(system_time: SystemTime) -> String { .unwrap_or_default() .as_secs(); // 使用 NaiveDateTime::from_timestamp 将时间戳转换为日期时间 - let naive_datetime = NaiveDateTime::from_timestamp(datetime as i64, 0); + let naive_datetime = DateTime::from_timestamp(datetime as i64, 0).unwrap().time(); format!("{}", naive_datetime.format("%Y-%m-%d %H:%M:%S")) } diff --git a/src/commands/file_ops/mod.rs b/src/commands/file_ops/mod.rs index 8c9089b..fb68eab 100644 --- a/src/commands/file_ops/mod.rs +++ b/src/commands/file_ops/mod.rs @@ -2,3 +2,7 @@ pub mod ls; pub mod mv; pub mod rm; pub mod touch; +pub mod tail; +pub mod cp; +pub mod pwd; +pub mod wget; diff --git a/src/commands/file_ops/mv.rs b/src/commands/file_ops/mv.rs index 4ad7a9e..389e151 100644 --- a/src/commands/file_ops/mv.rs +++ b/src/commands/file_ops/mv.rs @@ -12,7 +12,7 @@ pub fn execute(args: &[String]) { let mut interactive = false; // 是否启用交互模式 let mut force = false; // 是否启用强制模式 let mut sources = vec![]; // 源文件或目录列表 - let mut target = String::new(); // 目标路径 + let target; // 目标路径 // 解析命令行参数 for arg in &args[..args.len() - 1] { diff --git a/src/commands/file_ops/pwd.rs b/src/commands/file_ops/pwd.rs new file mode 100644 index 0000000..8a63c83 --- /dev/null +++ b/src/commands/file_ops/pwd.rs @@ -0,0 +1,15 @@ +use std::env; + +pub fn execute() { + // 获取当前工作目录 + match env::current_dir() { + Ok(path) => { + // 输出工作目录路径 + println!("{}", path.display()); + } + Err(e) => { + // 如果获取失败,输出错误信息 + eprintln!("错误: 无法获取当前工作目录 - {}", e); + } + } +} diff --git a/src/commands/file_ops/tail.rs b/src/commands/file_ops/tail.rs new file mode 100644 index 0000000..bfe8e89 --- /dev/null +++ b/src/commands/file_ops/tail.rs @@ -0,0 +1,127 @@ +use regex::Regex; +use std::fs; +use std::io::{self, BufReader, Read, Seek}; +use std::path::Path; +use std::thread; +use std::time::Duration; + +/// 执行 tail 命令 +pub fn execute(args: &[String]) { + if args.is_empty() { + eprintln!("用法: tail [-n <行数>] [-f] <文件名>"); + return; + } + + let mut line_count: usize = 10; // 默认行数 + let mut follow: bool = false; // 默认不跟踪 + let mut file_path: Option<&String> = None; + + // 解析命令行参数 + let mut iter = args.iter(); + while let Some(arg) = iter.next() { + match arg.as_str() { + "-f" => { + follow = true; + } + "-n" => { + if let Some(next) = iter.next() { + line_count = match next.parse::() { + Ok(n) => n, + Err(_) => { + eprintln!("错误: 行数必须是一个非负整数"); + return; + } + }; + } else { + eprintln!("错误: '-n' 选项后需要指定行数"); + return; + } + } + _ => { + // -{n}f 的处理, n代表数字, 使用正则匹配 + let re = Regex::new(r"-(\d+)(f)?").unwrap(); + if let Some(cap) = re.captures(arg) { + let num_str = cap.get(1).unwrap().as_str(); + line_count = match num_str.parse::() { + Ok(n) => n, + Err(_) => { + eprintln!("错误: 行数必须是一个非负整数"); + return; + } + }; + + // 检查是否有 -f 选项 + if cap.get(2).is_some() { + follow = true; + } + } + + // 假定第一个非选项参数是文件路径 + file_path = Some(arg); + } + } + } + + // 检查文件路径是否提供 + let file_path = match file_path { + Some(path) => Path::new(path), + None => { + eprintln!("错误: 需要指定文件名"); + return; + } + }; + + // 检查文件是否存在 + if !file_path.exists() { + eprintln!("错误: 文件 '{}' 不存在", file_path.display()); + return; + } + + // 读取文件内容并输出最后 n 行 + if let Err(e) = tail_file(file_path, line_count, follow) { + eprintln!("读取文件时出错: {}", e); + } +} + +/// 读取文件并输出最后 n 行或持续跟踪 +fn tail_file(file_path: &Path, line_count: usize, follow: bool) -> io::Result<()> { + let file = fs::OpenOptions::new().read(true).open(file_path)?; + let mut reader = BufReader::new(file); + + // 读取文件内容 + let mut contents = String::new(); + reader.read_to_string(&mut contents)?; + + // 输出最后 n 行 + let lines: Vec<&str> = contents.lines().collect(); + let total_lines = lines.len(); + let start_index = total_lines.saturating_sub(line_count); + + for line in &lines[start_index..] { + println!("{}", line); + } + + // 如果跟踪,持续监视文件变化 + if follow { + let mut last_size = contents.len(); + loop { + thread::sleep(Duration::from_secs(1)); // 每隔一秒检查一次 + let current_size = fs::metadata(file_path)?.len() as usize; + if current_size > last_size { + // 读取新内容 + let mut new_contents = String::new(); + let mut file = fs::OpenOptions::new().read(true).open(file_path)?; + file.seek(io::SeekFrom::Start(last_size as u64))?; + file.read_to_string(&mut new_contents)?; + + // 输出新内容 + for line in new_contents.lines() { + println!("{}", line); + } + last_size = current_size; // 更新文件大小 + } + } + } + + Ok(()) +} diff --git a/src/commands/file_ops/wget.rs b/src/commands/file_ops/wget.rs new file mode 100644 index 0000000..036f11f --- /dev/null +++ b/src/commands/file_ops/wget.rs @@ -0,0 +1,49 @@ +use std::fs::File; +use std::io::{self, copy}; +use std::path::Path; +use reqwest::blocking::get; +use reqwest::Url; + +pub fn execute(args: &[String]) { + if args.len() != 1 { + eprintln!("用法: wget "); + return; + } + + let mut url = args[0].clone(); + + // 如果不含 https 和 http 补全 + if !url.starts_with("https://") && !url.starts_with("http://") { + url = format!("https://{}", url); + // url = &(new_url.clone()); + println!("URL 不包含协议,已自动补全为: {}", url); + } + + let url = &url; + + // 检查 URL 是否有效 + if let Err(_) = Url::parse(url) { + eprintln!("错误: 无效的 URL"); + return; + } + + // 获取文件名 + let file_name = url.split('/').last().unwrap_or("downloaded_file"); + + // 下载文件 + match download_file(url, file_name) { + Ok(_) => println!("文件已成功下载: {}", file_name), + Err(e) => eprintln!("下载失败: {}", e), + } +} + +// 下载文件的函数 +fn download_file(url: &str, file_name: &str) -> Result<(), io::Error> { + let response = get(url).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + let mut dest = File::create(Path::new(file_name))?; + let mut content = response; + + // 将下载的内容写入文件 + copy(&mut content, &mut dest)?; + Ok(()) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 16ef935..75be922 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,4 +1,5 @@ pub mod file_ops; pub mod directory_ops; -pub mod text_ops; \ No newline at end of file +pub mod text_ops; +pub mod system_ops; \ No newline at end of file diff --git a/src/commands/system_ops/kill.rs b/src/commands/system_ops/kill.rs new file mode 100644 index 0000000..13c1099 --- /dev/null +++ b/src/commands/system_ops/kill.rs @@ -0,0 +1,37 @@ +// src/commands/system_ops/kill.rs +use std::process::Command; + +pub fn execute(args: &[String]) { + // 检查参数数量 + if args.len() < 1 { + eprintln!("用法: kill "); + return; + } + + let pid: u32 = match args[0].parse() { + Ok(pid) => pid, + Err(_) => { + eprintln!("错误: 无效的 PID '{}'", args[0]); + return; + } + }; + + // 使用 taskkill 命令终止进程 + let output = Command::new("taskkill") + .arg("/PID") + .arg(pid.to_string()) + .arg("/F") // 强制终止进程 + .output(); + + match output { + Ok(output) => { + if output.status.success() { + println!("成功: 终止进程 {}", pid); + } else { + let err = String::from_utf8_lossy(&output.stderr); + eprintln!("错误: 无法终止进程 {}: {}", pid, err); + } + } + Err(e) => eprintln!("错误: 执行 taskkill 失败: {}", e), + } +} diff --git a/src/commands/system_ops/mod.rs b/src/commands/system_ops/mod.rs new file mode 100644 index 0000000..d2a0f03 --- /dev/null +++ b/src/commands/system_ops/mod.rs @@ -0,0 +1,3 @@ + +pub mod top; +pub mod kill; \ No newline at end of file diff --git a/src/commands/system_ops/top.rs b/src/commands/system_ops/top.rs new file mode 100644 index 0000000..b81ddb2 --- /dev/null +++ b/src/commands/system_ops/top.rs @@ -0,0 +1,85 @@ +use std::{thread, time}; +use colored::Colorize; +// src/commands/system_ops/top.rs +use sysinfo::{Process, System}; + +pub fn execute(args: &[String]) { + let mut system = System::new_all(); + let mut refresh_interval = time::Duration::from_secs(3); // 默认刷新间隔 + let mut display_count = usize::MAX; // 默认显示所有进程 + let mut specific_pid: Option = None; + + // 解析命令行参数 + let mut i = 1; + while i < args.len() { + match args[i].as_str() { + "-n" => { + i += 1; + if i < args.len() { + display_count = args[i].parse().unwrap_or(usize::MAX); + } + } + "-s" => { + i += 1; + if i < args.len() { + if let Ok(seconds) = args[i].parse::() { + refresh_interval = time::Duration::from_secs(seconds); + } + } + } + "-p" => { + i += 1; + if i < args.len() { + specific_pid = args[i].parse().ok(); + } + } + _ => {} + } + i += 1; + } + + // 主循环 + loop { + // 更新系统信息 + system.refresh_all(); + + // 获取当前所有进程的信息 + let mut processes: Vec<&Process> = system.processes().iter().map(|(_, p)| p).collect(); + + // 过滤特定 PID + if let Some(pid) = specific_pid { + //processes.retain(|&p| p.pid() == pid); + processes.retain(|&p| p.pid().as_u32() == pid); + } + + // 按照 CPU 使用率进行排序 + processes.sort_by(|a, b| b.cpu_usage().partial_cmp(&a.cpu_usage()).unwrap_or(std::cmp::Ordering::Equal)); + + // 打印表头 + println!("{:<5}\t{:<25}\t{:<8}\t{:<10}\t{:<10}", + "PID", "Name", "CPU(%)", "Mem(KB)", "State"); + println!("{}", "-".repeat(80)); + + // 取 processes 前10条记录 + processes.truncate(15); + + // 打印进程信息 + for process in processes.iter().take(display_count) { + let pid = process.pid(); + let name = process.name().to_str().unwrap_or("N/A"); + let cpu_usage = process.cpu_usage(); + let memory = process.memory(); + let status = if process.status().eq(&sysinfo::ProcessStatus::Run) { + "运行中" + } else { + "已停止" + }; + + println!("{:<5}\t{:<25}\t{:<8}\t{:<10}\t{:<10}", + pid, name.blue(), cpu_usage, (memory / 1024) as u32, status); + } + + // 等待一段时间后刷新 + thread::sleep(refresh_interval); + } +} diff --git a/src/main.rs b/src/main.rs index 302b1bf..dc86c3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,12 +14,18 @@ fn main() { let command_args = &args[2..]; match command.as_str() { + "cp" => commands::file_ops::cp::execute(command_args), "ls" => commands::file_ops::ls::execute(command_args), "mv" => commands::file_ops::mv::execute(command_args), + "pwd" => commands::file_ops::pwd::execute(), "rm" => commands::file_ops::rm::execute(command_args), + "tail" => commands::file_ops::tail::execute(command_args), "touch" => commands::file_ops::touch::execute(command_args), + "wget" => commands::file_ops::wget::execute(command_args), "mkdir" => commands::directory_ops::mkdir::execute(command_args), "cat" => commands::text_ops::cat::execute(command_args), + "top" => commands::system_ops::top::execute(command_args), + "kill" => commands::system_ops::kill::execute(command_args), _ => eprintln!("未知命令: {}", command), } }