faf
fast as f*#! filename search.
faf is a parallel filesystem search tool written in Rust.
It matches filenames only and stays out of file contents.
Pair it with delfaf to mass-delete whatever the last faf run found.
why this exists
Most search tools scan file contents, allocate per entry, or stall on output locks. faf walks the tree on every core, matches raw filename bytes, and writes results in large batches.
install
cargo
cargo install fafind
The crate is named fafind because faf was already taken on crates.io.
It installs the faf binary.
packages
# Arch (AUR)
yay -S faf-bin
# Homebrew
brew install eof0/tap/faf
AUR packaging notes: packaging/aur/README.md.
from source
Requires Rust 1.88 or newer (edition 2024).
git clone https://github.com/eof0/faf
cd faf
cargo build --release
sudo cp target/release/faf /usr/local/bin/
Unix only - Linux and macOS. The build fails on Windows by design.
usage
faf <target> [root]
root defaults to /.
matching modes
default (stem match)
Matches the filename without its extension.
An extension on the query is ignored, so faf main.rs is the same as faf main.
faf main .
Matches main.rs and main.go.
Does not match domain.rs.
substring (-s)
faf -s foo .
Matches foobar.txt, myfoo.rs, prefoo, and notes.foo.
The whole filename is searched, extension included.
exact (-p)
faf -p Makefile .
Matches Makefile only.
-s and -p cannot be combined.
terminal colors
When stdout is a terminal and -0 is not set, matches are highlighted:
| Color | Applies to |
|---|---|
| Dim | Path before the filename |
| Green | The matched part of the name |
| Bold green | Stem in -p mode |
| Yellow | Extension in stem and -p modes |
| Orange | Non-matching parts of the name in -s mode |
--color auto is the default.
Use --color always or --color never to override.
what gets walked
- hidden files and directories are included
- symlinks are never followed
.gitignoreis ignored unless--gitignoreis passed
flags
case insensitive (-i)
faf -i readme .
ASCII names use a byte-wise fold. Non-ASCII names and queries fall back to full Unicode case folding.
limit depth
faf --max-depth 3 main .
exclude directories
faf --exclude target,node_modules main .
Matches directory names anywhere below the root. The root itself is never excluded.
respect .gitignore
faf --gitignore main .
filter by type (-f / -d / --type)
faf -f main . # files only
faf -d src . # directories only
faf --type a main . # any (default)
-f and -d stack with the other short flags, so -sd, -pf, and -id all work.
They cannot be combined with each other or contradict an explicit --type.
null-separated output (-0)
faf -0 main . | xargs -0 rm
Disables color.
verbose (-v)
Prints [SCAN], [SKIP], and [ERROR] lines to stderr.
Matches still go to stdout, but as [MATCH] <path> lines with color and -0 ignored.
Use it to trace a walk, not to feed another program.
quiet (-q)
Suppresses the summary line on stderr.
performance
- Every core walks the tree through a work-stealing scheduler.
- Filenames are matched as raw bytes, with no UTF-8 decoding or per-entry allocation in the matcher.
- Substring search uses a prebuilt SIMD
memmemfinder. - Non-ASCII names take a cold Unicode path only when
-ineeds it. - Each worker batches output into a private buffer and writes it in 64 KiB chunks, whether stdout is a terminal or a pipe.
- When the reader closes the pipe, as in
faf -s foo / | head, the walk stops instead of scanning the rest of the disk.
output
- newline-separated by default, NUL-separated with
-0 - raw OS bytes, no re-encoding
- the matched paths are also written to a cache file for
delfaf, always NUL-separated
The cache path is $FAF_LAST, else $XDG_CACHE_HOME/faf/last, else ~/.cache/faf/last.
A failed cache write never changes the exit code.
exit codes
0 = matches found
1 = no matches
2 = invalid usage
what this is NOT
- not a content search tool (use
greporrg) - not a fuzzy matcher
changelog
See CHANGELOG.md.
license
MIT