Compare folders and files on a Mac from the command line
Two folders, one on a working drive and one on a backup, and the question is whether they hold the same thing. It sounds like one question. It is at least four. Same file names, same sizes and dates, same bytes, or same everything including tags and permissions. Each of those has a different command, each returns a different answer for the same pair of folders, and picking the wrong one produces a clean report that quietly hides the difference that mattered. Worse, some of the advice written for Linux does not apply directly, because the versions of these tools shipped with recent macOS releases are not the GNU ones.
What follows is what each approach actually measures, which flags matter on a Mac specifically, the trap in the most commonly copied one line recipe, and the classes of difference that no shell command will report at all.
Decide what "the same" means before choosing a command
Sorting this first makes the tool choice nearly automatic.
Same inventory. Which paths exist on one side and not the other. This is the cheapest question and often the only one that matters after a partial copy. File contents are never read.
Same contents. Whether the bytes match. Expensive on large folders, because everything has to be read, but it is the only answer that survives a corrupted transfer.
Same as far as a sync tool cares. Size and modification time match, so a copy would skip the file. Fast, and what backup tools actually use, but it can report a match for a file whose contents differ if the size and timestamp happen to line up.
Same including metadata. Permissions, extended attributes, Finder tags, flags. This is where Mac specific surprises live, and no general purpose diff covers it.
A comparison that reports success against the first definition and a comparison that reports success against the fourth are not stronger and weaker versions of the same check. They are different checks.
diff -r, and why the Mac version behaves differently
The standard answer is diff -r, which walks both trees and reports both content differences and files present on only one side. Adding -q reduces the output to one line per difference, which is what is wanted for folders.
diff -qr /Volumes/Backup/Project ~/Project
Typical output names three kinds of result: files that differ, entries only in the left tree, and entries only in the right tree. The exit status is the part worth wiring into a script, because it is 0 when nothing differs and 1 when something does.
The detail specific to macOS is that diff here is not GNU diffutils. On macOS 26 the version string reads Apple diff (based on FreeBSD diff), and the usage output lists a smaller option set than the GNU manual describes. Colour is not one of the gaps, despite how often it is listed as one. --color=when appears in the manual page, takes never, always or auto, and paints removals red and additions green. auto emits the codes only when the output is a terminal, which is why a piped comparison looks plain and can be mistaken for an unsupported flag. The DIFFCOLORS environment variable overrides the two default colours, so DIFFCOLORS='01;35:01;36' diff --color=always a b recolours the same report without touching the command.
What is actually missing sits elsewhere in the option list. --from-file and --to-file, which GNU uses to compare one file against a list of others, return unrecognized option. So do --ignore-trailing-space and its short form -Z, and --left-column for trimming side by side output. Those are the places where a recipe written against GNU diffutils stops rather than degrades, and each one fails loudly, which at least makes it obvious. The options that do matter for folder work are present:
| Flag | What it does |
|---|---|
-r |
Recurse into subdirectories |
-q |
Report only that files differ, not how |
-s |
Also report files that are identical |
-N |
Treat a missing file as empty, so it shows as a full difference |
-x pattern |
Skip entries matching a pattern |
-X file |
Skip entries listed in a file |
-y |
Side by side output for a single pair |
--color=always |
Keep the red and green even when the output is piped or paged |
-x is the one that saves time in practice. Comparing two folders that have both been opened in Finder produces noise from .DS_Store files, and excluding them keeps the report readable.
The limit of this approach is that diff reads contents. On a folder of large video files it will read every byte of both sides, which is correct but slow, and it says nothing about which side is newer.
rsync dry runs answer the direction question
diff says the two sides differ. It does not say what would happen if one were copied onto the other. That is what a dry run is for.
rsync -rin --checksum --delete ~/Project/ /Volumes/Backup/Project/
-n performs no writes. -i prints an itemised line per action, so >f+++++++ marks a file that would be created, >fc...... marks one whose checksum differs, and deleting lines appear for destination files absent from the source. --checksum forces content comparison rather than the size and time shortcut, which is the difference between the second and third definitions above. The trailing slashes matter: with them the contents of the source folder are compared against the contents of the destination, without them the source folder is treated as an item to place inside the destination.
There is a Mac specific caveat here too. On macOS 26, rsync --version reports openrsync, describing itself as compatible with rsync 2.6.9. It covers the common flags, but options familiar from the GNU build are not all present. -X for extended attributes and -A for ACLs both return invalid option on that build. Any workflow that relied on those flags to carry Finder tags across a copy needs checking rather than assuming, and ditto is the built in tool that does preserve extended attributes.
Checksum lists, and the sort trap that breaks them
When the two folders cannot be mounted at the same time, the comparison has to happen on lists rather than on files. Generate a hash per file on each side, then compare the two lists.
cd side_a && find . -type f -exec shasum {} + | sort > /tmp/A.txt
cd side_b && find . -type f -exec shasum {} + | sort > /tmp/B.txt
comm -3 /tmp/A.txt /tmp/B.txt
comm -3 drops the lines the two lists share and prints what is left, with the left side unindented and the right side indented by a tab. Files whose hash and path both match disappear from the output, so whatever remains is the real difference.
The common failure is sorting by the wrong key. Writing sort -k2 to order the lists by path looks tidier, and it breaks the comparison. comm merges two streams assuming both are sorted by whole line, so ordering by the second field only puts the streams out of step and identical files start appearing in the output as differences. A plain sort with no key is what comm expects. If ordering by path is genuinely needed, join on the path field is the tool for that, not comm.
shasum on macOS defaults to SHA-1, which is adequate for detecting accidental corruption and copy errors. shasum -a 256 switches to SHA-256 if the comparison needs to resist deliberate tampering rather than accident.
mtree records the state, so the comparison can happen later
macOS also ships mtree at /usr/sbin/mtree, which is built for exactly the case the checksum list approximates. It writes a specification describing a directory tree, then checks any other tree against that specification.
mtree -c -p side_a -k sha256digest,size,type > /tmp/spec.txt
mtree -f /tmp/spec.txt -p side_b
The report uses three words rather than a diff format. A file whose digest does not match is reported as changed, along with both digests. A file present only in the checked tree is extra. A file listed in the specification and absent from the tree is missing. That vocabulary maps directly onto the inventory and contents questions from the start of this article, without any post processing of the output.
The -k list decides what gets recorded, and that is where mtree goes further than a hash list. Adding mode, uid, gid or flags puts permissions and ownership into the specification, so a restore that produced correct contents under the wrong owner is caught rather than passed. Recording fewer keys makes the check faster and the specification smaller.
The specification is a plain text file, so it can be committed, archived alongside a backup, or carried to another machine. A tree can then be verified months later against how it looked when the specification was written, with nothing but the file and the original folder.
What no shell command in this list will report
Three categories slip past all of the above, and two of them are specific to how macOS stores files.
Extended attributes and Finder tags. Take two files with identical contents, add a Finder tag to one of them, and diff between them exits 0. It compared the data fork and found no difference, which is correct and also not what a person looking at two coloured files in Finder would call the same. ls -l@ shows an @ in the permissions column when attributes are present, and xattr -l lists them.
Names that are equal without being identical. APFS as formatted by default is case insensitive, so a file created as Abc.txt answers to abc.txt. It is also normalisation insensitive, so a Japanese name containing a voiced character resolves whether it was written in composed or decomposed form. Two lists exported from a Mac and a Linux server can therefore contain names that look different in the text file and refer to the same file, or names that look identical and cannot both exist on the Mac side.
Timestamps and permissions. diff -qr never mentions them, and neither does a plain hash list. A restore that produced correct contents with wrong ownership passes both checks and still fails to run. This is the one category with a built in answer, since mtree records mode, uid and gid when asked to.
If any of those matter for the job at hand, the comparison needs ls -l@, xattr, or a tool built with the Mac file system in mind rather than a portable text diff.
When a visual tool earns its place
Command output is a list. Reviewing which side of a difference to keep is easier to do visually, and several tools do folder level comparison on macOS. Prices below are as listed on each vendor's own page.
| Tool | Price | Notes |
|---|---|---|
| FileMerge | Included with Xcode | Also available as opendiff on the command line |
| Kaleidoscope | 8 US dollars per month, 1 user and 3 devices | Free trial for 7 days, requires macOS Sequoia 15 or later |
| Beyond Compare 5 | 35 US dollars Standard, 70 US dollars Pro, per user | Windows, macOS and Linux |
| Araxis Merge | 129 US dollars Standard, 269 US dollars Professional | Perpetual licence covering Windows and macOS, 30 day trial |
| ForkLift 4 | 19.95 US dollars | Dual pane file manager with a folder sync view |
FileMerge is the one most people already have and never open. It ships inside Xcode, and opendiff at /usr/bin/opendiff launches it against two paths, so a folder comparison started in a terminal can be finished in a window.
The split worth noticing is between tools that compare and tools that also reconcile. A diff viewer tells you what differs. A sync view, of the kind ForkLift describes as identifying matching, modified, new and deleted files, is built to then act on the answer in one or both directions. Choosing between them is really a question of whether the comparison is an audit or the first half of a copy.
What to change first
Pick the definition of "the same" before the command, and write it down: inventory, contents, or contents plus metadata. Then keep the comparison and the folder view in the same place, because the slow part of this work is rarely the command itself, it is reading a list of paths in a terminal and then hunting for those paths in a separate window. A file manager with the shell built into it removes that hop, and the comparison with other file managers shows where each tool draws the line between viewing and acting. Atriens is worth a look if the folder list and the command output currently live in two windows.
Frequently asked questions
Why does diff say two folders match when Finder clearly shows differences?
diff compares file contents and inventory. It ignores Finder tags, extended attributes, permissions, timestamps and colour labels, so two files with the same bytes and different tags are reported as identical. Use ls -l@ or xattr -l to see the attributes that the content comparison skips.
Is diff on macOS the same as diff on Linux?
Not exactly. On macOS 26 the version reports as Apple diff based on FreeBSD diff, and its option list is smaller than GNU diffutils. Colour output is not one of the differences: --color=never, --color=always and --color=auto are all documented and working, and DIFFCOLORS changes the two colours. The gaps are --from-file, --to-file, --ignore-trailing-space and --left-column, each of which exits with unrecognized option. The core folder flags, -r, -q, -s, -N, -x and -X, all work.
How do you compare two folders without copying anything?
Add -n to rsync, which performs a dry run and writes nothing. Combined with -i it prints one itemised line per file that would change, and with --checksum it compares contents rather than size and modification time. Read the output before removing -n.
Which is faster for large folders, diff or a checksum list?
Both have to read every byte, so neither is free. The difference is reuse: a checksum list can be saved and compared later against a list from another machine, so the cost is paid once per side rather than once per comparison. For a single comparison of two mounted folders, diff -qr is simpler.