feat: 1.新增 cp、pwd、tail、wget、kill、top命令支持

2.新增 README.md 文件,提供项目说明和使用指南
This commit is contained in:
2024-10-24 22:57:03 +08:00
parent cb832f614b
commit f77aae5b2c
14 changed files with 548 additions and 6 deletions

View File

@@ -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"] }

134
README.md Normal file
View File

@@ -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 -- <command> [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。

View File

@@ -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());
}
}

View File

@@ -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"))
}

View File

@@ -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;

View File

@@ -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] {

View File

@@ -0,0 +1,15 @@
use std::env;
pub fn execute() {
// 获取当前工作目录
match env::current_dir() {
Ok(path) => {
// 输出工作目录路径
println!("{}", path.display());
}
Err(e) => {
// 如果获取失败,输出错误信息
eprintln!("错误: 无法获取当前工作目录 - {}", e);
}
}
}

View File

@@ -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::<usize>() {
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::<usize>() {
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(())
}

View File

@@ -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 <URL>");
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(())
}

View File

@@ -1,4 +1,5 @@
pub mod file_ops;
pub mod directory_ops;
pub mod text_ops;
pub mod text_ops;
pub mod system_ops;

View File

@@ -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 <PID>");
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),
}
}

View File

@@ -0,0 +1,3 @@
pub mod top;
pub mod kill;

View File

@@ -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<u32> = 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::<u64>() {
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);
}
}

View File

@@ -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),
}
}