Loading page…
Rust walkthroughs
Loading page…
The glob crate provides pattern matching for file paths using shell-style wildcard patterns (glob patterns). It allows you to find files matching patterns like *.rs, src/**/*.txt, or data/[0-9]*.csv. Glob patterns are commonly used for file discovery, build systems, and any task that needs to match multiple files by pattern. The crate returns an iterator over matching paths, making it easy to process results.
Key concepts:
* (any sequence), ? (single char), [] (character class), ** (recursive)PathBuf via GlobResult# Cargo.toml
[dependencies]
glob = "0.3"use glob::glob;
fn main() {
// Find all Rust files in current directory
for entry in glob("*.rs").expect("Failed to read glob pattern") {
match entry {
Ok(path) => println!("Found: {:?}", path.display()),
Err(e) => println!("Error: {}", e),
}
}
}use glob::glob;
fn main() {
// Match all files with .txt extension
println!("=== *.txt files ===");
for entry in glob("*.txt").unwrap() {
if let Ok(path) = entry {
println!("{}", path.display());
}
}
// Match all files starting with 'data'
println!("\n=== data* files ===");
for entry in glob("data*").unwrap() {
if let Ok(path) = entry {
println!("{}", path.display());
}
}
// Match all files ending with a number
println!("\n=== * with numbers ===");
for entry in glob("*[0-9]*").unwrap() {
if let Ok(path) = entry {
println!("{}", path.display());
}
}
}use glob::glob;
fn main() {
// * matches any sequence of characters (except path separator)
println!("=== Single * pattern ===");
for entry in glob("src/*.rs").unwrap() {
if let Ok(path) = entry {
println!("{}", path.display());
}
}
// ? matches exactly one character
println!("\n=== ? pattern (single character) ===");
for entry in glob("test?.rs").unwrap() {
if let Ok(path) = entry {
println!("{}", path.display());
}
}
// Multiple wildcards
println!("\n=== Multiple wildcards ===");
for entry in glob("src/*/mod.rs").unwrap() {
if let Ok(path) = entry {
println!("{}", path.display());
}
}
}use glob::glob;
fn main() {
// ** matches any number of directories recursively
println!("=== All Rust files recursively ===");
for entry in glob("**/*.rs").unwrap() {
if let Ok(path) = entry {
println!("{}", path.display());
}
}
// Find all mod.rs files in any subdirectory
println!("\n=== All mod.rs files ===");
for entry in glob("**/mod.rs").unwrap() {
if let Ok(path) = entry {
println!("{}", path.display());
}
}
// Find files in specific nested structure
println!("\n=== Nested src files ===");
for entry in glob("src/**/*.rs").unwrap() {
if let Ok(path) = entry {
println!("{}", path.display());
}
}
}use glob::glob;
fn main() {
// [abc] matches a, b, or c
println!("=== Files starting with a, b, or c ===");
for entry in glob("[abc]*").unwrap() {
if let Ok(path) = entry {
println!("{}", path.display());
}
}
// [!abc] matches anything except a, b, or c
println!("\n=== Files NOT starting with a, b, or c ===");
for entry in glob("[!abc]*").unwrap() {
if let Ok(path) = entry {
println!("{}", path.display());
}
}
// [0-9] matches digits
println!("\n=== Files starting with digit ===");
for entry in glob("[0-9]*").unwrap() {
if let Ok(path) = entry {
println!("{}", path.display());
}
}
// [a-z] matches lowercase letters
println!("\n=== Lowercase starting files ===");
for entry in glob("[a-z]*.rs").unwrap() {
if let Ok(path) = entry {
println!("{}", path.display());
}
}
// Combined character class
println!("\n=== Image files ===");
for entry in glob("*.[jJ][pP][gG]").unwrap() {
if let Ok(path) = entry {
println!("{}", path.display());
}
}
}use glob::{glob_with, MatchOptions};
fn main() {
// Case-insensitive matching
let options = MatchOptions {
case_sensitive: false,
require_literal_separator: false,
require_literal_leading_dot: false,
};
println!("=== Case-insensitive *.RS ===");
for entry in glob_with("*.RS", options).unwrap() {
if let Ok(path) = entry {
println!("{}", path.display());
}
}
// Require literal separator (don't allow * to match /)
let literal_sep = MatchOptions {
case_sensitive: true,
require_literal_separator: true,
require_literal_leading_dot: false,
};
println!("\n=== Literal separator ===");
for entry in glob_with("*/*.rs", literal_sep).unwrap() {
if let Ok(path) = entry {
println!("{}", path.display());
}
}
// Require literal leading dot (don't allow * to match leading .)
let literal_dot = MatchOptions {
case_sensitive: true,
require_literal_separator: false,
require_literal_leading_dot: true,
};
println!("\n=== Literal leading dot (skip hidden) ===");
for entry in glob_with("*", literal_dot).unwrap() {
if let Ok(path) = entry {
println!("{}", path.display());
}
}
}use glob::glob;
fn main() {
// Invalid pattern
match glob("[invalid") {
Ok(_) => println!("Pattern is valid"),
Err(e) => println!("Invalid pattern: {}", e),
}
// Handle both pattern and IO errors
println!("\n=== Searching with error handling ===");
match glob("src/**/*.rs") {
Ok(paths) => {
for entry in paths {
match entry {
Ok(path) => println!("Found: {}", path.display()),
Err(e) => println!("Error accessing path: {}", e),
}
}
}
Err(e) => println!("Invalid glob pattern: {}", e),
}
}use glob::glob;
use std::path::PathBuf;
fn main() {
// Collect into a Vec of successful paths
let files: Vec<PathBuf> = glob("*.rs")
.unwrap()
.filter_map(|e| e.ok())
.collect();
println!("Found {} Rust files", files.len());
for file in &files {
println!(" {}", file.display());
}
// Count matching files
let count = glob("src/**/*.rs")
.unwrap()
.filter_map(|e| e.ok())
.count();
println!("\nTotal Rust files in src: {}", count);
// Check if any match exists
let has_config = glob("*.toml")
.unwrap()
.any(|e| e.is_ok());
println!("Has TOML file: {}", has_config);
}use glob::glob;
use std::fs;
fn main() {
// Find and process Rust files
for entry in glob("src/**/*.rs").unwrap() {
if let Ok(path) = entry {
// Get file metadata
if let Ok(metadata) = fs::metadata(&path) {
let size = metadata.len();
println!("{} ({} bytes)", path.display(), size);
}
}
}
// Find files by extension
let extensions = ["rs", "toml", "md"];
for ext in extensions {
println!("\n=== .{} files ===", ext);
let pattern = format!("**/*.{}", ext);
for entry in glob(&pattern).unwrap() {
if let Ok(path) = entry {
println!("{}", path.display());
}
}
}
// Filter by file name pattern
println!("\n=== Test files ===");
for entry in glob("**/*.rs").unwrap() {
if let Ok(path) = entry {
if let Some(name) = path.file_name() {
if name.to_string_lossy().starts_with("test") {
println!("{}", path.display());
}
}
}
}
}use glob::glob;
fn main() {
// Find all directories
println!("=== Directories ===");
for entry in glob("*/").unwrap() {
if let Ok(path) = entry {
println!("Directory: {}", path.display());
}
}
// Find empty directories (directories with no files)
println!("\n=== Empty directories ===");
for entry in glob("*/").unwrap() {
if let Ok(path) = entry {
let has_files = glob(&format!("{}/*", path.display()))
.unwrap()
.any(|e| e.is_ok());
if !has_files {
println!("Empty: {}", path.display());
}
}
}
// Find directories at specific depth
println!("\n=== Directories 2 levels deep ===");
for entry in glob("*/*/").unwrap() {
if let Ok(path) = entry {
println!("{}", path.display());
}
}
}use glob::glob;
use std::path::PathBuf;
fn main() {
// Collect and sort by path
let mut files: Vec<PathBuf> = glob("**/*.rs")
.unwrap()
.filter_map(|e| e.ok())
.collect();
files.sort();
println!("=== Sorted by path ===");
for file in &files {
println!("{}", file.display());
}
// Sort by file name (not full path)
files.sort_by(|a, b| {
let a_name = a.file_name().unwrap_or_default();
let b_name = b.file_name().unwrap_or_default();
a_name.cmp(b_name)
});
println!("\n=== Sorted by filename ===");
for file in &files {
println!("{}", file.display());
}
// Sort by extension
files.sort_by(|a, b| {
let a_ext = a.extension().unwrap_or_default();
let b_ext = b.extension().unwrap_or_default();
a_ext.cmp(b_ext)
});
println!("\n=== Sorted by extension ===");
for file in &files {
println!("{}", file.display());
}
}use glob::glob;
use std::path::Path;
fn find_source_files(root: &Path) -> Vec<std::path::PathBuf> {
let patterns = [
"**/*.rs",
"**/*.c",
"**/*.cpp",
"**/*.h",
"**/*.hpp",
"**/*.py",
"**/*.js",
"**/*.ts",
];
let mut all_files = Vec::new();
for pattern in &patterns {
let full_pattern = root.join(pattern);
let matches: Vec<_> = glob(&full_pattern.to_string_lossy())
.unwrap()
.filter_map(|e| e.ok())
.collect();
all_files.extend(matches);
}
all_files.sort();
all_files.dedup();
all_files
}
fn main() {
let files = find_source_files(Path::new("."));
println!("Found {} source files:", files.len());
for file in files.iter().take(10) {
println!(" {}", file.display());
}
if files.len() > 10 {
println!(" ... and {} more", files.len() - 10);
}
}use glob::glob;
use std::fs;
use std::path::Path;
fn clean_build_artifacts() -> Result<usize, Box<dyn std::error::Error>> {
let patterns = [
"target/",
"**/*.o",
"**/*.obj",
"**/*.exe",
"**/*.dll",
"**/*.so",
"**/*.dylib",
"**/*.class",
"**/__pycache__/",
"**/node_modules/",
];
let mut removed = 0;
for pattern in &patterns {
for entry in glob(pattern)? {
if let Ok(path) = entry {
if path.exists() {
if path.is_dir() {
fs::remove_dir_all(&path)?;
} else {
fs::remove_file(&path)?;
}
println!("Removed: {}", path.display());
removed += 1;
}
}
}
}
Ok(removed)
}
fn main() {
match clean_build_artifacts() {
Ok(count) => println!("\nCleaned {} items", count),
Err(e) => println!("Error: {}", e),
}
}use glob::glob;
use std::path::PathBuf;
#[derive(Debug)]
struct ConfigFiles {
project: Option<PathBuf>,
cargo: Option<PathBuf>,
docker: Option<PathBuf>,
env: Vec<PathBuf>,
}
fn find_config_files() -> ConfigFiles {
ConfigFiles {
project: glob("*.toml")
.unwrap()
.filter_map(|e| e.ok())
.find(|p| p.file_name() == Some(std::ffi::OsStr::new("Cargo.toml"))),
cargo: glob("**/Cargo.toml")
.unwrap()
.filter_map(|e| e.ok())
.next(),
docker: glob("Dockerfile*")
.unwrap()
.filter_map(|e| e.ok())
.next(),
env: glob(".env*")
.unwrap()
.filter_map(|e| e.ok())
.collect(),
}
}
fn main() {
let configs = find_config_files();
println!("Configuration files:");
if let Some(p) = configs.project {
println!(" Project: {}", p.display());
}
if let Some(p) = configs.cargo {
println!(" Cargo: {}", p.display());
}
if let Some(p) = configs.docker {
println!(" Docker: {}", p.display());
}
for env in &configs.env {
println!(" Env: {}", env.display());
}
}use glob::glob;
use std::fs;
use std::path::Path;
fn batch_rename(
pattern: &str,
new_extension: &str,
dry_run: bool,
) -> Result<usize, Box<dyn std::error::Error>> {
let mut renamed = 0;
for entry in glob(pattern)? {
let path = entry?;
if !path.is_file() {
continue;
}
let new_path = path.with_extension(new_extension);
if dry_run {
println!("Would rename: {} -> {}",
path.display(), new_path.display());
} else {
fs::rename(&path, &new_path)?;
println!("Renamed: {} -> {}",
path.display(), new_path.display());
}
renamed += 1;
}
Ok(renamed)
}
fn main() {
// Dry run first
println!("=== Dry run ===");
match batch_rename("*.txt", "md", true) {
Ok(count) => println!("Would rename {} files", count),
Err(e) => println!("Error: {}", e),
}
// Uncomment to actually rename:
// batch_rename("*.txt", "md", false).unwrap();
}use glob::glob;
use std::fs;
use std::collections::HashMap;
fn calculate_extension_sizes(pattern: &str) -> HashMap<String, u64> {
let mut sizes: HashMap<String, u64> = HashMap::new();
for entry in glob(pattern).unwrap() {
if let Ok(path) = entry {
if let Ok(metadata) = fs::metadata(&path) {
if metadata.is_file() {
let ext = path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("no_ext")
.to_string();
*sizes.entry(ext).or_insert(0) += metadata.len();
}
}
}
}
sizes
}
fn main() {
let sizes = calculate_extension_sizes("**/*");
// Sort by size descending
let mut sorted: Vec<_> = sizes.iter().collect();
sorted.sort_by(|a, b| b.1.cmp(a.1));
println!("File sizes by extension:");
for (ext, size) in sorted {
println!(" .{:>10}: {:>10} bytes", ext, size);
}
}use glob::{Pattern, MatchOptions};
fn main() {
// Compile pattern once for repeated use
let pattern = Pattern::new("src/**/*.rs").unwrap();
// Test if paths match the pattern
let test_paths = [
"src/main.rs",
"src/lib/mod.rs",
"tests/test.rs",
"build.rs",
];
for path in test_paths {
if pattern.matches(path) {
println!("Matches: {}", path);
} else {
println!("No match: {}", path);
}
}
// Pattern with options
let options = MatchOptions {
case_sensitive: false,
require_literal_separator: false,
require_literal_leading_dot: false,
};
let case_insensitive = Pattern::new("*.RS").unwrap();
println!("\nCase-insensitive matching:");
for path in test_paths {
if case_insensitive.matches_with(path, options) {
println!("Matches: {}", path);
}
}
// Match path components
let pattern = Pattern::new("src/*/mod.rs").unwrap();
println!("\nComponent matching:");
println!(" matches: {}", pattern.matches("src/utils/mod.rs"));
println!(" matches: {}", pattern.matches("src/mod.rs"));
}use glob::glob;
fn main() {
// Escape special glob characters in literal strings
let filename = "report[2024].txt";
// Wrong: [ would be interpreted as character class
// let pattern = format!("*{}*", filename); // Dangerous!
// Correct: escape the filename
let escaped = glob::Pattern::escape(filename);
let pattern = format!("*{}*", escaped);
println!("Escaped pattern: {}", pattern);
// This would match files containing "report[2024].txt" literally
for entry in glob(&pattern).unwrap() {
if let Ok(path) = entry {
println!("Found: {}", path.display());
}
}
}use glob::glob;
use std::path::Path;
fn find_duplicates_by_name(dir: &Path) -> Vec<(&str, Vec<std::path::PathBuf>)> {
let mut file_map: std::collections::HashMap<String, Vec<std::path::PathBuf>> =
std::collections::HashMap::new();
for entry in glob(&format!("{}/**/*", dir.display())).unwrap() {
if let Ok(path) = entry {
if path.is_file() {
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
file_map
.entry(name.to_string())
.or_default()
.push(path);
}
}
}
}
file_map
.into_iter()
.filter(|(_, paths)| paths.len() > 1)
.collect()
}
fn main() {
let duplicates = find_duplicates_by_name(Path::new("."));
if duplicates.is_empty() {
println!("No duplicate filenames found");
} else {
println!("Duplicate filenames:");
for (name, paths) in duplicates {
println!("\n{}:", name);
for path in paths {
println!(" {}", path.display());
}
}
}
}use glob::glob;
use std::fs;
fn analyze_project() {
println!("=== Project Analysis ===
");
// Count files by type
let rust_files = glob("**/*.rs").unwrap().filter_map(|e| e.ok()).count();
let toml_files = glob("**/*.toml").unwrap().filter_map(|e| e.ok()).count();
let md_files = glob("**/*.md").unwrap().filter_map(|e| e.ok()).count();
println!("Rust files: {}", rust_files);
println!("TOML files: {}", toml_files);
println!("Markdown files: {}", md_files);
// Total size of Rust files
let total_size: u64 = glob("**/*.rs")
.unwrap()
.filter_map(|e| e.ok())
.filter_map(|p| fs::metadata(p).ok())
.map(|m| m.len())
.sum();
println!("Total Rust code size: {} bytes", total_size);
// Find test files
let test_files: Vec<_> = glob("**/*test*.rs")
.unwrap()
.filter_map(|e| e.ok())
.collect();
println!("\nTest files:");
for file in test_files {
println!(" {}", file.display());
}
// Find modules
let modules: Vec<_> = glob("**/mod.rs")
.unwrap()
.filter_map(|e| e.ok())
.collect();
println!("\nModule files:");
for file in modules {
println!(" {}", file.display());
}
}
fn main() {
analyze_project();
}glob(pattern) returns an iterator over matching paths* matches any sequence of characters (except /)? matches exactly one character[abc] matches any character in the set[!abc] matches any character NOT in the set** matches any sequence of directories recursivelyglob_with(options) allows customizing case sensitivity and moreGlobResult<PathBuf>, handle both IO and pattern errorsPattern::new() compiles patterns for repeated matchingPattern::escape() escapes glob special characters.filter_map(|e| e.ok()) to collect only successful matches