diff
Compare files or directories and show what changed
diff compares two files line by line and reports what's different. Its exit code carries real
information: 0 means the files are identical, 1 means they differ, 2 means something went
wrong (a missing file, a permission error). Scripts can and should check it directly instead of
parsing output.
The default output, a compact 3c3,4 / 8a10,13 style, is precise but hard to read. -u
(unified format) is what everyone uses in practice: a few lines of context around each
change, - for removed lines, + for added ones. It's also exactly the format patch expects,
which is what makes diff -u old new > changes.patch followed by patch < changes.patch
work. It is the same mechanism behind git diff and every .patch file you've ever
downloaded.
-r makes diff recurse into directories, comparing every file it finds and reporting which
ones exist on only one side. -w, -b, -i, and -B ignore whitespace, whitespace amount,
case, and blank-line changes respectively, which is useful for confirming two files are
meaningfully the same even when formatting drifted.
Sample files used on this page
Every example below was run against these files. Recreate them to follow along.
nginx-old.conf the starting version - 10 lines
server {
listen 80;
server_name example.com;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
nginx-new.conf the same file after adding IPv6, a second server_name, and an /api/ block
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
location /api/ {
proxy_pass http://127.0.0.1:8080;
}
}
sp1.txt / sp2.txt differ only in the amount of whitespace, for -w
==> sp1.txt <==
a b
==> sp2.txt <==
a b
ws1.txt / ws2.txt same words, different spacing throughout, for -b
==> ws1.txt <==
a b c
==> ws2.txt <==
a b c
c1.txt / c2.txt differ only in case, for -i
==> c1.txt <==
Hello
==> c2.txt <==
hello
bl1.txt / bl2.txt differ only by a blank line inserted after the first, for -B
==> bl1.txt <==
a
b
c
==> bl2.txt <==
a
b
c
crlf-a.txt / crlf-b.txt identical text, different line endings, for --strip-trailing-cr
==> crlf-a.txt <==
line1␍
line2␍
==> crlf-b.txt <==
line1
line2
confA/ and confB/ two trees for the -r examples: common.conf is identical in both, nginx.conf is the old/new pair above, and each has one file the other lacks
confA:
common.conf
nginx.conf
onlyA.conf
confB:
common.conf
nginx.conf
onlyB.conf
Basic comparison and exit codes
diff's exit code is as useful as its output - 0 for identical, 1 for different, 2 for an error.
Compare two files
diff nginx-old.conf nginx-new.conf
The default format: 3c3,4 means line 3 of the first file changed into lines 3-4 of the second; 8a10,13 means after line 8, four lines were added. < marks the old side, > the new side.
Show output
3c3,4
< server_name example.com;
---
> listen [::]:80;
> server_name example.com www.example.com;
8a10,13
> }
>
> location /api/ {
> proxy_pass http://127.0.0.1:8080;
Identical files produce no output and exit 0
diff nginx-old.conf nginx-old.conf
Comparing a file to itself (or to an identical copy) prints nothing - check the exit code, not the output, to know whether files matched.
Just report whether files differ, not how
diff -q nginx-old.conf nginx-new.conf
-q (--brief) skips the line-by-line detail entirely - useful in a script that only needs a yes/no answer.
Show output
Files nginx-old.conf and nginx-new.conf differ
Brief mode on identical files stays silent
diff -q nginx-old.conf nginx-old.conf
Same as full diff - no output at all when there's nothing to report, just exit 0.
Comparing against a file that doesn't exist is an error, not a difference
diff nginx-old.conf missing.conf
Exit code 2, not 1 - diff distinguishes 'these differ' from 'I couldn't actually compare them.' Worth checking for in a script rather than assuming any non-zero exit means a content difference.
Show output
diff: missing.conf: No such file or directory
Explicitly confirm two files are identical
diff -s nginx-old.conf nginx-old.conf
-s (--report-identical-files) is the mirror image of -q: prints a positive confirmation instead of staying silent, useful when silence-means-success would otherwise be ambiguous in a script's output.
Show output
Files nginx-old.conf and nginx-old.conf are identical
Output formats
The default format is compact but hard to read. Three alternatives trade brevity for clarity.
Unified format - the one everyone uses
diff -u nginx-old.conf nginx-new.conf
A few lines of shared context (unmarked), then - for removed lines and + for added ones. This is also exactly what patch expects as input, and the format git diff itself is built on.
Show output
Your output will differ: the timestamps in the --- and +++ header lines are when the sample files were created
--- nginx-old.conf 2026-08-14 09:15:00.000000000 +0000
+++ nginx-new.conf 2026-08-14 09:15:05.000000000 +0000
@@ -1,10 +1,15 @@
server {
listen 80;
- server_name example.com;
+ listen [::]:80;
+ server_name example.com www.example.com;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
+
+ location /api/ {
+ proxy_pass http://127.0.0.1:8080;
+ }
}
Control how many context lines unified format shows
diff -U1 nginx-old.conf nginx-new.conf
-U takes a number - fewer context lines than the default 3, for a tighter diff when you only care about the exact change, not its surroundings.
Show output
Your output will differ: the timestamps in the --- and +++ header lines are when the sample files were created
--- nginx-old.conf 2026-08-15 10:09:33.149381006 +0000
+++ nginx-new.conf 2026-08-15 10:09:33.149381006 +0000
@@ -2,3 +2,4 @@
listen 80;
- server_name example.com;
+ listen [::]:80;
+ server_name example.com www.example.com;
root /var/www/html;
@@ -9,2 +10,6 @@
}
+
+ location /api/ {
+ proxy_pass http://127.0.0.1:8080;
+ }
}
Context format - the older alternative to unified
diff -c nginx-old.conf nginx-new.conf
Shows both versions of each changed block in full, marked with !, rather than interleaving - and + lines - more verbose than unified, mostly seen in older codebases and some BSD tooling.
Show output
Your output will differ: the timestamps in the *** and --- header lines are when the sample files were created
*** nginx-old.conf Sun Aug 16 09:29:21 2026
--- nginx-new.conf Sun Aug 16 09:29:21 2026
***************
*** 1,10 ****
server {
listen 80;
! server_name example.com;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
--- 1,15 ----
server {
listen 80;
! listen [::]:80;
! server_name example.com www.example.com;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
+
+ location /api/ {
+ proxy_pass http://127.0.0.1:8080;
+ }
}
Side-by-side comparison
diff -y --width=60 nginx-old.conf nginx-new.conf
-y puts both files in two columns with changed lines marked by | (changed), < (removed), or > (added) in the middle. --width caps the line length so it doesn't overflow a normal terminal.
Show output
server { server {
listen 80; listen 80;
server_name example.com; | listen [::]:80;
> server_name example.com
root /var/www/html; root /var/www/html;
index index.html; index index.html;
location / { location / {
try_files $uri $uri/ try_files $uri $uri/
> }
>
> location /api/ {
> proxy_pass http://12
} }
} }
ed script format - output you could feed back into ed
diff -e nginx-old.conf nginx-new.conf
A niche format: valid input for the ed line editor to transform the old file into the new one. Rarely used directly today, but it's a reminder that diff predates every format built on top of it.
Show output
8a
}
location /api/ {
proxy_pass http://127.0.0.1:8080;
.
3c
listen [::]:80;
server_name example.com www.example.com;
.
Ignoring differences that don't matter
See whitespace-only changes flagged as real differences
diff sp1.txt sp2.txt
'a b' and 'a b' - two spaces versus one - count as a difference by default.
Show output
1c1
< a b
---
> a b
Ignore whitespace differences entirely
diff -w sp1.txt sp2.txt
-w (--ignore-all-space) treats any amount of whitespace as equivalent, including none at all - the strongest whitespace-ignoring option.
Ignore changes in the amount of whitespace, but not its presence
diff -b ws1.txt ws2.txt
-b (--ignore-space-change) is gentler than -w: a b c and a b c are still both a-then-b-then-c with spaces between, even though the exact spacing differs.
Ignore case differences
diff -i c1.txt c2.txt
-i treats Hello and hello as the same line - useful when comparing output that might differ only in casing conventions.
See a blank-line-only change reported as a real difference
diff bl1.txt bl2.txt
bl2.txt differs from bl1.txt only by an inserted blank line, and by default that counts: 1a2 means a line was added after line 1.
Show output
1a2
>
Ignore changes that only add or remove blank lines
diff -B bl1.txt bl2.txt
-B treats purely blank-line changes as no change at all, so this prints nothing and exits 0 - useful when reformatting shouldn't count as a content difference.
Ignore line-ending differences between Unix and Windows files
diff crlf-a.txt crlf-b.txt
Without any flag, a file with \r\n line endings looks completely different from the same content with plain \n endings, even though every visible character matches - see the tr page for converting between them.
Show output
1,2c1,2
< line1
< line2
---
> line1
> line2
The fix: strip trailing carriage returns before comparing
diff --strip-trailing-cr crlf-a.txt crlf-b.txt
Confirms the two files are identical in content once the CRLF-vs-LF line ending difference is discounted.
Comparing directories
Recursively diff two directory trees
diff -r confA confB
Compares every file present in both directories, and separately reports any file that exists in only one of them.
Show output
diff -r confA/nginx.conf confB/nginx.conf
3c3,4
< server_name example.com;
---
> listen [::]:80;
> server_name example.com www.example.com;
8a10,13
> }
>
> location /api/ {
> proxy_pass http://127.0.0.1:8080;
Only in confA: onlyA.conf
Only in confB: onlyB.conf
Recursively diff, but only list which files differ
diff -rq confA confB
Combines -r with -q - a fast overview of an entire tree without the line-by-line detail for every changed file.
Show output
Files confA/nginx.conf and confB/nginx.conf differ
Only in confA: onlyA.conf
Only in confB: onlyB.conf
A file missing from one side is normally an error, not a diff
diff confA/onlyA.conf confB/onlyA.conf
Same as the single-missing-file case earlier: exit 2, an error, because confB/onlyA.conf genuinely doesn't exist to compare against.
Show output
diff: confB/onlyA.conf: No such file or directory
Treat a missing file as empty instead of erroring
diff -N confA/onlyA.conf confB/onlyA.conf
-N (--new-file) changes a missing file from an error into 'compare against nothing' - every line in the existing file shows as added or removed, depending on which side it's missing from.
Show output
1d0
< only in A
Generating and applying a patch
diff -u's output isn't just for reading - it's a format patch understands directly.
Save a unified diff to a patch file
diff -u nginx-old.conf nginx-new.conf > changes.patch
Redirecting -u output to a file produces exactly the .patch/.diff format used everywhere - email patches, git diff output, code review tools.
Apply that patch to reproduce the change elsewhere
patch test-apply.conf < changes.patch
Given a copy of the original file and the patch, patch reconstructs the new version - this is literally how the patch was generated: diff old new produces the patch, and patch old < patchfile reconstructs new from it.
Show output
patching file test-apply.conf
Confirm the patched file now matches the target exactly
diff test-apply.conf nginx-new.conf
No output, exit 0 - the patch applied cleanly and the result is byte-for-byte the intended new version.