diff

Compare files or directories and show what changed

Updated 2026-08-14

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
17 outputs, collapsed by default

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

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

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 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.

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

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

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