【Rust】文件系统

简介: 【Rust】文件系统

       本文将介绍Rust的文件系统,涵盖文件读写、目录遍历,并给出代码示例。

一、读取文件的字符串行

       我们向文件写入三行信息,然后使用 BufRead::lines 创建的迭代器 Lines 读取文件,一次读回一行。File 模块实现了提供 BufReader 结构体的 Read trait。File::create 打开文件 File 进行写入,File::open 则进行读取,代码如下:

use std::fs::File;
use std::io::{Write, BufReader, BufRead, Error};
 
fn main() -> Result<(), Error> {
    let path = "lines.txt";
 
    let mut output = File::create(path)?;
    write!(output, "out")?;
 
    let input = File::open(path)?;
    let buffered = BufReader::new(input);
 
    for line in buffered.lines() {
        println!("{}", line?);
    }
 
    Ok(())
}

二、避免读取写入同一文件

       对文件使用 same_file::Handle 结构体,可以测试文件句柄是否等同。在本实例中,将对要读取和写入的文件句柄进行相等性测试。

use same_file::Handle;
use std::fs::File;
use std::io::{BufRead, BufReader, Error, ErrorKind};
use std::path::Path;
 
fn main() -> Result<(), Error> {
    let path_to_read = Path::new("new.txt");
 
    let stdout_handle = Handle::stdout()?;
    let handle = Handle::from_path(path_to_read)?;
 
    if stdout_handle == handle {
        return Err(Error::new(
            ErrorKind::Other,
            "You are reading and writing to the same file",
        ));
    } else {
        let file = File::open(&path_to_read)?;
        let file = BufReader::new(file);
        for (num, line) in file.lines().enumerate() {
            println!("{} : {}", num, line?.to_uppercase());
        }
    }
 
    Ok(())
}

三、使用内存映射随机访问文件

      使用 memmap 创建文件的内存映射,并模拟文件的一些非序列读取。使用内存映射意味着您仅需索引一个切片,而不是使用 seek 方法来导航整个文件。

       Mmap::map 函数假定内存映射后的文件没有被另一个进程同时更改,否则会出现竞态条件

use memmap::Mmap;
use std::fs::File;
use std::io::{Write, Error};
 
fn main() -> Result<(), Error> {
    write!(File::create("content.txt")?, "My hovercraft is full of eels!")?;
 
    let file = File::open("content.txt")?;
    let map = unsafe { Mmap::map(&file)? };
 
    let random_indexes = [0, 1, 2, 19, 22, 10, 11, 29];
    assert_eq!(&map[3..13], b"hovercraft");
    let random_bytes: Vec<u8> = random_indexes.iter()
        .map(|&idx| map[idx])
        .collect();
    assert_eq!(&random_bytes[..], b"My loaf!");
    
    Ok(())
}

四、过去 24 小时内修改过的文件名

       通过调用 env::current_dir 获取当前工作目录,然后通过 fs::read_dir 读取目录中的每个条目,通过 DirEntry::path 提取条目路径,以及通过通过 fs::Metadata 获取条目元数据。Metadata::modified 返回条目自上次更改以来的运行时间 SystemTime::elapsed。Duration::as_secs 将时间转换为秒,并与 24 小时(24 * 60 * 60 秒)进行比较。Metadata::is_file 用于筛选出目录。

use error_chain::error_chain;
 
use std::{env, fs};
 
error_chain! {
    foreign_links {
        Io(std::io::Error);
        SystemTimeError(std::time::SystemTimeError);
    }
}
 
fn main() -> Result<()> {
    let current_dir = env::current_dir()?;
    println!(
        "Entries modified in the last 24 hours in {:?}:",
        current_dir
    );
 
    for entry in fs::read_dir(current_dir)? {
        let entry = entry?;
        let path = entry.path();
 
        let metadata = fs::metadata(&path)?;
        let last_modified = metadata.modified()?.elapsed()?.as_secs();
 
        if last_modified < 24 * 3600 && metadata.is_file() {
            println!(
                "Last modified: {:?} seconds, is read only: {:?}, size: {:?} bytes, filename: {:?}",
                last_modified,
                metadata.permissions().readonly(),
                metadata.len(),
                path.file_name().ok_or("No filename")?
            );
        }
    }
 
    Ok(())
}

五、查找给定路径的循环

       使用 same_file::is_same_file 检测给定路径的循环。例如,可以通过软连接(符号链接)在 Unix 系统上创建循环:

mkdir -p /tmp/foo/bar/baz
ln -s /tmp/foo/  /tmp/foo/bar/baz/qux

   下面的实例将断言存在一个循环。

use std::io;
use std::path::{Path, PathBuf};
use same_file::is_same_file;
 
fn contains_loop<P: AsRef<Path>>(path: P) -> io::Result<Option<(PathBuf, PathBuf)>> {
    let path = path.as_ref();
    let mut path_buf = path.to_path_buf();
    while path_buf.pop() {
        if is_same_file(&path_buf, path)? {
            return Ok(Some((path_buf, path.to_path_buf())));
        } else if let Some(looped_paths) = contains_loop(&path_buf)? {
            return Ok(Some(looped_paths));
        }
    }
    return Ok(None);
}
 
fn main() {
    assert_eq!(
        contains_loop("/tmp/foo/bar/baz/qux/bar/baz").unwrap(),
        Some((
            PathBuf::from("/tmp/foo"),
            PathBuf::from("/tmp/foo/bar/baz/qux")
        ))
    );
}

六、递归查找重名文件

       在当前目录中递归查找重复的文件名,只打印一次。

use std::collections::HashMap;
use walkdir::WalkDir;
 
fn main() {
    let mut filenames = HashMap::new();
 
    for entry in WalkDir::new(".")
            .into_iter()
            .filter_map(Result::ok)
            .filter(|e| !e.file_type().is_dir()) {
        let f_name = String::from(entry.file_name().to_string_lossy());
        let counter = filenames.entry(f_name.clone()).or_insert(0);
        *counter += 1;
 
        if *counter == 2 {
            println!("{}", f_name);
        }
    }
}

七、使用给定断言递归查找所有文件

       在当前目录中查找最近一天内修改的 JSON 文件。使用 follow_links 确保软链接(符号链接)像普通目录和文件一样被按照当前查找规则执行。

use error_chain::error_chain;
 
use walkdir::WalkDir;
 
error_chain! {
    foreign_links {
        WalkDir(walkdir::Error);
        Io(std::io::Error);
        SystemTime(std::time::SystemTimeError);
    }
}
 
fn main() -> Result<()> {
    for entry in WalkDir::new(".")
            .follow_links(true)
            .into_iter()
            .filter_map(|e| e.ok()) {
        let f_name = entry.file_name().to_string_lossy();
        let sec = entry.metadata()?.modified()?;
 
        if f_name.ends_with(".json") && sec.elapsed()?.as_secs() < 86400 {
            println!("{}", f_name);
        }
    }
 
    Ok(())
}

八、跳过隐藏文件遍历目录

       递归下行到子目录的过程中,使用 filter_entry 对目录中的条目传递 is_not_hidden 断言,从而跳过隐藏的文件和目录。Iterator::filter 可应用到要检索的任何目录 WalkDir::DirEntry,即使父目录是隐藏目录。

       根目录 "." 的检索结果,通过在断言 is_not_hidden 中使用 WalkDir::depth 参数生成。

use walkdir::{DirEntry, WalkDir};
 
fn is_not_hidden(entry: &DirEntry) -> bool {
    entry
         .file_name()
         .to_str()
         .map(|s| entry.depth() == 0 || !s.starts_with("."))
         .unwrap_or(false)
}
 
fn main() {
    WalkDir::new(".")
        .into_iter()
        .filter_entry(|e| is_not_hidden(e))
        .filter_map(|v| v.ok())
        .for_each(|x| println!("{}", x.path().display()));
}

九、在给定深度的目录,递归计算文件大小

       通过 WalkDir::min_depth 和 WalkDir::max_depth 方法,可以灵活设置目录的递归深度。下面的实例计算了 3 层子文件夹深度的所有文件的大小总和,计算中忽略根文件夹中的文件。

use walkdir::WalkDir;
 
fn main() {
    let total_size = WalkDir::new(".")
        .min_depth(1)
        .max_depth(3)
        .into_iter()
        .filter_map(|entry| entry.ok())
        .filter_map(|entry| entry.metadata().ok())
        .filter(|metadata| metadata.is_file())
        .fold(0, |acc, m| acc + m.len());
 
    println!("Total size: {} bytes.", total_size);
}

十、递归查找所有 png 文件

       在路径任意部分使用 ** 模式,例如,/media/**/*.png 匹配 media 及其子目录中的所有 PNG 文件。

use error_chain::error_chain;
 
use glob::glob;
 
error_chain! {
    foreign_links {
        Glob(glob::GlobError);
        Pattern(glob::PatternError);
    }
}
 
fn main() -> Result<()> {
    for entry in glob("**/*.png")? {
        println!("{}", entry?.display());
    }
 
    Ok(())
}

十一、忽略文件名大小写,使用给定模式查找所有文件

       在 /media/ 目录中查找与正则表达模式 img_[0-9]*.png 匹配的所有图像文件。

       一个自定义 MatchOptions 结构体被传递给 glob_with 函数,使全局命令模式下不区分大小写,同时保持其它选项的默认值 Default。


       注:glob 是 glob command 的简写。在 shell 里面,用 * 等匹配模式来匹配文件,如:ls src/*.rs。

use error_chain::error_chain;
use glob::{glob_with, MatchOptions};
 
error_chain! {
    foreign_links {
        Glob(glob::GlobError);
        Pattern(glob::PatternError);
    }
}
 
fn main() -> Result<()> {
    let options = MatchOptions {
        case_sensitive: false,
        ..Default::default()
    };
 
    for entry in glob_with("/media/img_[0-9]*.png", options)? {
        println!("{}", entry?.display());
    }
 
    Ok(())
}

目录
打赏
0
1
1
0
20
分享
相关文章
如何使用rust写内核模块
近年来,Rust语言以内存安全、高可靠性、零抽象等能力获得大量开发者关注,而这些特性恰好是内核编程中所需要的,所以我们看下如何用rust来写Linux内核模块。
如何使用rust写内核模块
如何使用 rust 写内核模块
近年来,Rust 语言以内存安全、高可靠性、零抽象等能力获得大量开发者关注,而这些特性恰好是内核编程中所需要的,所以我们看下如何用rust来写Linux内核模块。
357 7
如何使用 rust 写内核模块
使用Rust进行内存安全系统编程
【5月更文挑战第31天】Rust是一种保证内存安全的系统编程语言,通过所有权和借用系统防止内存错误,如内存泄漏和数据竞争。它的高性能、并发安全和跨平台特性使其在系统编程中占有一席之地。学习Rust涉及理解基本语法、所有权系统及使用标准库。通过案例分析,展示了如何在内存安全的前提下编写文件服务器。随着Rust的成熟,它在系统编程领域的应用前景广阔。
|
5月前
|
C# 一分钟浅谈:文件系统编程
在软件开发中,文件系统操作至关重要。本文将带你快速掌握C#中文件系统编程的基础知识,涵盖基本概念、常见问题及解决方法。文章详细介绍了`System.IO`命名空间下的关键类库,并通过示例代码展示了路径处理、异常处理、并发访问等技巧,还提供了异步API和流压缩等高级技巧,帮助你写出更健壮的代码。
69 2
如何使用Rust进行系统编程?
在 Rust 中,要调用系统调用并与底层 C 函数进行交互,通常会使用 `libc` crate。`libc` 提供了 Rust 到 C 的 FFI(Foreign Function Interface)绑定,允许 Rust 代码调用和使用底层的 C 函数和系统调用。
217 0
用Rust语言开发Linux内核,得先过内存模型这关
最近Rust For Linux的项目,随着Rust的火爆也开始逐渐升温,但是谷歌的强烈支持以及rCore OS、Redox等各种Rust操作系统项目的经验积累,Rust想进入到Linux的真正核心,也还是有很长的路要走,之前笔者已经撰文对于Rust在汇编支持、panic和alloc等系统操作等方面的问题进行过简要说明了。这里再对于Rust进入到Linux内核的最大拦路虎-也就是内存模型方面的问题,做一下介绍。
Rust开发操作系统系列:全新Hello World系统
正如标题,这篇文章是关于如何用Rust开发一个船新的操作系统,而这个操作系统只会打印一句话: Hello World 在这之前,我们需要了解有关计算机底层的基础知识,包括IO、接口、网络底层等等,当然如果你是老手或者是对Rust一窍不通的人,通过这篇文章也能清楚的了解如何制作一个微型的、有Terminal的操作系统 首先让我们了解一下Rust,[Rust是一门系统编程语言 ,专注于安全,尤其是并发安全,支持函数式和命令式以及泛型等编程范式的多范式语言。
5832 0
|
9月前
|
Rust 笔记:Rust 语言中的 所有权 与 生命周期
Rust 笔记:Rust 语言中的 所有权 与 生命周期
251 0
探索Rust语言的内存安全特性
【6月更文挑战第8天】Rust语言针对内存安全问题提供了创新解决方案,包括所有权系统、借用规则和生命周期参数。所有权系统确保值与其所有者绑定,防止内存泄漏;借用规则保证同一时间只有一个可变引用或多个不可变引用,消除数据竞争和野指针;生命周期参数则强化了引用的有效范围,提升安全性。通过这些特性,Rust帮助开发者编写出更健壮、安全的高性能软件,有望成为系统编程领域的领头羊。
【Rust指南】使用Cargo工具高效创建Rust项目 | 理解Rust特别的输入输出语句
【Rust指南】使用Cargo工具高效创建Rust项目 | 理解Rust特别的输入输出语句
288 0
【Rust指南】使用Cargo工具高效创建Rust项目 | 理解Rust特别的输入输出语句

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等