find has shipped with Unix systems for decades. It’s on every server you’ll ever log into, it can test almost any file attribute, and its syntax feels like it came from a different century. fd is a newer tool that covers the most common part of find’s job with shorter commands and friendlier defaults. This page explains what fd does differently, where those differences help or hurt, and when you should still type find.
1. What problem fd solves
Most file searches are simple: "where is that config file?", "every Markdown file in this repo", "what changed in the last day?". With find, even simple questions need careful syntax. The path comes first, name patterns need quoting, case-insensitive matching is a separate flag (-iname), and running a command on each result means remembering -exec … {} \; or {} +.
fd changes the defaults:
- The pattern comes first and the path is optional.
fd configsearches the current directory, recursively, for names containing "config". - The pattern is a regular expression, not a shell glob. This catches most people out, so there’s more on it below.
- Smart case. A lower-case pattern matches case-insensitively. As soon as the pattern contains an upper-case letter, matching becomes case-sensitive.
- It skips hidden files and anything your
.gitignoreexcludes, sonode_modules/and build output don’t bury the results. - Output is colored using your
LS_COLORS, and directories are searched in parallel.
The fd README documents these defaults and includes a benchmark that it describes as "one particular benchmark on one particular machine". Don’t expect a specific speed-up on yours. If speed matters to you, measure it on your own tree with hyperfine.
2. Installing it
| System | Command | Binary name |
|---|---|---|
| Debian / Ubuntu | sudo apt install fd-find | fdfind |
| Fedora | sudo dnf install fd-find (from the fd README, not tested) | fd |
| Arch | sudo pacman -S fd (from the fd README, not tested) | fd |
| macOS (Homebrew) | brew install fd (from the fd README, not tested) | fd |
Why it’s called fdfind on Debian and Ubuntu
On Debian and Ubuntu the name /usr/bin/fd already belongs to another package, fdclone (a clone of the FD file manager), so the fd package installs its binary as fdfind. The fix is a symlink in your personal bin directory:
mkdir -p ~/.local/bin ln -s "$(command -v fdfind)" ~/.local/bin/fd
Use a symlink rather than alias fd=fdfind. Aliases only exist in your interactive shell, so scripts and cron jobs don’t see them.
~/.local/bin also has to be on your PATH. On Debian and Ubuntu, the default ~/.profile adds it, but only at login and only if the directory already existed when you logged in. After creating it, either log out and back in, or add it to the current shell yourself:
export PATH="$HOME/.local/bin:$PATH"
zsh doesn’t read ~/.profile. If zsh is your login shell, put that export line in ~/.zshrc (or ~/.zprofile).
In a script that has to run on several distributions, don’t rely on either name being present. Check for both:
FD=$(command -v fd || command -v fdfind) || { echo "fd not installed" >&2; exit 1; }
"$FD" -e conf . /etc
3. fd and find side by side
| Task | find | fd |
|---|---|---|
| Names containing "config" | find . -iname '*config*' | fd config |
All .log files (any case) | find . -iname '*.log' | fd -e log |
| Only regular files | find . -type f | fd -t f |
| Only directories, one level down | find . -mindepth 1 -maxdepth 1 -type d | fd -t d -d 1 |
| Changed in the last 24 hours | find . -mmin -1440 | fd --changed-within 24h |
| Empty files and directories | find . -empty | fd -t e |
| Empty regular files only | find . -type f -empty | fd -t e -t f |
| Run one command with all results | find . -name '*.txt' -exec wc -c {} + | fd -e txt -X wc -c |
| Run a command per result | find . -name '*.txt' -exec wc -c {} \; | fd -e txt -x wc -c |
| Include hidden and ignored files | (that’s find’s default) | fd -u … (or -H / -I) |
A few notes on the table:
-eignores case.fd -e logalso findsMy File.LOG. That’s why the find equivalent uses-iname, not-name.- The defaults differ.
findsearches everything, including.git/, hidden directories, andnode_modules/. fd skips hidden and git-ignored paths. When you compare their output, add-uto fd, or you aren’t comparing like with like. -xruns in parallel. fd’s-xstarts commands for several results at once. That’s fine for independent jobs such as checksums or image conversion. For anything that writes to a shared file or must run in order, use-X(one batch), or limit fd to one job at a time. Note that-j 1has to come before-x, because everything after-xis treated as part of the command:fd -j 1 -e txt -x cmd.
4. Workflows that are actually useful
Find a file when you only remember part of the name.
fd nginx /etc
Searches /etc for anything with "nginx" in its name. Add -t f to skip directories.
List what you touched today in a project.
fd -t f --changed-within 24h
Durations like 2d, 12h, and 30min work, and so do dates. This replaces find -mmin arithmetic and -newermt.
Act on files whose names contain spaces, safely.
fd -0 -e txt | xargs -0 wc -c
-0 separates results with a NUL byte, which xargs -0 reads, so file names with spaces or even newlines get through intact. fd’s own -x and -X pass each path as a separate argument, so they’re safe too.
Convert images using placeholders.
fd -e jpg -x magick {} {.}.png
(ImageMagick command from its documentation, not tested.) {} is the full path, {.} the path without its extension, {/} the file name, {//} the parent directory, and {/.} the file name without its extension. Running fd main.rs -x echo '{} {/} {//} {.} {/.}' printed ./src/main.rs main.rs ./src src/main main. Quote the placeholders if your shell treats braces specially.
ImageMagick 7 renamed its main command to magick. The old convert name still works on many systems but is deprecated. If you’re on ImageMagick 6, which some older distributions still ship, magick won’t exist and you need convert {} {.}.png instead. magick -version tells you which one you have.
Feed fzf. Adapted from the fd and fzf READMEs (not tested):
export FZF_DEFAULT_COMMAND='fd --type file --follow --hidden --exclude .git' export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
This lists files the way fd sees them: symlinks followed, hidden files included, .git and git-ignored paths left out. Two things have to be in place for it to work:
- The
fdname must resolve. On Debian and Ubuntu, create thefdfindsymlink from section 2 first. Otherwise fzf runs a command that doesn’t exist and shows an empty list. FZF_CTRL_T_COMMANDonly matters once fzf’s key bindings are loaded. With fzf 0.48 or newer, add one line to your shell’s config: basheval "$(fzf --bash)", zshsource <(fzf --zsh), fishfzf --fish | source. Older packaged versions, such as fzf 0.38 on Debian 12 and 0.44 on Ubuntu 24.04, don’t have those options. There, use the script shipped with the package, e.g.source /usr/share/doc/fzf/examples/key-bindings.bashfor bash.
5. Pitfalls
"regex parse error" on a pattern that looks fine. fd patterns are regular expressions. In a regex, * means "repeat the previous thing", so a leading * is an error:
$ fd '*.log'
[fd error]: regex parse error:
*.log
^
error: repetition operator missing expression
To find log files, use fd -e log (add -u to include hidden and ignored ones: fd -u -e log), or a real regex such as fd '\.log$'. Always single-quote patterns so the shell doesn’t expand them first.
Glob mode matches the whole name. -g switches to shell-style globs, but a glob has to match the entire file name. fd -g '*.log' works, while fd -g log only finds files named exactly log. A glob that contains a / is matched against the path, which needs -p as well. -p compares against the full absolute path, so start the glob with **/: fd -p -g '**/src/*.log' works, but fd -p -g 'src/*.log' finds nothing. Note that * doesn’t cross directory separators, so that pattern only matches files sitting directly in src/. To include nested folders, use fd -p -g '**/src/**/*.log'.
"fd doesn’t find my file." It’s nearly always the defaults. The file is hidden (add -H), git-ignored (add -I), or both (-u on fd 8.4.0 and newer). fd also matches only the file name by default. To match against the whole path, like find -path, add -p: fd -p 'src/.*\.rs$'.
Case surprises. fd readme finds README.md, but fd Readme doesn’t, because the capital letter turns on case-sensitive matching. Force the behaviour you want with -i (ignore case) or -s (case-sensitive).
-H shows the inside of .git/. On fd 10.x (and before 9.0.0), -H lists everything inside .git/. In a small test repository that was dozens of files. Exclude it with fd -H -E .git. For a permanent exclude, put patterns in a .fdignore file at the top of the project. According to the fd README, a global ignore file at ~/.config/fd/ignore applies to every search (from the fd docs, not tested).
Exit codes in scripts. On 10.2.0, a search with no results still exits 0, so if fd …; then won’t tell you whether anything matched. Use -q (--quiet). It prints nothing and exits 0 if there was a match and 1 if there wasn’t.
if fd -q -e orig; then echo "leftover merge files (*.orig) in this tree"; fi
Patterns that start with a dash are read as options. Put -- before them: fd -- '-weird'.
Deleting. fd can feed rm, but always run the search on its own first and add the action afterwards. -X rm -i asks before each file:
fd -H '^\.DS_Store$' -t f # look first fd -H '^\.DS_Store$' -t f -X rm -i # then delete, with confirmation
6. When to use find instead
- Portability.
findis on every POSIX system, including minimal containers, rescue shells, and old servers. If a script has to run anywhere, write it withfind. - Tests fd doesn’t have. Permission bits (
-perm), link counts (-links), inode numbers,-newercompared with another file, and complex-o/-aboolean expressions arefindterritory. fd covers type, size (-S), owner (-o), and modification time, but not the long tail. - Searching everything on purpose. Audits, forensics, or cleaning up a whole filesystem, where skipping hidden or ignored files would hide exactly what you’re looking for.
fd -ugets you most of the way, butfindmakes "everything" the default, and that suits the job.
For everyday interactive searching in projects and home directories, fd is shorter to type and harder to get wrong. Keep find for portable scripts and for the questions fd can’t ask.
Related
- ripgrep: fd finds files by name, ripgrep searches inside them.
fd -e py -X rg 'TODO'combines the two. - fzf: interactive picking on top of fd’s output.
Sources
- fd README (usage, install, fzf integration, ignore files): https://github.com/sharkdp/fd/blob/master/README.md
- fd CHANGELOG: 8.4.0 (
-ubecame-HI), 9.0.0 (-Hignores.git/), 10.0.0 (that change reverted): https://github.com/sharkdp/fd/blob/master/CHANGELOG.md - fd releases: https://github.com/sharkdp/fd/releases
- fzf README, "Setting up shell integration": https://github.com/junegunn/fzf#setting-up-shell-integration
- fzf CHANGELOG, 0.48.0 (shell integration built into the binary:
--bash,--zsh,--fish): https://github.com/junegunn/fzf/blob/master/CHANGELOG.md - GNU find manual: https://www.gnu.org/software/findutils/manual/html_mono/find.html
- ImageMagick command-line tools (
magick): https://imagemagick.org/script/command-line-tools.php
Updated 2026-10-24: rewritten. Fixed an example (fd -u '*.log', now fd -u -e log) that failed with a regex error.