The mdfind command is a powerful, headless way to access MacOS’s Spotlight search.
Using this command will dramatically decrease the time you spend dealing with the filesystem.
Like locate
, mdfind
returns a list of files matching the specified query string.
However, mdfind
has has several advantages over locate
; we’ll explore them in this post.
Firstly, mdfind
is backed by the system search index, not a separate cache.
So not only is mdfind
faster, but changes on your filesystem will be reflected instantly.
No more waiting for some background process to call sudo updatedb
.
Secondly, we can pass a number of file-specific parameters to mdfind
, narrowing down our search results.
This includes date ranges, metadata parsing and preferences, things that can sometimes be annoying to specify using command line tools.
Usage
As a shell command, mdfind
is best used with pipes.
W’ll cover a few examples here.
Manipulating Files
I often download things I need for projects straight from my browser, instead of using wget
or something similar.
Instead of opening all the gui windows and dragging the file, we can do something like:
# our filename:
➜ folder git:(master) ✗ ls /Users/brycesmith/Downloads | grep -oP "Source\w+.zip"
Source_Code_Pro.zip
➜ folder git:(master) ✗ mdfind Source_Code_Pro.zip | xargs -I{} cp {} .
This way, by using piping, xargs
and mdfind
we can simplify file operations that would otherwise require a lot of involved gui interaction.
Piping to Grep
mdfind
doesn’t support my preferred perl regexes, but it’s fast.
This means we can pipe the command’s output to tools like grep
or sed
.
A trivial example; to find all file ending with a certain string:
➜ folder git:(master) ✗ mdfind downloads | grep Downloads$
/Users/brycesmith/Library/Containers/com.apple.BKAgentService/Data/Documents/iBooks/Downloads
/Users/brycesmith/Downloads
/Users/brycesmith/Library/Containers/com.apple.mail/Data/Library/Mail Downloads
/Users/brycesmith/Library/Containers/com.apple.mail.MailQuickLookExtension/Data/Library/Mail Downloads
/Users/brycesmith/Library/Mobile Documents/com~apple~CloudDocs/Downloads
/Users/brycesmith/Library/Containers/com.apple.MailCacheDelete/Data/Library/Mail Downloads
/Users/brycesmith/Music/iTunes/iTunes Media/Downloads
/Users/brycesmith/Library/Application Support/Vivaldi/Webstore Downloads
/Users/brycesmith/Library/Application Support/Google/Chrome/Webstore Downloads```
Then, if we wanted to navigate to the second path:
➜ folder git:(master) ✗ cd `!! | sed '2,2!d'`
# expanded by zsh to:
➜ folder git:(master) ✗ cd `mdfind downloads | grep Downloads$ | sed '2,2!d'`
➜ Downloads
This is just an illustrative example. I probably wouldn’t use this method to change directories. Point is, the same idea works for anything we might want to provide an argument to.
Parameters
mdfind
’s man page describes the various parameter keywords and comparison operators we might want to use.
Here are some useful ones.
Searching Dates:
With mdfind
, dealing with date/time metadata is easy.
We just pass a keyword:
➜ folder git:(master) ✗ mdfind date:today bryce
/Users/brycesmith/Library/Messages/Archive/2020-06-19/Chat with ... et al on 2020-06-19 at 20.14.28-1.ichat
When combined with the -onlyin dir flag, this can be a useful way to see file changes for a project over a timeframe.
➜ folder git:(master) ✗ mdfind -onlyin ../ image | grep wox
/Users/brycesmith/Files/posts/windows_environment/wox_pic.jpg
As described in the man page, the underlying function for date keywords is exposed.
This means we can pass values as a parameter.
So .today(-1)
would refer to the previous day.
The Null Flag:
Again from the man page, the null flag -0
:
Print an ASCII NUL character after each result path. This is useful when used in conjunction with xargs -0.
In other words, when command line utilities support this functionality, they agree to use null characters as terminators instead of whitespace. This allows us to pipe paths with spaces in them.
# no xargs -0
➜ folder git:(master) ✗ mdfind -0 Downloads | grep --null-data "Mail Downloads" | xargs cat
cat: /Users/brycesmith/Library/Containers/com.apple.mail/Data/Library/Mail: Is a directory
cat: Downloads: No such file or directory
cat: Downloads: No such file or directory
cat: Downloads: No such file or directory
# with xargs -0
➜ folder git:(master) ✗ mdfind -0 Downloads | grep --null-data "Mail Downloads" | xargs -0 ls
/Users/brycesmith/Library/Containers/com.apple.MailCacheDelete/Data/Library/Mail Downloads:
/Users/brycesmith/Library/Containers/com.apple.mail.MailQuickLookExtension/Data/Library/Mail Downloads:
/Users/brycesmith/Library/Containers/com.apple.mail/Data/Library/Mail Downloads:
D3F8A1BB-7F48-4E71-822F-73010BF1FCD1
Matching Filenames
Like find
, the -name
flag is used to match a particular filename or directory name. Note by default, mdfind will match any part of a path.
➜ blog git:(master) ✗ mdfind -name mdfind.md
/Users/brycesmith/Files/blog/mdfind/mdfind.md
File Attributes
Using mdls
, we can see the attributes for a particular object. For example:
➜ stlprettyprinter mdls Makefile.in
_kMDItemRenderData = {length = 11, bytes = 0x0900000000fc00c2410002}
kMDItemContentCreationDate = 2019-03-28 16:52:18 +0000
kMDItemContentCreationDate_Ranking = 2019-03-28 00:00:00 +0000
kMDItemContentModificationDate = 2019-03-28 16:52:18 +0000
kMDItemContentType = "dyn.ah62d4rv4ge80w5u"
kMDItemContentTypeTree = (
"dyn.ah62d4rv4ge80w5u",
"public.item",
"public.data",
"dyn.ah62d4rv4ge80w5u"
)
kMDItemDateAdded = 2019-03-28 16:52:18 +0000
kMDItemDateAdded_Ranking = 2019-03-28 00:00:00 +0000
kMDItemDisplayName = "Makefile.in"
kMDItemFSContentChangeDate = 2019-03-28 16:52:18 +0000
kMDItemFSCreationDate = 2019-03-28 16:52:18 +0000
kMDItemFSCreatorCode = ""
kMDItemFSFinderFlags = 0
kMDItemFSHasCustomIcon = (null)
kMDItemFSInvisible = 0
kMDItemFSIsExtensionHidden = 0
kMDItemFSIsStationery = (null)
kMDItemFSLabel = 0
kMDItemFSName = "Makefile.in"
kMDItemFSNodeCount = (null)
kMDItemFSOwnerGroupID = 20
kMDItemFSOwnerUserID = 501
kMDItemFSSize = 18760
kMDItemFSTypeCode = ""
kMDItemInterestingDate_Ranking = 2019-03-28 00:00:00 +0000
kMDItemKind = "Document"
kMDItemLogicalSize = 18760
kMDItemPhysicalSize = 20480
We can then use these attributes in our mdfind
searches. mdfind "kMDItemContentType = 'com.microsoft.word.doc'"
will find all Microsoft Word documents.
Final Thoughts
mdfind
is an excellent command for developers and CLI users.
The tool is fast and has some great ways we can narrow down searches.
mdfind
does have some limitations; it is not a replacement for the sheer versatility of find
.Still, mdfind
can speed up certain workflows immensely.