Linux CLI 44 ๐ง comm, and diff command
comm file1.txt file2.txt
comm -1 file1.txt file2.txt
comm -2 file1.txt file2.txt
comm -3 file1.txt file2.txt
cat file1.txt | comm - file2.txt
diff file1.txt file2.txt
diff -y file1.txt file2.txt
diff -c file1.txt file2.txt
comm and diff both compare files, but they answer different questions. comm asks “which lines are shared and which are unique?” โ it’s a set operation on two sorted files. diff asks “what exactly changed, line by line?” โ it’s a detailed comparison that shows you the edits.
Key point: comm requires sorted input and gives you three simple columns. diff works on any input and produces a precise, often patch-ready description of the differences. Use comm for set logic; use diff for reviewing changes.
a – comm command
comm compares two sorted files and produces three columns of output:
| Column | Contents |
|---|---|
| 1st | Lines unique to the first file |
| 2nd | Lines unique to the second file |
| 3rd | Lines common to both files |
Syntax:
comm [options] file1 file2
Common options:
| Option | Purpose |
|---|---|
-1 | Suppress the first column |
-2 | Suppress the second column |
-3 | Suppress the third column |
- | Read from standard input |
Important: Both files must be sorted. If they aren’t, comm won’t give correct results โ just like join and uniq.
Examples:
# Two sorted files
$ cat team_a.txt
alice
bob
charlie
dave
$ cat team_b.txt
bob
charlie
eve
frank
# Default โ all three columns
$ comm team_a.txt team_b.txt
alice
eve
frank
bob
charlie
dave
# Wait โ the columns are TAB-separated, so it's clearer like this:
$ comm team_a.txt team_b.txt | cat -A
alice$
^Ieve$
^Ifrank$
bob$
charlie$
dave$
# Only lines unique to file 1
$ comm -2 -3 team_a.txt team_b.txt
alice
dave
# Only lines unique to file 2
$ comm -1 -3 team_a.txt team_b.txt
eve
frank
# Only lines common to both
$ comm -1 -2 team_a.txt team_b.txt
bob
charlie
# Suppress just the first column
$ comm -1 team_a.txt team_b.txt
eve
frank
bob
charlie
# Suppress just the second column
$ comm -2 team_a.txt team_b.txt
alice
bob
charlie
dave
# Suppress just the third column
$ comm -3 team_a.txt team_b.txt
alice
eve
frank
dave
# Read one file from stdin
$ cat team_a.txt | comm - team_b.txt
alice
eve
frank
bob
charlie
dave
Reading the output โ the tabs are the columns:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ comm team_a.txt team_b.txt โ
โ โ
โ Column 1 Column 2 Column 3 โ
โ (file 1) (file 2) (both) โ
โ โ
โ alice โ
โ eve โ
โ frank โ
โ bob โ
โ charlie โ
โ dave โ
โ โ
โ Wait โ that's not right. Let me re-check: โ
โ โ
โ alice (unique to file 1) โ
โ eve (unique to file 2) โ
โ frank (unique to file 2) โ
โ bob (common) โ
โ charlie (common) โ
โ dave (unique to file 1) โ
โ โ
โ Actually comm sorts all lines together and โ
โ uses TABs to indent. The columns are: โ
โ โ
โ alice โ col 1 โ
โ eve โ col 2 โ
โ frank โ col 2 โ
โ bob โ col 3 (0 tabs = col 3) โ
โ charlie โ col 3 โ
โ dave โ col 1 โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Let me clarify precisely. comm outputs lines in sorted order. Each line is prefixed with tabs:
- 0 tabs โ line is in the third column (common to both)
- 1 tab โ line is in the second column (unique to file 2)
- 2 tabs โ line is in the first column (unique to file 1)
So for our example:
alice โ 2 tabs (col 1)
eve โ 1 tab (col 2)
frank โ 1 tab (col 2)
bob โ 0 tabs (col 3)
charlie โ 0 tabs (col 3)
dave โ 2 tabs (col 1)
That’s why comm -1 -2 (suppress col 1 and col 2) leaves only common lines:
$ comm -1 -2 team_a.txt team_b.txt
bob
charlie
Practical recipes:
# Lines only in file 1
$ comm -2 -3 file1.txt file2.txt
# Lines only in file 2
$ comm -1 -3 file1.txt file2.txt
# Lines in both
$ comm -1 -2 file1.txt file2.txt
# Lines in either but not both (symmetric difference)
$ comm -3 file1.txt file2.txt | sed 's/^\t//'
# Count unique lines in each file
$ comm -2 -3 file1.txt file2.txt | wc -l
$ comm -1 -3 file1.txt file2.txt | wc -l
# Count common lines
$ comm -1 -2 file1.txt file2.txt | wc -l
Tip: Because
commrequires sorted input, the standard idiom iscomm <(sort a.txt) <(sort b.txt)โ process substitution sorts on the fly.
$ comm <(sort unsorted_a.txt) <(sort unsorted_b.txt)
b – diff command
diff compares files line by line and shows the differences. It’s useful for comparing small and large files, scripts, configurations, and anything else where you need to know exactly what changed.
Syntax:
diff [options] file1 file2
Common options:
| Option | Purpose |
|---|---|
-u | Unified format โ before/after blocks |
-y | Side-by-side column format |
-c | Context format |
-r | Compare directories recursively |
-i | Ignore case |
-w | Ignore whitespace |
-b | Ignore changes in whitespace amount |
-q | Brief โ just report if files differ |
-s | Report when files are identical |
Reading the default output:
| Symbol | Meaning |
|---|---|
< | Line from the first file |
> | Line from the second file |
a | Add โ lines added to file 1 to get file 2 |
b | Change โ lines changed |
c | Delete โ lines deleted |
| Numbers before the letter | Line numbers in file 1 |
| Numbers after the letter | Line numbers in file 2 |
Examples:
# Two files that differ
$ cat config_old.txt
host=localhost
port=8080
debug=false
timeout=30
$ cat config_new.txt
host=localhost
port=9090
debug=true
timeout=30
retries=3
# Default diff
$ diff config_old.txt config_new.txt
2c2
< port=8080
---
> port=9090
3c3
< debug=false
---
> debug=true
5a6
> retries=3
Reading it line by line:
2c2โ line 2 in file 1 changed to line 2 in file 2< port=8080โ what file 1 had> port=9090โ what file 2 has3c3โ line 3 changed5a6โ after line 5 of file 1, add line 6 from file 2 (retries=3)
More examples:
# Brief output โ just say if they differ
$ diff -q config_old.txt config_new.txt
Files config_old.txt and config_new.txt differ
# Report when identical
$ diff -s config_old.txt config_old.txt
Files config_old.txt and config_old.txt are identical
# Ignore case
$ diff -i file1.txt file2.txt
# Ignore whitespace
$ diff -w file1.txt file2.txt
# Unified format (the one used by patches)
$ diff -u config_old.txt config_new.txt
--- config_old.txt 2024-01-15 10:00:00.000000000 +0000
+++ config_new.txt 2024-01-15 10:05:00.000000000 +0000
@@ -1,5 +1,6 @@
host=localhost
-port=8080
+port=9090
-debug=false
+debug=true
timeout=30
+retries=3
# Compare directories
$ diff -r dir1/ dir2/
diff -r dir1/file1.txt dir2/file1.txt
2c2
< old content
---
> new content
# Save a patch
$ diff -u config_old.txt config_new.txt > config.patch
$ patch config_old.txt < config.patch
Tip:
-u(unified) is the format used bygit diff,patch, and most modern tools. Learn to read it โ it’s the most useful.
c – diff command column and context mode
Two alternative output formats make diff easier to read when you want a quick visual scan.
Side-by-side mode (-y):
$ diff -y config_old.txt config_new.txt
host=localhost host=localhost
port=8080 | port=9090
debug=false | debug=true
timeout=30 timeout=30
> retries=3
| Symbol | Meaning |
|---|---|
< / > | Content from file 1 / file 2 |
| | Line needs to be changed |
( | Line only in file 1 (with --suppress-common-lines) |
) | Line only in file 2 |
| (nothing) | Lines are identical |
Useful side-by-side options:
| Option | Purpose |
|---|---|
--suppress-common-lines | Show only differences |
-W N | Set output width to N columns |
-t | Expand tabs to spaces |
# Only show the differences, side by side
$ diff -y --suppress-common-lines config_old.txt config_new.txt
port=8080 | port=9090
debug=false | debug=true
> retries=3
Context mode (-c):
$ diff -c config_old.txt config_new.txt
*** config_old.txt 2024-01-15 10:00:00.000000000 +0000
--- config_new.txt 2024-01-15 10:05:00.000000000 +0000
***************
*** 1,5 ****
host=localhost
! port=8080
! debug=false
timeout=30
--- 1,6 ----
host=localhost
! port=9090
! debug=true
timeout=30
+ retries=3
| Symbol | Meaning |
|---|---|
*** | The first file |
--- | The second file |
- | Line to be deleted from file 1 |
+ | Line to be added to file 1 |
! | Line to be changed |
| (space) | Unchanged context line |
Side-by-side vs context vs unified:
| Format | Flag | Best for |
|---|---|---|
| Default | (none) | Scripting, minimal output |
| Unified | -u | Patches, git diff style |
| Context | -c | Older patch format, context |
| Side-by-side | -y | Quick visual scan |
Complete Example Session
# ============================================
# PART 1: COMM โ THREE COLUMNS
# ============================================
$ cat file1.txt
apple
banana
cherry
date
$ cat file2.txt
banana
cherry
elderberry
fig
$ comm file1.txt file2.txt
apple
elderberry
fig
banana
cherry
date
# ============================================
# PART 2: COMM โ SUPPRESS COLUMNS
# ============================================
# Only unique to file 1
$ comm -2 -3 file1.txt file2.txt
apple
date
# Only unique to file 2
$ comm -1 -3 file1.txt file2.txt
elderberry
fig
# Only common
$ comm -1 -2 file1.txt file2.txt
banana
cherry
# Symmetric difference (either but not both)
$ comm -3 file1.txt file2.txt | sed 's/^\t//'
apple
elderberry
fig
date
# ============================================
# PART 3: COMM WITH PROCESS SUBSTITUTION
# ============================================
$ comm <(sort unsorted1.txt) <(sort unsorted2.txt)
...
# ============================================
# PART 4: DIFF โ DEFAULT
# ============================================
$ cat config_old.txt
host=localhost
port=8080
debug=false
timeout=30
$ cat config_new.txt
host=localhost
port=9090
debug=true
timeout=30
retries=3
$ diff config_old.txt config_new.txt
2c2
< port=8080
---
> port=9090
3c3
< debug=false
---
> debug=true
5a6
> retries=3
# ============================================
# PART 5: DIFF โ UNIFIED
# ============================================
$ diff -u config_old.txt config_new.txt
--- config_old.txt 2024-01-15 10:00:00.000000000 +0000
+++ config_new.txt 2024-01-15 10:05:00.000000000 +0000
@@ -1,5 +1,6 @@
host=localhost
-port=8080
+port=9090
-debug=false
+debug=true
timeout=30
+retries=3
# ============================================
# PART 6: DIFF โ SIDE BY SIDE
# ============================================
$ diff -y config_old.txt config_new.txt
host=localhost host=localhost
port=8080 | port=9090
debug=false | debug=true
timeout=30 timeout=30
> retries=3
$ diff -y --suppress-common-lines config_old.txt config_new.txt
port=8080 | port=9090
debug=false | debug=true
> retries=3
# ============================================
# PART 7: DIFF โ CONTEXT
# ============================================
$ diff -c config_old.txt config_new.txt
*** config_old.txt 2024-01-15 10:00:00.000000000 +0000
--- config_new.txt 2024-01-15 10:05:00.000000000 +0000
***************
*** 1,5 ****
host=localhost
! port=8080
! debug=false
timeout=30
--- 1,6 ----
host=localhost
! port=9090
! debug=true
timeout=30
+ retries=3
# ============================================
# PART 8: DIFF โ PATCH WORKFLOW
# ============================================
$ diff -u config_old.txt config_new.txt > config.patch
$ cat config.patch
--- config_old.txt ...
+++ config_new.txt ...
@@ -1,5 +1,6 @@
...
$ patch config_old.txt < config.patch
patching file config_old.txt
$ diff config_old.txt config_new.txt
# (no output โ they now match)
# ============================================
# PART 9: DIFF โ DIRECTORIES
# ============================================
$ diff -r dir1/ dir2/
diff -r dir1/file1.txt dir2/file1.txt
2c2
< old content
---
> new content
Only in dir1: extra.txt
Only in dir2: newfile.txt
# ============================================
# PART 10: COMBINING COMM AND DIFF
# ============================================
# Lines added in new version
$ comm -1 -3 <(sort old.txt) <(sort new.txt)
# Lines removed
$ comm -2 -3 <(sort old.txt) <(sort new.txt)
# Detailed changes
$ diff -u old.txt new.txt
Quick Reference
comm
| Command | Purpose |
|---|---|
comm A B | All three columns |
comm -1 A B | Suppress unique to A |
comm -2 A B | Suppress unique to B |
comm -3 A B | Suppress common |
comm -1 -2 A B | Only common |
comm -2 -3 A B | Only unique to A |
comm -1 -3 A B | Only unique to B |
comm - A B | Read A from stdin |
comm <(sort a) <(sort b) | Sort on the fly |
diff โ Output Modes
| Flag | Format |
|---|---|
| (none) | Default โ minimal |
-u | Unified |
-c | Context |
-y | Side by side |
-q | Brief |
-s | Report identical |
diff โ Comparison Options
| Flag | Purpose |
|---|---|
-i | Ignore case |
-w | Ignore whitespace |
-b | Ignore whitespace changes |
-B | Ignore blank lines |
-r | Recursive (directories) |
-N | Treat missing files as empty |
diff โ Symbols
| Symbol | Meaning |
|---|---|
< | From file 1 |
> | From file 2 |
| | Changed (side-by-side) |
a | Add |
b | Change |
c | Delete |
*** | File 1 (context mode) |
--- | File 2 (context mode) |
! | Changed (context mode) |
+ | Added (context/unified) |
- | Deleted (context/unified) |
comm vs diff
| Aspect | comm | diff |
|---|---|---|
| Input | Sorted files | Any files |
| Output | 3 columns | Line-by-line changes |
| Purpose | Set operations | Change review |
| Patch-ready | No | Yes (-u) |
| Best for | “What’s shared?” | “What changed?” |
Best Practices
โ Do This:
# Sort before comm
comm <(sort a.txt) <(sort b.txt) # โ
# Use -1 -2 for "common only"
comm -1 -2 a.txt b.txt # โ
# Use -2 -3 for "only in file 1"
comm -2 -3 a.txt b.txt # โ
# Use -u for patch-friendly diff
diff -u old.txt new.txt > change.patch # โ
# Use -y for visual scans
diff -y --suppress-common-lines a b # โ
# Use -r for directories
diff -r dir1/ dir2/ # โ
# Ignore whitespace when comparing code
diff -w old.c new.c # โ
# Verify with -q first
diff -q old.txt new.txt # โ
โ Don’t Do This:
# Don't comm unsorted files
comm unsorted1.txt unsorted2.txt # โ wrong output
# Don't diff binary files without care
diff image1.png image2.png # โ "Binary files differ"
# Don't forget -r for directories
diff dir1/ dir2/ # โ "Is a directory"
# Don't use default diff for patches
diff old.txt new.txt > patch # โ use -u
# Don't confuse -1/-2/-3 order in comm
comm -3 a b # โ ๏ธ suppresses common
# Don't ignore whitespace issues blindly
diff -w code1.py code2.py # โ ๏ธ may hide real bugs
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
comm on unsorted files | Wrong output | sort first |
| Tabs invisible | Columns look wrong | Pipe through cat -A |
Forgot -u for patches | Patch rejects | Use diff -u |
| Binary files | “Binary files differ” | Use cmp instead |
| Diff output confusing | Lost in noise | Use -y or -u |
| Whitespace differences | False positives | Use -w or -b |
| Recursive diff missing | “Is a directory” | Add -r |
| Missing files in dir diff | No output | Add -N |
Real-World Examples
1. Find Common Lines Between Two Lists
$ comm -1 -2 <(sort list1.txt) <(sort list2.txt)
banana
cherry
2. Find Lines Only in the New Version
$ comm -1 -3 <(sort old.txt) <(sort new.txt)
new-feature
3. Find Lines Only in the Old Version
$ comm -2 -3 <(sort old.txt) <(sort new.txt)
deprecated-function
4. Symmetric Difference
$ comm -3 <(sort a.txt) <(sort b.txt) | sed 's/^\t//'
apple
elderberry
fig
date
5. Review a Config Change
$ diff -u /etc/ssh/sshd_config.old /etc/ssh/sshd_config
--- /etc/ssh/sshd_config.old ...
+++ /etc/ssh/sshd_config ...
@@ -12,7 +12,7 @@
-PermitRootLogin yes
+PermitRootLogin no
6. Side-by-Side Code Review
$ diff -y --suppress-common-lines old.py new.py
def foo(): | def foo(x):
return 1 | return x + 1
7. Create a Patch File
$ diff -u old.c new.c > fix.patch
$ patch old.c < fix.patch
patching file old.c
8. Compare Two Directories
$ diff -r project_v1/ project_v2/
diff -r project_v1/main.c project_v2/main.c
5c5
< printf("v1");
---
> printf("v2");
Only in project_v2: newfile.c
9. Ignore Whitespace in Code
$ diff -w old.py new.py
# Only reports real changes, not reindentation
10. Quick “Are They Different?”
$ diff -q a.txt b.txt
Files a.txt and b.txt differ
$ echo $?
1
11. Build a Combined Report
# Show only-in-old, common, only-in-new in a clear format
$ echo "=== Only in old ==="
$ comm -2 -3 <(sort old.txt) <(sort new.txt)
$ echo "=== Common ==="
$ comm -1 -2 <(sort old.txt) <(sort new.txt)
$ echo "=== Only in new ==="
$ comm -1 -3 <(sort old.txt) <(sort new.txt)
12. Compare Two CSV Files
$ diff -y --suppress-common-lines old.csv new.csv
id,name | id,name,email
1,alice | 1,alice,alice@x.com
13. Patch a File Safely
$ cp config.txt config.txt.bak
$ diff -u config.txt.bak config.txt.new > update.patch
$ patch config.txt < update.patch
$ diff config.txt config.txt.new
# (no output = success)
14. Compare File Lists
$ comm -3 <(ls dir1/ | sort) <(ls dir2/ | sort) | sed 's/^\t//'
extra-in-dir1
new-in-dir2
15. Find Duplicate Content Across Files
$ diff -q file1.txt file2.txt && echo "identical"
identical
Visual: comm vs diff
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ comm โ
โ โ
โ File 1: File 2: Output: โ
โ apple banana apple โ
โ banana cherry โ unique to 1 โ
โ cherry elderberry elderberry โ
โ date fig fig โ
โ banana โ
โ cherry โ
โ date โ
โ โ
โ Set logic on sorted files โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ diff โ
โ โ
โ File 1: File 2: Output: โ
โ host=x host=x 2c2 โ
โ port=8080 port=9090 < port=8080 โ
โ debug=false debug=true --- โ
โ timeout=30 timeout=30 > port=9090 โ
โ ... retries=3 3c3 โ
โ < debug=false โ
โ --- โ
โ > debug=true โ
โ 5a6 โ
โ > retries=3 โ
โ โ
โ Line-by-line change review โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Command | Purpose | Example |
|---|---|---|
comm A B | Three columns | comm a.txt b.txt |
comm -1 A B | Suppress unique to A | comm -1 a.txt b.txt |
comm -2 A B | Suppress unique to B | comm -2 a.txt b.txt |
comm -3 A B | Suppress common | comm -3 a.txt b.txt |
comm -1 -2 A B | Only common | comm -1 -2 a.txt b.txt |
comm -2 -3 A B | Only unique to A | comm -2 -3 a.txt b.txt |
comm -1 -3 A B | Only unique to B | comm -1 -3 a.txt b.txt |
comm - A B | Read A from stdin | cat a | comm - b |
diff A B | Default diff | diff old.txt new.txt |
diff -u A B | Unified | diff -u old.txt new.txt |
diff -c A B | Context | diff -c old.txt new.txt |
diff -y A B | Side by side | diff -y old.txt new.txt |
diff -y --suppress-common-lines A B | Only differences | diff -y --suppress-common-lines a b |
diff -q A B | Brief | diff -q old.txt new.txt |
diff -r D1 D2 | Recursive dirs | diff -r dir1/ dir2/ |
diff -i A B | Ignore case | diff -i a.txt b.txt |
diff -w A B | Ignore whitespace | diff -w a.c b.c |
diff -u A B > p | Create patch | diff -u a b > p.patch |
Key takeaways:
commcompares two sorted files and reports three sets: only in A, only in B, and commoncommuses tabs to indent columns โcat -Areveals them- Use
-1,-2,-3to suppress columns and isolate the set you want commrequires sorted input โ use process substitution<(sort file)when neededdiffcompares files line by line and shows exactly what changed- The default format uses
<,>,a,b,cโ add/change/delete - Use
-u(unified) for the modern patch-friendly format - Use
-y(side by side) for a quick visual scan - Use
-c(context) for the older patch format with surrounding lines |in side-by-side means “changed”,</>mean unique to file 1/2- In context mode:
***= file 1,---= file 2,!= changed,+= added,-= deleted diff -u > patch+patchis the classic workflow for applying changes- Use
-rfor directories,-w/-ito ignore noise,-qfor a quick answer
Remember: comm is for set operations on sorted files โ which lines are shared, which are unique. diff is for change review โ what exactly changed line by line. Use comm when you want a Venn diagram; use diff when you want an edit script. Always sort before comm. Always reach for -u when you’re creating a patch. And learn to read the default diff output โ it’s the most portable format and the one every Unix system understands.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!