Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Directory Traversal

File names that have been modified in the last 24 hours

walkdir-badge cat-filesystem-badge

Gets the current working directory and returns file names modified within the last 24 hours. env::current_dir gets the current working directory, WalkDir::new creates a new WalkDir for the current directory. WalkDir::into_iter creates an iterator, Iterator::filter_map applies Result::ok to WalkDir::DirEntry and filters out the directories.

std::fs::Metadata::modified returns the SystemTime::elapsed time since the last modification. Duration::as_secs converts the time to seconds and compared with 24 hours (24 * 60 * 60 seconds). Iterator::for_each prints the file names.

use walkdir::WalkDir;
use anyhow::Result;
use std::env;

fn main() -> Result<()> {
    let current_dir = env::current_dir()?;
    println!("Entries modified in the last 24 hours in {:?}:", current_dir);

    for entry in WalkDir::new(current_dir)
            .into_iter()
            .filter_map(|e| e.ok())
            .filter(|e| e.metadata().unwrap().is_file()) {
        let path = entry.path();
        let metadata = entry.metadata()?;
        let modified = metadata.modified()?.elapsed()?.as_secs();
        if modified < 24 * 3600 {
            println!("{}", path.display());
        }
    }

    Ok(())
}

Find loops for a given path

same_file-badge walkdir-badge cat-filesystem-badge

Use same_file::is_same_file to detect loops for a given path. For example, a loop is created on a Unix system via symlinks:

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

The following would assert that a loop exists.

use walkdir::WalkDir;
use same_file::is_same_file;

fn main() {
    let mut loop_found = false;
    for entry in WalkDir::new(".")
        .follow_links(true)
        .into_iter()
        .filter_map(|e| e.ok()) {
        let ancestor = entry.path()
            .ancestors()
            .skip(1)
            .find(|ancestor| is_same_file(ancestor, entry.path()).is_ok());

        if ancestor.is_some() {
            loop_found = true;
        }
    }
    // Note: This test would only pass if there are actual symlink loops
    // println!("Loop found: {}", loop_found);
}

Recursively find duplicate file names

walkdir-badge cat-filesystem-badge

Find recursively in the current directory duplicate filenames, printing them only once.

use walkdir::WalkDir;
use std::collections::HashMap;

fn main() {
    let mut filenames = HashMap::new();

    for entry in WalkDir::new(".")
                         .into_iter()
                         .filter_map(Result::ok)
                         .filter(|e| e.file_type().is_file()) {

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

# Recursively find all files with given predicate

[![walkdir-badge]][walkdir] [![cat-filesystem-badge]][cat-filesystem]

Find JSON files modified within the last day in the current directory.
Using [`follow_links`] ensures symbolic links are followed like they were
normal directories and files.

```rust,edition2021
use walkdir::WalkDir;
use anyhow::Result;

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!("{}", entry.path().display());
        }
    }
    Ok(())
}

Traverse directories while skipping dotfiles

walkdir-badge cat-filesystem-badge

Uses filter_entry to descend recursively into entries passing the is_not_hidden predicate thus skipping hidden files and directories. Iterator::filter_map applies is_not_hidden on each WalkDir::DirEntry even if the parent is a hidden directory.

Root dir "." yields through WalkDir::depth usage in is_not_hidden predicate.

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

Recursively calculate file sizes at given depth

walkdir-badge cat-filesystem-badge

Recursion depth can be flexibly set by WalkDir::max_depth. Calculates sum of all file sizes to 3 subdir levels, ignoring files in the root directory.

use walkdir::WalkDir;

fn main() {
    let total_size = WalkDir::new(".")
        .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);
}

Find all png files recursively

glob-badge cat-filesystem-badge

Recursively find all PNG files in the current directory. In this case, the ** pattern matches the current directory and all subdirectories.

Use the ** pattern in any path portion. For example, /media/**/*.png matches all PNGs in media and it's subdirectories.

use glob::glob;
use anyhow::Result;

fn main() -> Result<()> {
    for entry in glob("**/*.png")? {
        println!("{}", entry?.display());
    }
    Ok(())
}

Find all files with given pattern ignoring filename case

walkdir-badge glob-badge cat-filesystem-badge

Find all image files in the /media/ directory matching the img_[0-9]*.png pattern.

A custom MatchOptions struct is passed to glob_with instead of glob to make the glob pattern case insensitive while keeping the other options Default.

use walkdir::WalkDir;
use anyhow::Result;
use glob::{glob_with, MatchOptions};

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