You keep a line like find . -name '*.py' -not -path './node_modules/*' in a notes file because you never remember its order, and you still get node_modules noise when you forget the -not -path part. fd is a small program that finds files by name with a shorter command, skips what your .gitignore already ignores, and runs a command on every result.

This guide covers the 20% of fd that does 80% of the work: matching names with plain text, regular expressions, and globs; filtering by extension, type, depth, and size; ignore rules and hidden files; and running commands with -x and -X. A searchable command reference and a set of videos, books, and online resources close the article.

What You’ll Learn

  • What fd does, and when find still fits better.
  • How to find files by name with plain text, regular expressions, and globs.
  • How to narrow results by extension, file type, depth, and size.
  • Why fd skips some files, and how to see them with -H, -I, and -u.
  • How to run a command on every result with -x and -X.
  • Where to go next to learn more about fd.

When to Use fd or find

fd searches the current folder and every folder below it for names that match a pattern. It prints paths relative to where you run it, skips hidden files and anything your ignore files list, and treats the pattern as a regular expression unless you ask for a glob. Those defaults fit the search you run most days: find a file in a project by part of its name.

Each find command below has a shorter fd equivalent:

  • find . -name '*cart*' is fd cart.
  • find . -name '*.py' is fd -e py.
  • find . -type d is fd -t d.
  • find . -maxdepth 1 -type f is fd -t f -d 1.
  • find . -name '*.py' -not -path './node_modules/*' is fd -e py, when a .gitignore lists node_modules/.
  • find . -name '*.jpeg' -exec mv {} ... \; is fd -e jpeg -x mv {} {.}.jpg.

Keep find for the jobs fd leaves out:

  • Matching by modification time relative to another file (-newer) or by exact permission bits (-perm).
  • Scripts that must run on any POSIX system, where fd may not be installed.
  • Complex conditions that combine -o, !, and grouping in one expression.
  • Running a different action per match with several -exec clauses.

fd works the same in any shell. The examples below run in zsh, from a home folder that holds a few practice files: notes.txt, a logs/ folder, a photos/ folder, and a small web project in shop/ that is a git repository with a .gitignore.

Find Files by Name

Give fd part of a name. It matches anywhere in the file or folder name, below the current folder:

fd cart shop
shop/src/cart.py
shop/src/test_cart.py

The second argument is the folder to search. Leave it out and fd searches the current folder. fd matches only the last part of each path, the file or folder name, so the folders above a file never cause a match.

fd uses smart case. A pattern in lowercase ignores case, and a pattern with any uppercase letter matches case exactly. fd App shop prints nothing, because no name contains App with a capital A. Add -i to ignore case anyway, or -s to match case even for a lowercase pattern:

fd -i readme shop
shop/README.md

Use Regex and Glob Patterns

The pattern is a regular expression. ^ anchors the start of the name and $ the end, so ^test_ finds files whose names start with test_:

fd '^test_' shop
shop/src/test_cart.py

Quote patterns with special characters, so the shell passes them to fd unchanged. Escape a literal dot with a backslash, or use -F to treat the whole pattern as fixed text:

fd 'cart\.py$' shop
shop/src/cart.py
shop/src/test_cart.py

Use -g when a shell-style glob reads better than a regular expression. A glob must match the whole name:

fd -g '*.py' shop
shop/src/__init__.py
shop/src/app.py
shop/src/cart.py
shop/src/test_cart.py

To match against the full path instead of the name, add -p. Then fd -p 'src/t' shop matches the templates folder and test_cart.py, because both paths contain src/t.

Filter by Extension, Type, Depth, and Size

-e filters by file extension, without the dot. It ignores case, so -e jpg also finds .JPG files:

fd -e jpg
photos/IMG_2043.JPG

To list every file with an extension, use . as the pattern. It matches any name:

fd -e py . shop
shop/src/__init__.py
shop/src/app.py
shop/src/cart.py
shop/src/test_cart.py

Repeat -e to match several extensions, as in fd -e html -e css.

-t filters by type: f for files, d for directories, x for executables, and e for empty files and folders. Combine it with -d to limit how deep fd goes. -d 1 stays in the folder you search:

fd -t d -d 1
logs/
photos/
shop/

fd marks directories with a trailing /. Two more filters round out the set. -E excludes names that match a glob, and -S filters by size, such as -S +1M for files over one megabyte:

fd -e py -E 'test_*' . shop
shop/src/__init__.py
shop/src/app.py
shop/src/cart.py

Respect .gitignore and Hidden Files

By default fd skips two kinds of entries: hidden files and folders, whose names start with a dot, and anything an ignore file lists. Inside shop/, .gitignore lists node_modules/, dist/, and *.log, so a plain fd in that folder shows only the project’s own files:

cd shop
fd
README.md
run.sh
src/
src/__init__.py
src/app.py
src/cart.py
src/templates/
src/templates/index.html
src/test_cart.py
static/
static/style.css

Add -H to include hidden entries such as .env and .gitignore, -I to include ignored ones, or -u for both. Run from the home folder, fd -e log lists the three files in logs/, and -I adds shop/debug.log, which shop/.gitignore hides:

fd -I -e log
logs/app-2026-09.log
logs/app.log
logs/error.log
shop/debug.log

In shop/, the same flag brings back node_modules/ and dist/. fd reads .gitignore only inside a git repository, a folder with a .git directory at or above it.

A .gitignore copied into a folder that isn’t a repository does nothing until you add --no-require-git:

mkdir ~/scratch
cp ~/shop/.gitignore ~/scratch/
echo hi > ~/scratch/debug.log
cd ~/scratch
fd
debug.log

Run fd --no-require-git in the same folder and it prints nothing, because the copied .gitignore now applies.

To hide files from fd without changing what git tracks, list them in .fdignore (fd only) or .ignore (fd, ripgrep, and other tools that read it). Each uses the same syntax as .gitignore:

cd shop
echo 'static/' > .fdignore
fd
README.md
run.sh
src/
src/__init__.py
src/app.py
src/cart.py
src/templates/
src/templates/index.html
src/test_cart.py

static/ and its stylesheet are gone. fd --no-ignore shows everything again. For patterns you never want to see in any folder, write them to ~/.config/fd/ignore. --no-global-ignore-file turns that file off for one search, and --no-ignore-vcs turns off only .gitignore.

Run Commands on Results with -x and -X

-x runs a command once for each result, and -X runs it once with every result as arguments. Placeholders in braces stand for parts of each path:

  • {} is the whole path, src/app.py.
  • {.} is the path without its extension, src/app.
  • {/} is the file name, app.py.
  • {//} is the parent folder, src.
  • {/.} is the file name without its extension, app.

When you leave out the placeholder, fd adds {} at the end. Rename every .jpeg file to .jpg. The mv prints nothing, so check the result with a second search. Sort it, because a renamed file moves to the end of its folder, and fd lists a slow search in the order it finds files:

fd -e jpeg -x mv {} {.}.jpg
fd -e jpg | sort
photos/IMG_2041.jpg
photos/IMG_2042.jpg
photos/IMG_2043.JPG

-X suits commands that take many files at once, such as wc -l to count lines:

cd shop
fd -e py -X wc -l | sort
        0 ./src/__init__.py
        7 ./src/test_cart.py
       11 ./src/app.py
       13 ./src/cart.py
       31 total

Paths that fd passes to a command start with ./, so a name that begins with - can’t pass for an option. And the output ends in | sort because the order varies. fd sorts its own results only when the search finishes within about 100 milliseconds, and otherwise prints them in the order it finds them. -x runs several commands at once and prints their output as each finishes, so its order changes from run to run. Pipe to sort whenever the order matters. -j 1 runs one command at a time.

Find every file that mentions TODO:

cd shop
fd -t f -X grep -l TODO | sort
./README.md
./src/cart.py

Format Output

A few flags change how fd prints each path:

  • -a prints absolute paths, starting at /.
  • -l prints a long listing, like ls -l, with permissions, owner, size, and date.
  • -0 ends each path with a NUL character instead of a newline, for xargs -0 and other tools that read NUL-separated names safely.
  • --color never turns colors off; --color always keeps them when you pipe to less -R.
fd -a -e css
/home/reader/shop/static/style.css

Pair -0 with xargs -0 when names may contain spaces:

cd shop
fd -0 -e py | xargs -0 wc -l
        0 ./src/__init__.py
       11 ./src/app.py
       13 ./src/cart.py
        7 ./src/test_cart.py
       31 total

Practice fd

fd sticks once you type it. The practice shell below runs real fd 10.5.0 in zsh, in this page. Nothing downloads until you press Start, nothing you type leaves your browser, and Reset brings back a fresh shell. Click the shell to type.

The practice shell has no network and holds only fd, zsh, and BusyBox’s basic programs. Its home holds the practice files the examples above use: notes.txt, logs/, photos/, and the shop/ project with its .gitignore. These commands cannot run there, so run them on your own machine:

  • git, rg, fzf, zoxide, bat, curl, brew, and sudo are not installed, so zsh answers command not found.
  • fd -x convert and fd -x ffmpeg fail once per match, because neither program is installed.
  • wget, ping, nc, and nslookup fail, because the practice shell has no network.

Practice the commands on this page in real fd, right here. Nothing downloads until Start (about 7 MB).

reader@practice ~ % fd cart shop
shop/src/cart.py
shop/src/test_cart.py
reader@practice ~ %

The practice shell needs JavaScript. Every command in the reference also works in fd on your own machine.

Run the drill below in the practice shell or in your own terminal with the same files. Type each command exactly as shown and press Enter. It takes about ten minutes:

  1. Move into the project: cd shop. The prompt changes to reader@practice ~/shop %.
  2. List the Python files: fd -e py. You see src/__init__.py, src/app.py, src/cart.py, and src/test_cart.py. Nothing from node_modules or dist shows up, because the project’s .gitignore lists them.
  3. Add hidden files: fd -H. The list now starts with .env, .git/, .git/HEAD, and .gitignore, then the 11 files and folders that plain fd shows. node_modules, dist, and debug.log stay hidden, because -H shows hidden files but still respects .gitignore.
  4. Go back home with cd, then list the log files: fd -e log. You see logs/app-2026-09.log, logs/app.log, and logs/error.log. Run fd -I -e log to stop respecting ignore files, and shop/debug.log joins the list.
  5. Rename the .jpeg photos: fd -e jpeg -x mv {} {.}.jpg. {} is each match and {.} is the match without its extension. It prints nothing. Check the result with fd -e jpg | sort. You see photos/IMG_2041.jpg, photos/IMG_2042.jpg, and photos/IMG_2043.JPG, because -e ignores the case of the extension. A renamed file moves to the end of its folder, and fd lists a slow search in the order it finds files, so sort keeps the order fixed.
  6. Press Reset shell. The renamed photos are back to .jpeg, and the shell starts again in your home. Run fd -e jpeg to see both .jpeg photos again.
  7. Type cd sh and press Tab. zsh completes the line to cd shop/. Press Enter to move into the project.

To practice on your own machine, install fd first. On macOS, install fd with Homebrew:

Runs on your machine, not in the practice shell

brew install fd

On Debian and Ubuntu, the package is fd-find and the program is named fdfind, because another package already uses fd. Install it, then link fd to it in a folder on your PATH:

Runs on your machine, not in the practice shell

sudo apt install fd-find
mkdir -p ~/.local/bin
ln -s "$(command -v fdfind)" ~/.local/bin/fd

With a Rust toolchain on any platform, build it with cargo:

Runs on your machine, not in the practice shell

cargo install fd-find

Then work through these steps in one of your own project folders that is a git repository:

  1. List every file fd sees: fd -t f. Compare the count with fd -t f -u, which adds hidden and ignored files. The difference is what your ignore files hide.
  2. Find a file by part of its name, such as fd config. Then try the same word with a capital letter, fd Config, and see smart case match case exactly.
  3. List one kind of file: fd -e md, or any extension your project uses. Add -d 2 to stay near the top.
  4. List only folders, two levels deep: fd -t d -d 2.
  5. Find the files that mention TODO: fd -t f -X grep -l TODO.
  6. Count lines in every file of one kind: fd -e md -X wc -l. Pipe it to sort -n to see the longest file last.
  7. Hide a folder from fd without touching git: write its name to .fdignore, run fd again, then delete .fdignore.

Command Reference

Verified with fd 10.5.0. Search the reference to find a command fast. Each entry shows the command, then what it does.

Patterns

  • fd cart - Find names that contain cart, ignoring case for a lowercase pattern (smart case)
  • fd App - Match case exactly, because the pattern has an uppercase letter
  • fd -s app - Match case exactly for any pattern
  • fd -i readme - Ignore case for any pattern
  • fd '^test_' - Find names that start with test_ (regular expression)
  • fd 'cart\.py$' - Find names that end with cart.py
  • fd -F '.py' - Treat the pattern as fixed text, not a regular expression
  • fd -g '*.py' - Match a whole name with a glob instead of a regular expression
  • fd -p 'src/t' - Match the pattern against the full path, not only the name
  • fd . src - List everything in one folder (. matches any name)

Filters

  • fd -e py - Find files with an extension, ignoring case
  • fd -e html -e css - Find files with any of several extensions
  • fd -t f - Find files only
  • fd -t d - Find directories only
  • fd -t x - Find executable files
  • fd -t e - Find empty files and directories
  • fd -d 1 - Search one level deep at most
  • fd --min-depth 2 - Skip results less than two levels deep
  • fd -E 'test_*' - Exclude names that match a glob
  • fd -S +1M - Find files larger than one megabyte
  • fd -S -1k - Find files smaller than one kilobyte

Hidden and Ignored Files

  • fd -H - Include hidden files and folders
  • fd -I - Include files that an ignore file lists
  • fd -u - Include hidden and ignored files
  • fd --no-ignore-vcs - Stop reading .gitignore, but keep .ignore and .fdignore
  • fd --no-require-git - Read .gitignore outside a git repository
  • fd --no-global-ignore-file - Skip ~/.config/fd/ignore for one search
  • echo 'static/' > .fdignore - Hide a folder from fd only
  • echo 'static/' > .ignore - Hide a folder from fd and other tools that read .ignore
  • echo '*.html' > ~/.config/fd/ignore - Hide a pattern from fd in every folder

Output

  • fd -a - Print absolute paths
  • fd -l - Print a long listing with permissions, size, and date
  • fd -0 - End each path with a NUL character, for xargs -0
  • fd --color never - Turn colors off

Run Commands

  • fd -e jpeg -x mv {} {.}.jpg - Run a command once per result
  • fd -e py -X wc -l - Run a command once with every result as arguments
  • fd -x echo {} - Use the whole path
  • fd -x echo {.} - Use the path without its extension
  • fd -x echo {/} - Use the file name
  • fd -x echo {//} - Use the parent folder
  • fd -x echo {/.} - Use the file name without its extension
  • fd -j 1 -x wc -l - Run one command at a time

Examples

Each example runs on the practice files and shows what fd prints.

fd -e log
logs/app-2026-09.log
logs/app.log
logs/error.log
fd -H . shop -d 1
shop/.env
shop/.git/
shop/.gitignore
shop/README.md
shop/run.sh
shop/src/
shop/static/
fd -t x . shop
shop/run.sh
fd -t e . shop
shop/src/__init__.py
fd -e py -x echo {/.} | sort
__init__
app
cart
test_cart
fd app shop
shop/src/app.py
fd -F '.py' shop
shop/src/__init__.py
shop/src/app.py
shop/src/cart.py
shop/src/test_cart.py
fd -p 'src/t' shop
shop/src/templates/
shop/src/templates/index.html
shop/src/test_cart.py
fd -e html -e css . shop
shop/src/templates/index.html
shop/static/style.css
fd -t d . shop
shop/src/
shop/src/templates/
shop/static/
fd -I -t d -d 2 . shop
shop/dist/
shop/node_modules/
shop/node_modules/left-pad/
shop/src/
shop/src/templates/
shop/static/
fd --min-depth 3 -e py
shop/src/__init__.py
shop/src/app.py
shop/src/cart.py
shop/src/test_cart.py
fd -e py -x echo {//} | sort -u
./shop/src

Learn fd: Beyond the Basics

With names, filters, ignore rules, and -x in hand, these resources take you further. Start with a video, then keep fd’s manual open while you replace the find lines in your notes.

Video

Books

Online

  • fd on GitHub, the README with installation for every platform, a feature tour, and benchmarks.
  • fd’s manual page, the full list of options and placeholders.
  • fd’s changelog, useful when an option you read about behaves differently in your version.
  • Regular expression syntax from the Rust regex crate, the engine fd’s patterns use.
  • modern-unix, a list of newer command-line tools that pair with fd, such as ripgrep, bat, and fzf.
  • Learn Zsh covers globbing and glob qualifiers, the shell’s own way to match files.
  • Learn Nushell shows a shell that lists files as tables you can filter and sort.
  • What Is fzf? explains the fuzzy finder that reads fd’s output to pick files interactively.
  • What Is Zoxide? explains a smarter cd for jumping to folders you visit often.

Related Articles by Category