Compare Files Safely with diff on Linux
Two files look the same until diff tells you exactly which line changed and how. This guide gets you a reliable way to compare two files or directory trees, read the useful parts of the output, and use the exit status in scripts. The examples use GNU diffutils 3.10, installed here as diff at /usr/bin/diff.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes. You need a shell and two files you are allowed to read. The ordinary comparisons below do not need sudo. This guide does not change either input file, but be careful with shell redirection: writing diff output to an existing file can truncate it.
1. Check the installed command
Confirm the binary and version before relying on an option in a script. This is read-only:
$ command -v diff
/usr/bin/diff
$ diff --version
diff (GNU diffutils) 3.10
The local manual describes the normal output as the default. It accepts two files, two directories, or one file and one directory. A single hyphen means standard input, useful for a generated or piped document.
Checkpoint
If command -v diff returns a different path, use that installation's version and manual when writing portable automation.
2. Read a normal file comparison
Create two temporary example files, or substitute paths to your own files. The command reads both and prints the changes:
$ diff /path/to/old.txt /path/to/new.txt
2c2,3
< line two
---
> line changed
> new line
In this form, 2c2,3 says line 2 in the first file should be changed to lines 2 through 3 in the second. A line beginning with < came from the first operand; a line beginning with > came from the second. The three hyphens separate the two sides.
Do not treat a non-zero result as an automatic failure yet. diff returns 0 for identical inputs, 1 when they differ, and 2 when it cannot complete the comparison at all, because of trouble such as a missing file or an access error.
3. Use unified output for reviews and patches
Unified output is usually easier to review because it puts the old and new lines into one context. Add -u, or use --unified:
$ diff -u /path/to/old.txt /path/to/new.txt
--- /path/to/old.txt 2026-09-23 02:37:42.846885271 +0100
+++ /path/to/new.txt 2026-09-23 02:37:42.846885271 +0100
@@ -1,2 +1,3 @@
same
-line two
+line changed
+new line
The timestamps come from the files and will differ on your machine. Lines beginning with a space are context, a minus line was removed from the first file, and a plus line was added in the second. The header identifies the operand order, so keep the old file first when the output will be reviewed or passed to another tool.
Three context lines are the default. Use -U 0 for no context or -U 5 for five lines when that makes a review clearer. More context can make a small change easier to locate, but it also makes output longer.
4. Decide whether differences matter
If you only need a yes-or-no answer, use -q or --brief:
$ diff -q /path/to/old.txt /path/to/new.txt
Files /path/to/old.txt and /path/to/new.txt differ
$ printf 'exit status: %s\n' "$?"
exit status: 1
For an identical pair, -q prints nothing and exits 0. Use -s or --report-identical-files when you want confirmation in a human-facing check:
$ diff -s /path/to/old.txt /path/to/old.txt
Files /path/to/old.txt and /path/to/old.txt are identical
In a shell script, handle all three states explicitly:
if diff -q "$left" "$right" >/dev/null; then
printf '%s\n' 'same'
elif [ "$?" -eq 1 ]; then
printf '%s\n' 'different'
else
printf '%s\n' 'comparison failed' >&2
exit 2
fi
The second test works because the status from the immediately preceding diff is still available. Do not replace it with a generic test for any non-zero status if an operational error must be distinguished from an expected difference.
5. Compare directories without drowning in output
Pass two directory paths and add -r or --recursive:
$ diff -ru /path/to/old-tree /path/to/new-tree
diff -ru /path/to/old-tree/config.ini /path/to/new-tree/config.ini
--- /path/to/old-tree/config.ini 2026-09-23 02:37:42.853885189 +0100
+++ /path/to/new-tree/config.ini 2026-09-23 02:37:42.854885177 +0100
@@ -1,2 +1,3 @@
mode=quiet
-retries=2
+retries=3
+timeout=30
The -u and -r options are independent: -r walks matching subdirectories, while -u selects the display format. Add -q instead of -u when you only need to know whether the trees differ.
Directory comparisons also report files present on one side only. Review those names before using the result to remove or synchronise anything: diff itself does not copy, delete or merge the files.
6. Filter harmless formatting noise deliberately
Text files can differ in presentation rather than meaning. Choose the narrowest rule that matches your goal:
- -i ignores case differences.
- -b ignores changes in the amount of whitespace.
- -w ignores all whitespace.
- -B ignores changes where lines are blank.
- -Z ignores whitespace at the end of a line.
- -E ignores changes caused by tab expansion.
These options can hide a real change. For example, -i makes Admin and admin compare equal, even when a case-sensitive program treats them as different. Start with plain diff -u, then add one justified filter and record it in the review or script.
For files that are not plain text, use -a or --text only when treating the bytes as text is appropriate. Do not infer that two binary files are equal from a short display; use the exit status, and consider cmp when a byte-for-byte comparison is the actual requirement.
7. Avoid accidental data loss
Warning
Diff is read-only, but this command is not safe when the output path is one of its inputs:
$ diff -u old.txt new.txt > old.txt
The shell opens and truncates old.txt before diff even reads it. That damage is not reversible through diff. Stop before running a redirection if the destination could be an input. Write to a new path, inspect it, and replace the old file only after a separate backup and review:
$ diff -u old.txt new.txt > comparison.patch
$ less comparison.patch
$ cp --preserve=all old.txt old.txt.bak
$ mv comparison.patch reviewed.patch
The example leaves both original inputs untouched. If you later apply a patch, treat that as a separate, state-changing operation and keep the backup until the result has been tested. No command here requires elevated privileges; use sudo only when file permissions genuinely require it, and keep the comparison itself unprivileged where possible.
Done means
- Confirmed the version as GNU diffutils 3.10, or identified the one you are actually using.
- Can read both output forms, normal and unified, including the operand order.
- Script distinguishes all three states: identical, different and failed.
- Used recursive comparison for directory trees and filtered whitespace only for a stated reason.
- Never redirected diff output over an input.