Text processing
diff
Compare two files or directories line by line and print the changes that turn one into the other.
diff [OPTION...] FILE1 FILE2
diff answers "what changed?" by printing the smallest set of line edits that turns the first
file into the second. Read its output as instructions: lines marked - (or <) are only in
the first file, lines marked + (or >) only in the second.
Use diff -u almost always. The unified format shows a few lines of context around each
change, with ---/+++ headers naming the files and @@ lines giving line numbers. It is the
format code review tools show and the one patch applies. The default "normal" format is
terse and hard to read; side-by-side (-y) is good for short files on a wide terminal.
The exit status is reliable for scripts: 0 when the inputs are the same, 1 when they
differ, 2 on trouble such as a missing file. That makes diff -q a good "did this change?"
test. For binary files or a byte-exact check, cmp is the simpler tool.
Examples
Compare two files
host = 0.0.0.0
port = 8080
log_level = info
timeout = 30
host = 0.0.0.0
port = 9090
log_level = info
timeout = 30
retries = 3
diff old.conf new.conf
2c2 < port = 8080 --- > port = 9090 4a5 > retries = 3
The default format prints commands: 2c2 means "line 2 changed into line 2", 4a5 "after
line 4, add line 5". < lines come from the first file, > from the second. The exit status
is 1 because the files differ.
Show a unified diff with context
host = 0.0.0.0
port = 8080
log_level = info
timeout = 30
host = 0.0.0.0
port = 9090
log_level = info
timeout = 30
retries = 3
diff -u old.conf new.conf
--- old.conf 2026-10-31 20:19:37.406788005 +0000 +++ new.conf 2026-10-31 20:19:37.406788005 +0000 @@ -1,4 +1,5 @@ host = 0.0.0.0 -port = 8080 +port = 9090 log_level = info timeout = 30 +retries = 3
-u prints the file names and times, then a hunk header @@ -1,4 +1,5 @@ (lines 1-4 of the
old file become lines 1-5 of the new one) and the lines themselves: - removed, + added,
a space for unchanged context. -U 1 would show one line of context instead of three.
Use the exit status to test for changes
cp todo.txt todo.copy
diff -q todo.txt todo.copy && echo "same"
echo 'call the bank' >> todo.copy
diff -q todo.txt todo.copy || echo "exit status: $?"
same Files todo.txt and todo.copy differ exit status: 1
-q prints only whether the files differ. Identical files give exit status 0 and no
output, so the && branch runs. After appending a line, diff reports the difference and
exits with 1.
A missing file is exit status 2
diff todo.txt nosuch.txt
diff: nosuch.txt: No such file or directory
Status 2 means diff could not compare at all. Scripts that treat any non-zero status as
"files differ" will miss this case; test for 1 specifically.
Compare side by side
sed 's/fix/FIX/; /tests/d' todo.txt > todo.new
diff -y -t -W 50 todo.txt todo.new
buy milk buy milk fix login bug | FIX login bug write tests | FIX logout bug fix logout bug < deploy v2.4 deploy v2.4
-y shows the files in two columns: | marks a changed line, < a line only on the left
and > one only on the right. diff pairs write tests with FIX logout bug because it
matches whole lines only and has no idea they are unrelated. -W 50 sets the total width
and -t expands tabs so the columns line up. --suppress-common-lines hides the unchanged
lines.
Ignore whitespace and blank-line changes
sed 's/ = /=/; /^$/d' data/settings.conf > reformatted.conf
diff -q data/settings.conf reformatted.conf
diff -w -B data/settings.conf reformatted.conf && echo "only whitespace changed"
Files data/settings.conf and reformatted.conf differ only whitespace changed
The copy has its spaces around = removed and blank lines deleted. A plain comparison says
the files differ; -w ignores all whitespace within lines and -B ignores added or removed
blank lines, so the second diff finds nothing.
Ignore Windows line endings
printf 'line one\nline two\n' > unix.txt
diff -q unix.txt data/windows.txt
diff --strip-trailing-cr unix.txt data/windows.txt && echo "same text"
Files unix.txt and data/windows.txt differ same text
The two files hold the same words, but windows.txt ends each line with CR LF, so every
line differs. --strip-trailing-cr drops the carriage returns before comparing.
Compare two directories
Setup run before this example
cp -r src src.orig
sed -i 's/hello, world/hello, demo/' src/main.c
rm src/util.sh
echo 'print("new")' > src/new.pydiff -r src.orig src
diff -r src.orig/main.c src/main.c
4c4
< printf("hello, world\n");
---
> printf("hello, demo\n");
Only in src: new.py
Only in src.orig: util.sh -r walks both trees. Files present on one side only are reported as Only in ..., and
files that exist in both are compared line by line. Add -q for just the list of
differing files.
Make a patch for a whole directory
Setup run before this example
cp -r src src.orig
sed -i 's/hello, world/hello, demo/' src/main.c
echo 'print("new")' > src/new.pydiff -ruN src.orig src
diff -ruN src.orig/main.c src/main.c
--- src.orig/main.c 2026-10-31 20:19:37.745796697 +0000
+++ src/main.c 2026-10-31 20:19:37.747796748 +0000
@@ -1,6 +1,6 @@
#include <stdio.h>
int main(void) {
- printf("hello, world\n");
+ printf("hello, demo\n");
return 0;
}
diff -ruN src.orig/new.py src/new.py
--- src.orig/new.py 1970-01-01 00:00:00.000000000 +0000
+++ src/new.py 2026-10-31 20:19:37.747796748 +0000
@@ -0,0 +1 @@
+print("new") -N treats a file missing on one side as empty, so new files appear as full additions
instead of an Only in note. -ruN is the classic recipe for a patch that patch can
apply to a copy of the original tree.
Compare command output, not files
diff <(cut -d, -f4 data/users.csv | sort -u) <(printf '%s\n' dept engineering sales support)
3d2 < marketing
<(...) (a bash feature) gives diff a file name that reads from the command. Here it checks
the list of departments in the CSV against an expected list and finds the unexpected
marketing.
List only which files differ
Setup run before this example
cp -r data data.bak
sed -i 's/4.50/4.75/' data/sales.csv
echo 'cherry' >> data/fruits.txtdiff -rq data.bak data
Files data.bak/fruits.txt and data/fruits.txt differ Files data.bak/sales.csv and data/sales.csv differ
With -r and -q, diff prints one line per file that differs and does not show the
changes. Use it to see what a script touched, then look at single files with diff -u.
Common pitfalls
diffoutput order matters:diff old newshows what you must do tooldto getnew. Swapping the arguments flips every+and-.In scripts, exit status 1 is not an error. Under
set -e, adiffthat finds changes stops the script; useif diff -q a b >/dev/null; thenordiff ... || true.Line endings and trailing spaces make files differ that look identical. Try
--strip-trailing-cror-wbefore staring at the output, orcat -Aboth files.Diffing very large files compares them in memory and can be slow;
cmpis faster when you only need to know whether they differ.
GNU vs BSD and macOS
The examples above ran the GNU/Linux version. These are the differences that matter on macOS and the BSDs (written from their documentation, not run here).
The everyday options (
-u,-r,-q,-N,-w,-B,-y) work in both GNU diff and the diff that ships with macOS and the BSDs. Rarer GNU long options such as--strip-trailing-crmay be missing there; check the local man page.
Options worth knowing
A selection, described in our own words. For the full list, see diff(1) or diff --help.
| Option | What it does |
|---|---|
-u, --unified | Print a unified diff with three lines of context. |
-U, --unified NUM | Unified diff with NUM lines of context. |
-c, --context | Print an older-style context diff. |
-q, --brief | Only report whether the files differ. |
-s, --report-identical-files | Say so when two files are the same. |
-r, --recursive | Compare directories recursively. |
-N, --new-file | Treat files missing on one side as empty. |
-y, --side-by-side | Show the files in two columns. |
-W, --width NUM | Total output width for side-by-side mode. |
--suppress-common-lines | In side-by-side mode, hide lines that are the same. |
-w, --ignore-all-space | Ignore all whitespace within lines. |
-b, --ignore-space-change | Ignore changes in the amount of whitespace. |
-B, --ignore-blank-lines | Ignore changes that only add or remove blank lines. |
-i, --ignore-case | Ignore case differences. |
--strip-trailing-cr | Remove carriage returns at the ends of lines before comparing. |
-x, --exclude PATTERN | Skip files whose names match PATTERN when comparing directories. |
-a, --text | Treat all files as text. |
--color[=WHEN] | Colorize the output: never, always or auto. |
Related
patchApply a diff to files, turning the original versions into the changed ones.cmpCompare two files byte by byte and report the first difference, or just whether they differ.commCompare two sorted files line by line and show lines unique to each and lines in both.sedEdit a stream of text with a script of commands, most often to substitute text or delete lines.- Process substitutionUse <(cmd) and >(cmd) to hand a command's output to a program that wants a file name, keep while-read loops in the current shell, and avoid its timing traps.
- Exit codesWhat exit statuses mean, how $? works, the reserved values 126, 127 and 128+N, PIPESTATUS, and how to choose codes for your own scripts.