opscape

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

11 of 11 examples run in Debian 13.7 on 2026-10-31 · diffutils 1:3.10-4 · manual: diff(1)

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

old.conf
host = 0.0.0.0
port = 8080
log_level = info
timeout = 30
new.conf
host = 0.0.0.0
port = 9090
log_level = info
timeout = 30
retries = 3
diff old.conf new.conf
explain
outputexit 1
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

old.conf
host = 0.0.0.0
port = 8080
log_level = info
timeout = 30
new.conf
host = 0.0.0.0
port = 9090
log_level = info
timeout = 30
retries = 3
diff -u old.conf new.conf
explain
outputexit 1
--- 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: $?"
output
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
explain
outputexit 2
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
outputexit 1
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"
output
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"
output
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.py
diff -r src.orig src
explain
outputexit 1
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.py
diff -ruN src.orig src
explain
outputexit 1
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)
explain
outputexit 1
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.txt
diff -rq data.bak data
explain
outputexit 1
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

  • diff output order matters: diff old new shows what you must do to old to get new. Swapping the arguments flips every + and -.

  • In scripts, exit status 1 is not an error. Under set -e, a diff that finds changes stops the script; use if diff -q a b >/dev/null; then or diff ... || true.

  • Line endings and trailing spaces make files differ that look identical. Try --strip-trailing-cr or -w before staring at the output, or cat -A both files.

  • Diffing very large files compares them in memory and can be slow; cmp is 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-cr may 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.

OptionWhat it does
-u, --unifiedPrint a unified diff with three lines of context.
-U, --unified NUMUnified diff with NUM lines of context.
-c, --contextPrint an older-style context diff.
-q, --briefOnly report whether the files differ.
-s, --report-identical-filesSay so when two files are the same.
-r, --recursiveCompare directories recursively.
-N, --new-fileTreat files missing on one side as empty.
-y, --side-by-sideShow the files in two columns.
-W, --width NUMTotal output width for side-by-side mode.
--suppress-common-linesIn side-by-side mode, hide lines that are the same.
-w, --ignore-all-spaceIgnore all whitespace within lines.
-b, --ignore-space-changeIgnore changes in the amount of whitespace.
-B, --ignore-blank-linesIgnore changes that only add or remove blank lines.
-i, --ignore-caseIgnore case differences.
--strip-trailing-crRemove carriage returns at the ends of lines before comparing.
-x, --exclude PATTERNSkip files whose names match PATTERN when comparing directories.
-a, --textTreat all files as text.
--color[=WHEN]Colorize the output: never, always or auto.

Related