Using the `mdfind` Command

June 03, 2020

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.


© Bryce Smith, 2021