Terminal commands for finding files on a Mac
The file did not show up in Spotlight, the Finder window returned something else, and now the search is being repeated in a terminal. That is the sequence behind most searches for find file mac terminal. The number of commands involved is small. The part that decides whether a search takes three seconds or three minutes is not the command name, it is the choice between two families of tools, and a handful of flags that turn an unreadable wall of output into a list short enough to scan.
There are two axes. The first is whether a command queries an index or walks the disk. The second is whether it matches on names or on contents. Every command below sits somewhere on those two axes, and knowing where it sits explains both what it will find and what it will silently miss.
Index queries and disk walks behave differently
mdfind and mdls query the same metadata index that Spotlight builds. find, grep -r, fd, and rg traverse the file system directly, directory by directory.
The trade is speed against blind spots. An index query returns almost instantly, no matter how many files exist, because the work of cataloguing happened earlier. A disk walk has no blind spots at all, because it looks at what is actually on the volume, but it takes real time. Running find across an entire home directory that contains a few large project trees can take tens of seconds.
That difference maps cleanly onto two situations. For a file handled regularly, where only a fragment of the name is remembered, the index query wins. For a file that has already failed to appear in a graphical search, the index itself is the suspect, so a disk walk is the right next move. Repeating mdfind with slightly different words against a file the index never saw is the single most common way to lose ten minutes at a terminal.
The second axis matters just as much. Name matching returns few results and reads quickly. Content matching finds things that name matching cannot, and returns far more rows than anyone wants to read. In practice the productive order is to narrow by name first, then read contents inside the shortlist. Starting with a content search across a broad tree is what makes the terminal feel slow.
Making find return a readable list
Typed plainly, find fills the screen with permission errors and thousands of matches. Four flags do most of the work of making it usable.
Restrict the type with -type f for files or -type d for directories. Cut the depth with -maxdepth 3, which stops the traversal from descending further. Ignore case with -iname instead of -name. Discard the noise with 2>/dev/null at the end, which drops warnings about directories that cannot be read.
find ~/Documents -maxdepth 3 -type f -iname "*invoice*" 2>/dev/null
Time and size conditions translate memory into filters. -mtime -7 matches files modified within the last seven days. -size +10M matches files larger than ten megabytes. A recollection along the lines of "a big file touched last week" becomes those two flags with almost no thought.
Exclusions work on the path rather than the name. -not -path "*/node_modules/*" removes an entire branch from consideration. On a machine that holds several project directories, adding one exclusion of that kind changes the result count by an order of magnitude.
Quoting the pattern is not decoration. Written bare as find . -name *.log, the shell expands the pattern before find ever sees it, using whatever happens to match in the current directory. Written as -name "*.log", the pattern reaches find intact. The failure mode is confusing because the command still runs and still returns results, just not the ones intended.
Combining conditions has one piece of syntax that trips people up. Two tests written next to each other are joined with an implicit and. An or requires -o, and grouping requires parentheses that the shell would otherwise interpret, so they are escaped: \( -name "*.png" -o -name "*.jpg" \). Most reports of a find command returning the wrong set come down to those brackets rather than the patterns inside them.
Sorting by time is often faster than remembering a name. ls -lt lists the current directory newest first, and find . -newer reference-file returns everything modified after a known file. For the very common case of a file saved an hour ago whose name has already been forgotten, a time filter beats any amount of guessing at the spelling.
One more habit is worth building. When a search is going to be run more than once, build it up in stages: start with -maxdepth 2 to confirm the starting directory is right, then widen. Debugging a long find invocation that returns nothing is harder than growing a short one that returns something.
What mdfind trades away for speed
mdfind gives shell access to the Spotlight index. Scope it with mdfind -onlyin ~/Documents "contract", or restrict it to file names with mdfind -name report. Results arrive immediately, and for supported formats the text inside documents is searchable too, which no plain name search can do.
The catch is that an empty result never explains itself. Four exclusions cover most disappearances. Folders added to the Search Privacy list in the Spotlight section of System Settings are removed from results entirely. Folders whose names end in .noindex are skipped by the indexer, and that suffix is inherited by copies. Files inside zip archives and disk images are not indexed until the archive is opened. Volumes where indexing was disabled, or interrupted by an ejection mid-scan, are quietly absent.
None of those cases mean the file is gone. Each one means the index does not know about it, and find will still turn it up.
Checking a volume takes one command. mdutil -s /Volumes/Name reports whether indexing is enabled there. A reply of Indexing disabled makes any mdfind result from that volume meaningless. For a single file, mdls filename prints the attributes the index holds, and mostly empty output means the importer never processed it.
locate comes up in older advice and deserves a note. It reads a database that is rebuilt on a weekly schedule, needs to be enabled manually on macOS, and by definition knows nothing about a file created this morning. For daily work, keeping to mdfind and find avoids a third set of behaviours to remember.
| Command | Uses index | Hidden files | Respects ignore files | Speed on large trees |
|---|---|---|---|---|
| mdfind | Yes | Same limits as Spotlight | Not applicable | Instant |
| find | No | Included | Not applicable | Slow |
| grep -r | No | Included | No | Slow |
| rg, fd | No | Skipped by default | Yes, by default | Fast |
Read that table by column, not by row. The question is not which tool is best. The question is which blind spot is acceptable for the search about to be run.
Content search and the defaults nobody reads
grep -rn is the baseline for searching inside files: -r recurses, -n prints line numbers. The BSD version shipped with macOS looks at everything it walks past, including dot files.
ripgrep and fd are built on a different assumption. Both skip files that start with a dot, and both skip anything listed in a .gitignore. Inside a source tree those defaults are a genuine improvement. Outside one, they are a source of missed results.
grep -rn "api_key" ~/work
rg -uu --hidden "api_key" ~/work
fd -HI "\.env$" ~/work
A search that succeeds with grep and fails with rg is almost never a bug. It is that difference in defaults. rg takes --hidden and -uu to switch the behaviour off; fd takes -HI. Anyone hunting for configuration files, credentials, or editor state needs those flags, because every one of those file types begins with a dot.
When a content search returns too much, restructure rather than refine the pattern. Produce a candidate list by name with find, then search inside only those files. Cutting the input from several thousand files to a few dozen does more for the wait than any regular expression tuning.
Handing the result to the next command
Finding the file is rarely the goal. The goal is to open it, rename it, move it, or feed it to something else. This is where spaces in file names break things.
To pass results from find safely, use -exec, or pair -print0 with xargs -0. Both keep a path with spaces or non-ASCII characters as a single argument.
find . -name "*.png" -print0 | xargs -0 -n1 open -R
open -R path reveals the file in a Finder window with it already selected, which is the fastest way to look at something found in the shell. The reverse direction is documented by Apple in the Terminal user guide.
You can use the path bar in a Finder window to open a new Terminal window or tab with the working directory set to a folder's location. If you don't see the path bar at the bottom of the Finder window, choose View > Show Path Bar. Source: support.apple.com
Two habits keep the output manageable. Counting first with find . -name "*.md" | wc -l says whether the conditions are tight enough before a thousand lines scroll past; a three digit answer means another filter is needed rather than a longer scroll. Piping into less handles the rest.
The other habit applies to anything destructive. Before running a delete or a move across a result set, run the identical conditions with -print and look at what comes back. -delete in particular removes matches with no confirmation, so a single mistyped character in a pattern is the difference between clearing a cache directory and clearing a project. Printing first costs one command and makes the mistake visible while it is still reversible.
Those two routes remove most of the copying and pasting of paths. What they do not remove is the window switch itself, and that is the part that repeats all day.
Count the switches, not the keystrokes
A terminal search that takes two seconds is often wrapped in thirty seconds of moving between windows. Results appear in one place, the command that acts on them lives in another, and confirming the outcome means going back again. Each trip is short. The count is what makes it expensive.
The fix for that is layout, not a better command. When the folder view and the command line share one working directory, the copy and the switch drop out of the sequence entirely. A tool built that way is described on the Features page, and the differences against dual pane browsers and terminal-first tools, which solve a different problem, are laid out on Compared with other file managers.
What to change first
Spend one week counting how often a search in a folder window is followed by a switch to a terminal. If the number is low, the flags above are the whole fix: narrow find with -maxdepth and -type, and add --hidden to rg. If the number is high, the problem is not the search command, and moving to a file manager with a built-in terminal is the change worth testing, and Atriens is one place to start comparing that shape of tool.
Frequently asked questions
Why does mdfind return nothing when the file clearly exists?
The file is not in the Spotlight index, which is a different statement from the file not existing. The usual causes are the Search Privacy list in System Settings, a parent folder whose name ends in .noindex, a file sitting inside a zip archive or disk image, or a volume where indexing is disabled. Running the same search with find will still locate it.
How do you stop find from printing permission errors?
Append 2>/dev/null to send the error stream to nowhere. That hides warnings about directories that cannot be read without changing which files are matched. Adding -maxdepth 3 and -type f at the same time usually does more, because it shortens the result list as well as the noise.
Why does grep find a string that ripgrep misses?
Both rg and fd skip dot files and anything matched by a .gitignore unless told otherwise. Pass --hidden and -uu to rg, or -HI to fd, and the results line up with grep. This matters most when the target is a configuration file, since those names begin with a dot.
What is the safest way to run a command on every file that find returns?
Use -exec command {} \; inside the find invocation, or pipe -print0 into xargs -0. Both treat a path containing spaces as one argument. Piping plain find output into xargs without those flags splits names at every space, which is why filenames with spaces appear to vanish or produce errors about missing files.