首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Rust案例:几十行代码实现一个显示更好的ls

Rust语言现在非常火,各种项目也是层出不穷,尤其一些老掉牙shell小工具就特别适合用很少的Rust代码来实现。实际上这类工具也非常的多,虫虫之前的文章对此进行总结介绍过,那么如何用Rust来写一个小工具呢,本文就学习一个案例,这个案例中使用不到100行的代码实现一个ls的替代,而且能支持比ls更好的功能。

ls的基本功能就是列出当前文件下的所有文件及其Pid,大小、修改时间,属主和权限等信息。在Rust可以使用一个板条箱walkdir。

创建项目

Rust开发,首先用cargo来创建项目,并添加要调用walkdir库,项目的名称为explore

cargo new exploreCreated binary (application) `explore` packagecargo add walkdirUpdating crates.io indexAdding walkdir v2.4.0 to dependencies.

而在为了实现基本功能,在main.rs添加代码:

初始版本

use walkdir::WalkDir;fn main() {for entry in WalkDir::new(".") {let entry = entry.unwrap();println!("{}", entry.path().display())}}

cargo run运行项目:

cargo run --quiet../Cargo.toml./target./target/.rustc_info.json./target/CACHEDIR.TAG./target/debug./target/debug/.fingerprint./target/debug/.fingerprint/explore-43ca298cd94bedc4[snip]./src./src/main.rs

隐藏文件

这样已经实现了,基本目录文件名的输出,而且连.xxx隐藏文件也都显示出来了,实际上默认是不需要的,需要隐藏起来。

增加一个隐藏文件隐藏函数is_hidden:

fn is_hidden(entry: &DirEntry) -> bool {entry.file_name().to_str().map(|s| s.starts_with('.')).unwrap_or(false)}

//并将其增加到main循环中:

for entry in WalkDir::new(".").min_depth(1).max_depth(1).into_iter().filter_entry(|e| !is_hidden(e)){let entry = entry.unwrap();println!("{}", entry.path().display())}

现在再次运行:

cargo run -q./Cargo.toml./target./Cargo.lock./src

命令参数

基本的列文件功能已经实现,再来实现命令行参数来实现各种参数和对应的功能。命令行参数解析可以使用Clap库derive。先添加到项目

cargo add clap -F deriveUpdating crates.io indexAdding clap v4.4.12 to dependencies.Features:+ color+ derive+ error-context+ help+ std+ suggestions+ usage- cargo- debug- deprecated- env- string- unicode- unstable-doc- unstable-styles- unstable-v5- wrap_help

通过Clap创建一个表示输入参数的结构,添加一些属性来让Clap初始化命令显示,并连接到对应功能函数:

#[derive(Parser)]struct Options {#[arg(short, long, value_name = "PATH")]path: Option

,#[arg(long, default_value_t = 1)]min_depth: usize,#[arg(long, default_value_t = 1)]max_depth: usize,#[arg(long, default_value_t = false)]hidden: bool,}fn main() {let options = Options::parse();for entry in WalkDir::new(options.path.unwrap_or(".".into())).min_depth(options.min_depth).max_depth(options.max_depth).into_iter().filter_entry(|e| options.hidden || !is_hidden(e)){// ...

重新执行:

cargo run -q./Cargo.toml./target./Cargo.lock./src

执行一下,新增加的参数项--max-depth, --min-depth, --hidden和 --path/ -p!

cargo run -q -- --max-depth 2 --hidden -p .

初具成型,但是还有一些功能需要添加,比如文件大小,对此我们需要解析出entry中对应的元数据即可,在main函数循环中:

let entry = entry.unwrap();let size = entry.metadata().unwrap().len();println!("{size:>9}B\t{}", entry.path().display());

其中{size}将打印大小变量,以字节为单位。 {size:>9}表示将数字右对齐,宽度为9个字符\t添加一个制表符,可以很好地对齐下一个内容,接着还添加了一个B单位,运行一下新代码:

cargo run -q246B ./Cargo.toml160B ./target7466B ./Cargo.lock96B ./src

现在已经实现了ls基本功能了。为了更好一点,我们要学习bat,来给其进行颜色渲染,添加颜色来区分目录、文件和符号链接。这个功能可以“拿来”colored板条箱:

cargo add coloredUpdating crates.io indexAdding colored v2.0.4 to dependencies.Features:- no-color

修改main主函数:

let formatted_entry = if entry.file_type().is_dir() {entry.path().display().to_string().blue()} else if entry.file_type().is_file() {entry.path().display().to_string().white()} else {entry.path().display().to_string().yellow()};println!("{size:>9}B\t{formatted_entry}");

代码中调用colored,该库添加了一个Colorize-trait提供了扩展方法,例如.blue()等属性,但它只适用于字符串(&str和String),所以需要将输出转换为.display(),再次运行:

还有点朴素,接着在该文件大小也上色:

println!("{:>9}{}\t{formatted_entry}", size.to_string().green(), "B".green());

已经有点意思了。但是还有一个问题,大小只用了一个单位B,有点不是很完美么,在来对其处理下,为此添加一个bytesize板条箱:

cargo add bytesizeUpdating crates.io indexAdding bytesize v1.3.0 to dependencies.Features:- serde

Bytesize可添加B, KB或者其他单位显示。修改一下:

println!("{:>9}\t{formatted_entry}", format!("{}", ByteSize(size)).green());

效果如下:

这已经很不错了。

路劲处理

现在还有个问题,文件名中,带着的./前缀显得不太协调,下面着手处理这个问题。

为了解决这个问题,在输出之前只获取文件名和扩展名,而不获取每个条目的所有前缀路径。为了避免将所有内容打印在一行上并且不了解哪个文件属于哪个目录,按每个文件的深度(它所在的嵌套目录的数量)缩进每个文件。

让再次编译并运行:

时间显示

现在显示已经很漂亮了,还缺少文件的时间信息。Rust支持标准库中的日期,但不支持格式化它们,我们添加chrono板条箱来做下扩展:

cargo add chronoUpdating crates.io indexAdding chrono v0.4.31 to dependencies.Features:+ android-tzdata+ clock+ iana-time-zone+ js-sys+ oldtime+ std+ wasm-bindgen+ wasmbind+ winapi+ windows-targets- __internal_bench- alloc- arbitrary- libc- pure-rust-locales- rkyv- rustc-serialize- serde- unstable-locales

然后,我们从entry中把时间有关的数据.metadata()解析出来。.metadata()返回一个Option,该对象还包含一个.modified(),一个方法 Result的SystemTime。

let metadata = entry.metadata().unwrap();let size = metadata.len();let date = metadata.modified().unwrap();

chrono可以支持对序列化的,按需要显示时间格式化和显示。创建一个 formatted_date变量检查是否modified/ -m-flag已设置,如果未设置,则返回空字符串。如果是,则将日期转换为chrono的DateTime,吐出漂亮的RFC2822输出,然后剥去丑陋的+0000-后缀。

let formatted_date = if options.modified {format!("\t{}",DateTime::::from(date).to_rfc2822().strip_suffix(" +0000").unwrap().blue())} else { "".to_string() };println!("{:>9}{}\t{formatted_entry}",format!("{}", ByteSize(size)).green(),formatted_date);

结果:

上面还遗留的函数命令(--hiden)选项没实现,将其补上:

WalkDir::new(options.path.unwrap_or(".".into()))

.sort_by_file_name()

测试一下该功能:

总结

至此,我们实现了一个ls Plus简略版本的小工具,基于Rust其板条箱生态体系我们用几十行的代码就实现了一个支持颜色渲染的版本的ls。

当然要实现完整的ls功能还有一些事情没完成:

权限作者创建日期最近访问日期

完整版本的ls命令行选项功能等,但是那就超出作为一个学习项目的目的,实际上这样的工具rust中早有人就写过了,那就是exa工具

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OY7HUQLshOvl43qqJQ62fwBQ0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券