Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,9 +329,9 @@ Options:
-p, --full-path Search full abs. path (default: filename only)
-d, --max-depth <depth> Set maximum search depth (default: none)
-E, --exclude <pattern> Exclude entries that match the given glob pattern
-t, --type <filetype> Filter by type: file (f), directory (d/dir), symlink (l),
executable (x), empty (e), socket (s), pipe (p), char-device
(c), block-device (b)
-t, --type <filetype> Filter by type: file (f), directory (d/dir), leaf (leafdir),
symlink (l), executable (x), empty (e), socket (s), pipe (p),
char-device (c), block-device (b)
-e, --extension <ext> Filter by file extension
-S, --size <size> Limit results based on the size of files
--changed-within <date|dur> Filter by file modification time (newer than)
Expand Down
1 change: 1 addition & 0 deletions contrib/completion/_fd
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ _fd() {
fd_types=(
{f,file}'\:"regular files"'
{d,directory}'\:"directories"'
{leaf,leafdir}'\:"directories without subdirectories"'
{l,symlink}'\:"symbolic links"'
{e,empty}'\:"empty files or directories"'
{x,executable}'\:"executable (files)"'
Expand Down
4 changes: 4 additions & 0 deletions doc/fd.1
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ Filter search by type:
regular files
.IP "d, dir, directory"
directories
.IP "leaf, leafdir"
directories without subdirectories
.IP "l, symlink"
symbolic links
.IP "b, block-device"
Expand Down Expand Up @@ -247,6 +249,8 @@ Examples:
- Find empty directories:
fd --type empty --type directory
fd -te -td
- Find leaf directories (directories without subdirectories):
fd --type leaf
.RE
.TP
.BI "\-e, \-\-extension " ext
Expand Down
6 changes: 5 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ pub struct Opts {
/// Filter the search by type:
/// {n} 'f' or 'file': regular files
/// {n} 'd' or 'dir' or 'directory': directories
/// {n} 'leaf' or 'leafdir': directories without subdirectories
/// {n} 'l' or 'symlink': symbolic links
/// {n} 's' or 'socket': socket
/// {n} 'p' or 'pipe': named pipe (FIFO)
Expand Down Expand Up @@ -353,7 +354,7 @@ pub struct Opts {
hide_possible_values = true,
value_enum,
help = "Filter by type: file (f), directory (d/dir), symlink (l), \
executable (x), empty (e), socket (s), pipe (p), \
leaf (leafdir), executable (x), empty (e), socket (s), pipe (p), \
char-device (c), block-device (b)",
long_help
)]
Expand Down Expand Up @@ -778,6 +779,9 @@ pub enum FileType {
File,
#[value(alias = "d", alias = "dir")]
Directory,
/// A directory that has no subdirectories
#[value(alias = "leafdir", alias = "leaf-dir", alias = "leaf")]
Leaf,
#[value(alias = "l")]
Symlink,
#[value(alias = "b")]
Expand Down
37 changes: 36 additions & 1 deletion src/filesystem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::ffi::OsStr;
use std::fs;
use std::io;
#[cfg(any(unix, target_os = "redox"))]
use std::os::unix::fs::FileTypeExt;
use std::os::unix::fs::{FileTypeExt, MetadataExt};
use std::path::{Path, PathBuf};

use normpath::PathExt;
Expand Down Expand Up @@ -59,6 +59,41 @@ pub fn is_empty(entry: &dir_entry::DirEntry) -> bool {
}
}

pub fn is_leaf_directory(entry: &dir_entry::DirEntry) -> bool {
if !entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false) {
return false;
}

#[cfg(any(unix, target_os = "redox"))]
if let Some(metadata) = entry.metadata() {
let link_count = metadata.nlink();

if link_count == 2 {
return true;
}

if link_count > 2 {
return false;
}
}

if let Ok(mut entries) = fs::read_dir(entry.path()) {
for child in entries.by_ref() {
match child {
Ok(dirent) => {
if dirent.file_type().map(|ft| ft.is_dir()).unwrap_or(false) {
return false;
}
}
Err(_) => return false,
}
}
true
} else {
false
}
}

#[cfg(any(unix, target_os = "redox"))]
pub fn is_block_device(ft: fs::FileType) -> bool {
ft.is_block_device()
Expand Down
6 changes: 5 additions & 1 deletion src/filetypes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use faccess::PathExt;
pub struct FileTypes {
pub files: bool,
pub directories: bool,
pub leaf_directories: bool,
pub symlinks: bool,
pub block_devices: bool,
pub char_devices: bool,
Expand All @@ -20,8 +21,11 @@ pub struct FileTypes {
impl FileTypes {
pub fn should_ignore(&self, entry: &dir_entry::DirEntry) -> bool {
if let Some(ref entry_type) = entry.file_type() {
let is_dir = entry_type.is_dir();

(!self.files && entry_type.is_file())
|| (!self.directories && entry_type.is_dir())
|| (!self.directories && is_dir)
|| (self.leaf_directories && is_dir && !filesystem::is_leaf_directory(entry))
|| (!self.symlinks && entry_type.is_symlink())
|| (!self.block_devices && filesystem::is_block_device(*entry_type))
|| (!self.char_devices && filesystem::is_char_device(*entry_type))
Expand Down
4 changes: 4 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,10 @@ fn construct_config(mut opts: Opts, pattern_regexps: &[String]) -> Result<Config
match value {
File => file_types.files = true,
Directory => file_types.directories = true,
Leaf => {
file_types.leaf_directories = true;
file_types.directories = true;
}
Symlink => file_types.symlinks = true,
Executable => {
file_types.executables_only = true;
Expand Down
27 changes: 27 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1293,6 +1293,33 @@ fn test_type() {
te.assert_output(&["--type", "l"], "symlink");
}

#[test]
fn test_type_leaf() {
let te = TestEnv::new(
&[
"parent/child/grandchild",
"parent/child/sibling",
"parent/solo",
],
&["parent/child/file.txt"],
);

te.assert_output(
&["--type", "leaf"],
"parent/child/grandchild/
parent/child/sibling/
parent/solo/",
);

te.assert_output(
&["--type", "leafdir", "--type", "file"],
"parent/child/file.txt
parent/child/grandchild/
parent/child/sibling/
parent/solo/",
);
}

/// Test `--type executable`
#[cfg(unix)]
#[test]
Expand Down