Directory Traversal
Construct and inspect a path
Most filesystem code is path arithmetic before it touches the disk. PathBuf
owns the buffer; Path is the borrowed view. The inspection methods —
file_name, file_stem, extension, parent — return Option
because a path might not have the component you ask for.
PathBuf::push grows the buffer one component at a time. with_extension
and with_file_name derive sibling or backup paths in one call without
mutating the original. Conversion to &str goes through Path::to_str,
which returns None on non-UTF-8 paths.
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
fn main() {
let mut path = PathBuf::from("/etc/nginx");
path.push("sites-available");
path.push("default.conf");
assert_eq!(path.parent(), Some(Path::new("/etc/nginx/sites-available")));
assert_eq!(path.file_name(), Some(OsStr::new("default.conf")));
assert_eq!(path.file_stem(), Some(OsStr::new("default")));
assert_eq!(path.extension(), Some(OsStr::new("conf")));
// Derive a backup name and a sibling path without mutating `path`.
let backup = path.with_extension("conf.bak");
assert_eq!(backup.file_name(), Some(OsStr::new("default.conf.bak")));
let sibling = path.with_file_name("nginx.conf");
assert_eq!(sibling, Path::new("/etc/nginx/sites-available/nginx.conf"));
// Borrow a `Path` as `&str` for APIs that need one.
let s: &str = path.to_str().expect("path is valid UTF-8");
println!("{s}");
}
Create a nested directory tree
fs::create_dir_all is mkdir -p: it walks the requested path and creates
every missing intermediate directory. It’s idempotent — calling it on a path
that already exists returns Ok(()), not an error — so it’s the right call
when seeding cache directories or log roots on startup.
fs::create_dir only creates the final component. If any parent on the way
is missing it fails with io::ErrorKind::NotFound. Reach for create_dir
when the parent is known to exist and you want the operation to fail loudly
if your assumption is wrong.
use std::fs;
use std::io;
use tempfile::tempdir;
fn main() -> io::Result<()> {
let root = tempdir()?;
let nested = root.path().join("cache").join("2026").join("05");
// Creates every missing component along the way.
fs::create_dir_all(&nested)?;
assert!(nested.is_dir());
// Calling it again on an existing directory is not an error.
fs::create_dir_all(&nested)?;
// `create_dir` only creates the leaf, and only when the parent exists.
let leaf = nested.join("26");
fs::create_dir(&leaf)?;
assert!(leaf.is_dir());
Ok(())
}
File names that have been modified in the last 24 hours
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
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
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
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.
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
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
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
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
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(())
}