Compare text files on a Mac and see only what changed

The quickest way to compare two text files is to paste both into a web page, and that is what most people do. It works, and it has one property worth noticing before it becomes a habit: the text goes to someone else's server. Diffchecker says as much about its own desktop application, describing it as operating "exclusively on your computer", with no interaction with the company's web servers at all. That is a fair description of what the browser version does not do.

A Mac already has five comparison commands installed and a graphical one that arrives with Xcode, and the reason to know them is not thrift. It is that a paste-into-a-textarea comparison answers exactly one question, and most real comparisons turn out to be a different question wearing the same clothes.

What is installed, and which question each one answers

Five commands ship with macOS, and they are not interchangeable.

diff reports the changes needed to turn the first file into the second. It is line based, which is the single most important thing about it: a one character change is reported as a whole line removed and a whole line added.

cmp answers whether two files are byte for byte identical, and if not, where the first difference is. Its -l option prints the byte number and the differing byte values in octal for every difference, and -x does the same in hexadecimal. For asking whether two files are the same at all, it does less work than diff and gives a cleaner answer.

comm compares two sorted files and prints three columns: lines only in the first, lines only in the second, and lines in both. Suppressing columns with -1, -2 and -3 turns it into a set operation. It is the right tool when the files are lists rather than documents and line order carries no meaning. Its own manual page warns that it "assumes that the files are lexically sorted", and it produces confident nonsense when they are not.

sdiff and diff3 cover side-by-side merging and three-way comparison.

opendiff opens FileMerge, the graphical comparison tool, and its manual page notes that "opendiff and FileMerge are installed as part of the Xcode Developer Tools". It takes an -ancestor file for three-way comparison and a -merge destination, which is the case the web tools are weakest at.

Five reasons two identical-looking files differ

Most of the confusion around comparing text files comes from a report of differences on lines that look the same on screen. There are five usual causes, and diff has a flag for four of them.

Line endings are the first. A file written on Windows ends lines with a carriage return and a line feed; one written on a Mac or Linux ends with a line feed alone. Every line differs, invisibly. --strip-trailing-cr removes the carriage return on input and the report collapses to whatever actually changed.

Trailing whitespace is the second. -b, documented as causing "trailing blanks (spaces and tabs) to be ignored, and other strings of blanks to compare equal", handles the common case. -w goes further and ignores whitespace entirely, so that if ( a == b ) and if(a==b) compare equal. That is sometimes what is wanted and sometimes a way to hide a real change, so it is worth choosing deliberately rather than reaching for -w first.

Blank lines are the third, and -B ignores chunks that consist only of them.

Encoding is the fourth, and it announces itself. diff prints Binary files ... differ rather than a report when it finds bytes it does not consider text, which happens with UTF-16 files as readily as with an image. -a forces a text comparison, though the result on a UTF-16 file is rarely readable.

The fifth has no flag, and it catches anyone working in Japanese, Korean, or any language with combining marks. Two strings can be identical on screen and different in bytes, because an accented or voiced character can be stored as one code point or as a base character plus a combining mark. diff compares contents, and its option list contains nothing that normalises composition, so those two strings are two different lines as far as it is concerned. A string typed locally and a string that arrived from another system are the usual pair.

There is a sixth, minor case worth a mention: a missing newline at the end of the last line makes that line differ even when its visible text matches.

For the situation where a specific kind of change should be ignored rather than a specific kind of whitespace, -I pattern ignores changes whose lines all match an extended regular expression, and it can be given more than once. Timestamps and generated version strings are what it is for.

The Mac version lets you pick the algorithm

This is the part missing from nearly every guide, because it is recent.

diff on macOS supports three algorithms through -A or --algorithm. Myers "finds the shortest edit which transforms one input into the other". Patience "attempts to create more aesthetically pleasing diff output by logically grouping lines". Stone, also known as Hunt-McIlroy, "looks for the longest common subsequence between compared files" and is "maintained for compatibility".

The default is Myers, with one documented exception: when POSIXLY_CORRECT or POSIX_PEDANTIC is set in the environment, the default becomes Stone. That is a real source of "the same command gives different output on the other machine", and it is an environment variable rather than anything about the files.

Patience is the one worth trying by hand. On a file where a block was moved or a repeated structure was edited, Myers can produce a technically minimal report that reads as though unrelated lines were swapped. diff -A patience on the same pair often produces a report that matches what a person would describe as the change. The output is not more correct, it is more legible, which is the whole point when a human is reading it.

Reading it side by side, in the terminal

diff -y prints two columns with a marker between them: a space where the lines match, | where both files have a different line, < where only the first file has it, and > where only the second does. The default width is 130 columns, adjustable with -W.

Two additions make that usable. --suppress-common-lines drops the matching lines, so only the changes remain. --color=auto colours additions green and removals red, and the manual page notes that auto "will use color if the output is a tty and the COLORTERM environment variable is set to a non-empty string". The colours themselves come from DIFFCOLORS in the form add:rm.

For a report meant to be sent to someone or applied later, -u is the format to use. A unified diff puts all changed lines in a single section with three lines of context, and it is what patch reads.

Line level is not always the level

diff cannot show which word inside a line changed, and for prose or a long configuration line that is the only question worth answering. The tool for it is already installed too.

git diff --no-index <path> <path> compares two files on disk with no repository involved. The documentation notes the --no-index option can be omitted "when running the command outside a working tree controlled by Git", and that this form implies --exit-code.

Adding --word-diff marks changes at word level, with words delimited by whitespace by default. --word-diff-regex=. goes one step further: the documentation states it "will treat each character as a word and, correspondingly, show differences character by character". For finding the single changed digit in a long line, that is the shortest route on a Mac without installing anything.

Using the answer as a gate rather than reading it

diff exits 0 when no differences were found, 1 when differences were found, and greater than 1 on error. That makes it usable in a condition rather than a thing to read, and -q reduces the output to a single line stating that the files differ.

That is the form to reach for when the comparison is a check rather than a review: does the deployed configuration still match the one in the repository, did the export change since yesterday. cmp -s does the same job with no output at all when only the exit status matters.

What the paid and web tools are buying

Nothing above does three-way merging well, and nothing above shows a folder of comparisons at once. That is what the paid tools are for.

Tool Platforms Price Notes
diff, cmp, comm, sdiff, diff3 macOS, installed Free Line and byte comparison, no merge editor
FileMerge via opendiff macOS, with Xcode Free Three-way comparison and a merge editor
Diffchecker Desktop Windows, macOS, Linux Pro + Desktop listed at 15 US dollars per user per month, 14 day trial with no credit card Runs locally, described as no interaction with their servers
Beyond Compare Windows, macOS, Linux Standard 35 US dollars per user, Pro 70 US dollars per user Perpetual per-user licence, free evaluation
Kaleidoscope macOS only, Sequoia (15.x) or later Subscription from 8 US dollars per month, free for 7 days Mac-only, subscription rather than one-off
WinMerge Windows XP SP3 or newer Free, open source No macOS version

The distinctions that matter when choosing are the licence shape and the platform list. Beyond Compare is a per-user purchase and runs on three platforms. Kaleidoscope is a subscription and runs on one. WinMerge appears in most comparison roundups and has no Mac build, which is worth knowing before following a tutorial that recommends it. Diffchecker exists as both a browser tool and a desktop application, and the difference between them is where the text goes.

Why the installed diff may not match the tutorial

One piece of history explains a category of confusion. The manual page records that "the diff implementation used in FreeBSD was GNU diff until FreeBSD 11.4", that it "was replaced in FreeBSD 12.0 by a BSD-licensed implementation written by Todd Miller", and that "some GNUisms were lost in the process".

The practical consequence is that flags from a Linux guide may simply not be there, and that at least one is present but inert: --speed-large-files is documented as a "stub option for compatibility with GNU diff". Checking man diff on the machine in front of you is faster than debugging why a copied command did nothing.

Where comparing stops and the switching starts

The comparison itself takes a second. What takes the time is everything around it: locating both files, getting their paths into a command, reading a report in a terminal, then going back to the folder to act on whichever side was wrong. For two files in the same directory that is fine. For a file in a download folder against one buried in a project, most of the elapsed time is window changes.

That is a layout problem rather than a tooling problem, and it is what a file manager with a built-in terminal is for: two files selected in a listing, the comparison run against them, the report read without leaving the window. The shape of that is in Features, and how it sits against the other options on the Mac is in Compared with other file managers.

What to change first

Run one comparison with --strip-trailing-cr and -b added and see whether the report shrinks, because if it does, the difference was never in the text. Then try diff -A patience on the same pair and keep whichever report reads more like the change actually made. Keeping the file listing and the shell in one window, which is the problem Atriens is built around, removes the part of this that is only switching.

Frequently asked questions

Why does diff report every line as changed when the files look the same?

Almost always line endings. A file written on Windows ends each line with a carriage return and a line feed, so every line differs invisibly. Adding --strip-trailing-cr removes them on input. If the report still shows changes on matching lines, trailing whitespace is the next suspect, handled with -b.

How do you see which word changed instead of which line?

Use Git, which is installed on most Macs: git diff --no-index fileA fileB --word-diff marks changes at word level with no repository involved. For character level, --word-diff-regex=. treats every character as a word. The diff command is line based and has no equivalent option.

Is there a way to compare two files without sending the text anywhere?

Yes, several. diff, cmp, comm, sdiff and diff3 are already installed and run locally, and FileMerge through opendiff gives a graphical view once Xcode is present. Among paid tools, Diffchecker Desktop is described by its maker as operating exclusively on your computer with no interaction with their servers.

Which tool handles a three-way merge on a Mac?

FileMerge, opened with opendiff file1 file2 -ancestor ancestorFile -merge mergeFile, is the option that ships with Xcode. Among paid tools, Beyond Compare is licensed per user at 35 US dollars for Standard and 70 for Pro across Windows, macOS and Linux, and Kaleidoscope is macOS only on a subscription starting at 8 US dollars per month.

Can a comparison be used as a pass or fail check rather than something to read?

Yes. diff exits 0 when the files match, 1 when they differ and above 1 on error, and -q reduces the output to a single line. cmp -s gives the same result with no output at all. Note that git diff --no-index implies --exit-code, so it behaves the same way.

Back to all posts