everyday mac tools

mdfind: searching a Mac from the Terminal

· 6 min read

mdfind asks the Spotlight index the same questions Finder does, and prints full paths instead of icons. Type mdfind invoice and you get every indexed file whose name, contents or metadata match, one path per line, unranked and complete.

That last part is why it is worth learning. The Spotlight window shows you what it thinks you want. mdfind shows you everything, which is what you need when you are trying to establish whether something exists at all.

The basic form

mdfind budget

Matching is case-insensitive and covers names, file contents and metadata, so a text file containing the word appears alongside a folder named for it. Quote anything with spaces:

mdfind "annual budget"

Two flags make the plain form usable straight away:

mdfind -count budget
mdfind -name budget

-count prints the number of matches instead of the list, which is the right first move on a query you suspect will return thousands of lines. -name restricts matching to the filename, which is usually what you meant and always faster to read.

There is also -live, which leaves the query open and keeps reporting as files change. Press Control-C to stop it. It is a good way to watch where an application actually writes its files while you use it.

Scope it to a folder

mdfind -onlyin ~/Documents invoice
mdfind -onlyin /Volumes/Archive -name .psd

-onlyin takes a directory and restricts results to it and everything below. Repeat the flag to search more than one place. This is the command-line version of clicking the folder name in a Finder search window, and it is faster to type than the equivalent sequence of clicks. Search inside one folder only on a Mac covers the Finder side, including how to make the current folder the default scope.

mdfind also reads smart folders you saved in Finder:

mdfind -s "Recent PDFs"

The name is the smart folder’s name in ~/Library/Saved Searches. Building a query visually in Finder and then running it from a script is a reasonable division of labor.

Query by metadata attribute

The plain form searches everything. The query language searches one field at a time, and this is where mdfind stops being a slower Spotlight and starts doing things Finder cannot.

Every indexed file has a set of attributes. Look at them for a file you have:

mdls ~/Downloads/statement.pdf

You get a list of names beginning kMDItem and their values: kMDItemFSName, kMDItemFSSize, kMDItemContentType, kMDItemContentModificationDate, kMDItemNumberOfPages, kMDItemPixelHeight for images, kMDItemDurationSeconds for media. Any of them can be queried.

mdfind "kMDItemFSName == '*.sketch'c"
mdfind "kMDItemContentType == 'com.adobe.pdf'"
mdfind "kMDItemContentTypeTree == 'public.image'" -onlyin ~/Desktop

The c after the closing quote means case-insensitive; d ignores accents, and cd does both. * is a wildcard. Comparisons work on numbers and dates:

mdfind "kMDItemFSSize > 1000000000"
mdfind -onlyin ~ 'kMDItemContentModificationDate >= $time.today(-7)'

The $time tokens are part of the query language, not the shell: $time.today(), $time.yesterday(), $time.this_week(), $time.now(-3600) for the last hour. Use single quotes around any query containing $ so the shell leaves it alone.

Combine terms with && and ||, and print an attribute alongside each result with -attr:

mdfind -attr kMDItemFSSize "kMDItemFSSize > 500000000 && kMDItemContentTypeTree == 'public.movie'"

That gives you every video over roughly half a gigabyte with its size next to the path, which is a useful two seconds of work before a disk cleanup.

Feed the results to other commands

Paths on standard output are the whole point. The one thing to get right is spaces in filenames, which is what -0 is for:

mdfind -0 -onlyin ~/Documents -name draft | xargs -0 ls -lh
mdfind -0 "kMDItemContentType == 'com.adobe.pdf'" -onlyin ~/Desktop | xargs -0 -n1 basename

-0 separates results with a null character and xargs -0 reads them the same way, so a file called Q3 report final.pdf survives the trip. Without it, that filename becomes three arguments and the command does something you did not ask for.

Piping into open is the quickest way to act on a small result set, and the reason to check with -count first.

Where mdfind stops

It is a client of the index, so it inherits every limitation the index has.

Excluded locations return nothing. Anything in the privacy list in System Settings, Spotlight is not indexed, and mdfind cannot see it. Neither can Finder. Spotlight cannot find a file you know exists covers how to check.

Unindexed volumes return nothing. Network shares usually, external drives where indexing was switched off, and any volume still being indexed after a restore. Check with mdutil -s /Volumes/Name.

Protected locations depend on permissions. If Terminal has not been granted Full Disk Access, results from places like the Mail store can be missing or unreadable even though the files exist. Full Disk Access on a Mac covers what that permission does and does not include, and why granting it to a shell is the broadest version of it.

When the index is the problem rather than the query, find is the second opinion, because it walks the filesystem itself:

find ~/Documents -iname "*invoice*"

Slower on a large tree, and completely unaffected by indexing state. If find sees a file and mdfind does not, you have learned something specific about the index rather than about the file.

For interactive use, a terminal command is the wrong shape for search anyway: you type the whole query before you see any results, and refining means editing the line and running it again. That is fine in a script and tiring at 4pm. Tools built for the interactive case narrow as you type instead, which is what Everywhere does with its own catalog of every name and path on the Mac, with path:, ext: and size: operators covering the same ground as -onlyin and the size attributes. It does not search inside file contents, so mdfind keeps that job.

Questions

Is mdfind faster than searching in Finder? The query itself takes about the same time, since both read one index. mdfind feels faster because it prints plain paths with no ranking, no thumbnails and no window to draw, and because it is repeatable with the up arrow.

Why does mdfind return nothing for a file I can see? In order of likelihood: the location is excluded from indexing, the volume is not indexed, the term does not appear in the name or contents in the form you typed, or Terminal lacks the permission to read that area. Try find on the same path to separate a file problem from an index problem.

Can I search file contents with it? Yes, that is the default behavior of a plain query, because the index holds extracted text for document formats. To search contents in a folder that is not indexed, grep -ril "phrase" ~/Notes is the direct approach.

What is mdutil for? Managing the index rather than querying it: mdutil -s / reports status for a volume, and sudo mdutil -E / erases and rebuilds it. The rebuild takes hours and is worth trying only after the simpler explanations are ruled out.