find is one of the most capable commands on a Unix system and one of the least pleasant to type. Its syntax predates the conventions everything else follows, which is why the simplest possible search — “find files called config” — needs a path, a predicate and a quoted pattern in a specific order.

find . -type f -name "*config*"

# fd, doing the same thing
fd config

That is the pitch in two lines. Whether it is worth adopting depends on what you use find for.

The short verdict

Use fd when you are looking for something. Interactive searching, where you half-remember a filename and want it now, is where the shorter syntax pays for itself several times a day.

Use find when you are doing something to what you found — and always in scripts. Its predicate system handles permissions, ownership, inode links and complex boolean logic that fd deliberately does not attempt.

The split is cleaner here than with most tool pairs. fd is a better search; find is a better batch operation.

Installing it, and the naming trap

# Debian, Ubuntu - note the package name
sudo apt install fd-find

# Fedora, RHEL, Rocky, Alma
sudo dnf install fd-find

# Arch
sudo pacman -S fd

# macOS
brew install fd

On Debian and Ubuntu the command is installed as fdfind, not fd. The name fd was already taken by an unrelated package, so Debian renamed the binary. Every tutorial you read will say fd and nothing will happen when you type it.

# Confirm what you actually got
which fdfind

# Make a personal link so tutorials work
mkdir -p ~/.local/bin
ln -s $(which fdfind) ~/.local/bin/fd

# Or just alias it, in ~/.bashrc
alias fd=fdfind

The symlink is the better of the two, because an alias only exists in interactive shells — scripts and xargs will not see it. ~/.local/bin is the conventional place for it, as covered in the Linux filesystem. The same trap applies to bat, which Debian installs as batcat for the same reason.

What fd changes

findfd
Pattern matchingGlob, must be quotedRegex, substring by default
Searches current directory by defaultNeeds .Yes
Case handlingCase sensitive, or -inameSmart case
Respects .gitignoreNoYes
Skips hidden filesNoYes
ParallelNoYes
Colour outputNoYes
Permission errorsPrinted as noiseSilently skipped
Filter by owner, permissions, inodeYesLimited
Complex boolean logicYesNo
Installed everywhereYesNo

Two of those deserve expanding. Permission errors being suppressed is a bigger quality-of-life change than it sounds — searching from / with find buries real results under hundreds of “Permission denied” lines, which is why every example you have seen ends in 2>/dev/null. fd just does not print them.

And the gitignore behaviour cuts both ways, exactly as it does with ripgrep. Searching a project for a build artefact will come back empty, because build artefacts are precisely what gitignore lists:

# Include ignored files
fd --no-ignore pattern

# Include hidden files
fd --hidden pattern

# Both - the "why can't it find my file" escape hatch
fd -HI pattern

Translating what you already know

You wantfindfd
Files matching a namefind . -name "*.log"fd -e log
Case insensitivefind . -iname "*readme*"fd readme
Files onlyfind . -type ffd -t f
Directories onlyfind . -type dfd -t d
Symlinks onlyfind . -type lfd -t l
Limit the depthfind . -maxdepth 2fd -d 2
Search a specific directoryfind /var/log -name "*.gz"fd -e gz . /var/log
Modified in the last dayfind . -mtime -1fd --changed-within 1d
Older than a weekfind . -mtime +7fd --changed-before 1w
Larger than 100 MBfind . -size +100Mfd -S +100M
Exclude a directoryfind . -path ./node_modules -prune -o -printfd -E node_modules
Run a command on each resultfind . -name "*.txt" -exec gzip {} \;fd -e txt -x gzip
Run once with all resultsfind . -name "*.txt" -exec ls {} +fd -e txt -X ls

Note the argument order in the “specific directory” row — fd takes the pattern first and the path second, the opposite of find. fd -e gz . /var/log reads as “extension gz, any name, under /var/log”.

-x and -X are nicer than -exec

find’s -exec needs a trailing \; or + that everyone forgets, and it runs one process per file. fd’s equivalents are shorter and run in parallel:

# Once per file, in parallel across cores
fd -e jpg -x convert {} {.}.png

# Once, with every result as arguments
fd -e log -X rm

# Placeholders fd understands
#   {}   the full path
#   {.}  path without the extension
#   {/}  the filename only
#   {//} the parent directory

The {.} placeholder is genuinely useful and has no find equivalent — converting a directory of images while keeping their base names is a one-liner rather than a shell loop.

A warning that applies to both: anything of the shape fd -X rm or find -exec rm deletes without confirmation. Run the search on its own first, read the list, and only then add the action. Deleting the wrong thing here is fast and total.

Where find is still the right answer

fd covers the common cases. find covers all of them, and the gap shows up in exactly the situations where you are doing systems work rather than looking for a file.

# Everything owned by a user - before deleting their account
sudo find / -user alice -not -path '/proc/*'

# World-writable files, a real security audit
sudo find / -type f -perm -o+w -not -path '/proc/*'

# Files with the setuid bit set
sudo find / -perm -4000 -type f

# Empty directories
find . -type d -empty

# Boolean logic: shell scripts NOT under vendor/
find . -name "*.sh" -not -path "./vendor/*"

# Fix permissions across a tree, directories and files differently
find /var/www -type d -exec chmod 755 {} \;
find /var/www -type f -exec chmod 644 {} \;

Those -perm and -user searches have no fd equivalent worth using, and they are the ones that matter when you are auditing a machine or cleaning up after a departed colleague. The permissions pair at the end is a standard step after any file transfer — see migrating a server.

The other reasons are the familiar ones. find is on every machine, including the container with no package manager and the server you are logged into during an incident. It is POSIX-specified, so a script using it behaves the same on Linux, BSD and macOS. And it will not silently skip your build directory because git ignores it.

Same rule as everywhere else in this section: learn find, install fd. Reach for fd when you are hunting; reach for find when you are operating.

Two things worth knowing

fd pairs well with fzf. Piping fd into a fuzzy finder turns “where is that file” into a few keystrokes:

# Pick a file interactively, then open it
fd -t f | fzf | xargs -r $EDITOR

And locate still exists. It searches a database updated nightly rather than the live filesystem, which makes it faster than both for “where on this whole machine is that file” — at the cost of not knowing about anything created since the last update. sudo updatedb refreshes it.

Quick reference

You wantCommand
Find by partial namefd name
By extensionfd -e log
Directories onlyfd -t d name
In a specific directoryfd pattern /var/log
Include hidden and ignoredfd -HI pattern
Changed in the last dayfd --changed-within 1d
Bigger than 100 MBfd -S +100M
Run a command per resultfd -e txt -x gzip
Exclude a directoryfd -E node_modules pattern
Debian: it is calledfdfind

Related reading