The find command on a Mac: locating files Finder will not show

Someone types a filename into the Finder search field, gets nothing, and opens Terminal. That is the usual route to the find command on a Mac. The command works, but the reason it works is worth understanding before memorising flags: find does not consult an index. It opens directories and reads what is inside them, one level at a time. Every quirk that follows comes from that single fact.

Two different search systems live on the same Mac

macOS ships two unrelated ways to locate a file, and they fail in opposite directions.

Spotlight maintains an index. The Finder search field queries it, and so does the mdfind command. Results come back instantly because the answer was computed in advance. The cost is that anything absent from the index does not exist as far as Spotlight is concerned. Volumes with indexing turned off, developer directories, external drives that were never indexed, and anything inside a container the indexer skipped all fall into that gap.

find has no index. It starts at a path given on the command line, reads the directory, descends into every subdirectory it is allowed to enter, and tests each entry against the conditions supplied. It is slower on large trees and it will be interrupted by permission errors, but what is on disk is what it reports.

That difference explains the most common frustration. A file that Finder swears does not exist turns up in one line of find output. Nothing is broken. The index simply never saw it. Before assuming a deeper problem, run mdutil -s /, which prints whether indexing is enabled on that volume. If it is disabled, no amount of retyping in Finder will help, and find is the only route.

The reverse case is just as important. find tests names, sizes, timestamps and permissions, but never file contents. Searching for a phrase inside a stack of PDFs is index work, not find work.

The smallest useful form

A find invocation has three parts: where to start, what to match, and what to do with matches. The default action is to print, so the third part can be omitted.

find . -name "invoice.pdf"

The dot is the starting point. Omitting it is the most common beginner error on macOS, because BSD find requires a path. Use . for the current directory, ~ for the home folder, or an absolute path when the target is elsewhere.

Three conditions cover most day-to-day use.

-iname matches a name while ignoring case. Human-authored filenames drift between capitalisation styles, so case-insensitive matching is the safer default. -name exists for the rare case where case matters.

-type f restricts results to regular files, -type d to directories. When a folder shares a name with the file being hunted, this one flag makes the output readable.

-maxdepth 2 stops the descent two levels below the starting point. This is the single most effective way to cut waiting time when the target is known to sit near the top of a tree.

Wildcards belong inside quotes. Written bare, the shell expands them before find ever sees them, and find receives something else entirely.

find . -iname "*estimate*" -type f -maxdepth 3

Filtering by time, which BSD find does unusually well

Names are unreliable memory aids. Timestamps are not. Anyone who edited a file last week usually remembers the week more reliably than the filename.

-mtime counts in 24-hour periods measured from the moment find started, not from midnight. -mtime -7 matches files modified within the last seven of those periods, and +30 matches anything older than thirty. The distinction matters more than it sounds: a file edited yesterday evening may fall outside -mtime -1 depending on the hour the command runs.

-mmin uses minutes. When the question is which files a script just touched, minutes are the right resolution.

The flag worth learning specifically on macOS is -newermt, which accepts a date directly.

find ~/Documents -type f -newermt "2026-09-20" ! -newermt "2026-09-24"

No mental arithmetic about how many days ago something happened. Add a negated second test and the result becomes a date range. Two conditions written side by side are combined with an implicit AND; -o is needed to express OR.

-newerXY generalises this. The first letter picks which timestamp on the candidate file to compare (access, inode birth, change, modification) and the second picks what to compare it against, including a literal date when it is t. Most work only ever needs -newermt, but knowing the pattern explains why the flag looks cryptic.

Size and path filters bring the output down to a readable list

Conditions compose. When a date filter still produces hundreds of lines, size and path narrow it further.

-size needs an explicit unit suffix: c for bytes, k for kilobytes, M for megabytes, G for gigabytes. Leave the unit off and the number is interpreted as 512-byte blocks, which produces results that look random.

find ~/Downloads -type f -size +100M

Excluding a directory has two forms with very different performance. Filtering matches out with -path still reads the whole subtree and then discards it. -prune stops find from descending at all, which is what actually saves time on a tree containing a dependency folder with tens of thousands of entries.

find . -name node_modules -prune -o -type f -name "*.ts" -print

-xdev keeps find on the volume it started on. Leaving it off means mounted external disks and network shares get walked too, which is how a search that should take two seconds takes four minutes.

Permission errors print one line per unreadable directory. Redirecting them with 2>/dev/null hides only the errors; matches still appear.

Handing results to the next command

Locating a file is rarely the end goal. The value of find is that its output is a list a shell can act on.

-exec runs a command for each match. Ending the expression with + instead of ; batches the matches into as few invocations as possible, which is noticeably faster on large result sets.

find . -name "*.png" -exec du -h {} +

When piping instead, pair -print0 with xargs -0. The default word splitting breaks on spaces, and filenames with spaces are normal outside of source trees. The null-delimited pair removes an entire category of silent breakage.

-delete removes matches in place with no recovery step. The safe order is to run the expression without it, read the output, and only then add it. Skipping that step converts a typo in a condition into data loss.

Making the output usable instead of overwhelming

A find expression that matches too much is worse than one that matches nothing, because the reader stops trusting it. Three habits keep the output at a size a person can read.

Count before looking. Appending | wc -l reports how many matches the expression produces. A result in the thousands means the conditions are wrong, and reading the first screen of a wrong answer wastes more time than rewriting the expression.

Sort deliberately. find emits results in the order it walks the tree, which is neither alphabetical nor chronological. Piping to sort gives name order, and -exec du -h {} + | sort -h gives size order with human-readable units, because the -h flag on macOS sort understands suffixes such as K, M and G.

Save the expressions that survive. A search that took three attempts to get right will be needed again, and retyping it reintroduces the same mistakes. Recording it as a shell alias or a small script, with the starting path and depth already fixed, converts a five-part command into one word.

None of this is find functionality. find locates; counting, sorting and formatting belong to other commands. That separation is why learning how to connect commands pays off more than memorising additional primaries. The set of conditions is large and mostly situational, whereas the piping patterns are the same every time.

Where BSD find and GNU find part ways

This is the most time-consuming failure mode, because the error message says nothing about the cause. macOS ships the BSD implementation. Most tutorials on the internet were written against the GNU implementation used on Linux. They share a name and a large overlap, not an interface.

Flag On macOS Notes
-iname Available Case-insensitive name match
-maxdepth, -mindepth Available Applies to the whole expression wherever it appears
-newermt Available Accepts an ISO 8601 or RFC 822 date directly
-size with k, M, G Available Unit suffix required, otherwise 512-byte blocks
-printf Not available BSD find has no output formatting primary
-regextype Not available No switch for regular expression dialect

-printf is the one that catches the most people, because it appears in a large share of the recipes published for Linux. The replacement is to hand the paths to an external command: -exec stat -f "%z %N" {} + produces size and path columns. A GNU build can be installed alongside the system one, and projects like GNU Coreutils are widely packaged, but running a different implementation locally than on the servers involved creates its own confusion. Checking man find on the machine in front of you settles the question in seconds.

Choosing between find and the index

The decision reduces to three cases.

When the rough location is known, use find. A tight starting point makes it fast and it does not care whether the index is healthy.

When the file is somewhere on the Mac and the location is genuinely unknown, use mdfind. It queries the same index Finder does, but prints bare paths to standard output, so the result can be piped. Attribute queries work too: mdfind "kMDItemFSSize > 1073741824" lists indexed files above one gigabyte.

When the search term is text inside a document, the index is the only option. An empty mdfind result means the file is not indexed, which is a different statement from the file not existing.

What the two-window habit actually costs

The flags are the easy part. The step that consumes time sits on either side of the command.

The sequence usually runs like this. Finder comes up empty, so attention moves to Terminal. find prints a list of paths. One path gets copied back into Finder to confirm the location visually. Space bar previews the contents. Then a third window opens to ask an assistant what to do with the batch. That is four context switches for one lookup, and lookups happen many times a day.

Scoping a search correctly saves seconds. Removing the copy-and-paste handoff between a file browser, a shell and an assistant removes the switches themselves. A file manager with a built-in terminal and an assistant in the same window makes the path under the cursor and the path on the command line the same object, so there is nothing to transcribe. The specific capabilities involved are listed on the features page, and the differences against Finder and other options are laid out in this comparison.

What to change first

Make -iname plus -maxdepth the default shape for name searches, and decide the starting point before typing anything else. Then, when Finder returns nothing, check mdutil -s / before assuming the file is gone: that one line separates an indexing gap from a missing file. If the copying of paths between windows is where the time goes rather than the search itself, Atriens collapses those windows into one.

Frequently asked questions

Why does find take so long on a Mac?

Because it reads directories instead of querying an index, and by default it follows into every subdirectory it can enter, including mounted external disks and network shares. Narrow the starting path, add -maxdepth, use -prune on large dependency folders, and add -xdev to stay on one volume. If the search genuinely spans the whole machine, mdfind is the faster tool.

Why does the same command work on Linux but not on macOS?

macOS ships BSD find and Linux distributions generally ship GNU find. Flags such as -printf and -regextype exist only in the GNU version, so recipes copied from Linux tutorials fail with a syntax error rather than a helpful message. Run man find locally to see which primaries the installed version supports.

Is mdfind a replacement for find?

No, they answer different questions. mdfind queries the Spotlight index, so it is instant and can search document contents, but it is blind to anything the indexer skipped. find reads the disk directly, so it is slower but complete. Using both, with mdfind for unknown locations and find for known ones, covers more ground than picking a favourite.

How should filenames with spaces be handled?

Pipe with find ... -print0 | xargs -0 command, which separates entries with a null byte instead of whitespace, or use -exec command {} + and let find pass the arguments directly. The default behaviour of splitting on spaces will otherwise break a single filename into two arguments, and the failure is silent.

Back to all posts