A difference is a program, not a report
◈ 10 cardsRead default diff output as an ed script that transforms file 1 into file 2, read the unified format everyone actually uses, run the diff-to-ed round trip, and count with wc.
Two files, one difference
Two four-line files, v1.txt and v2.txt, differing in exactly one line:
v1.txt v2.txt
alpha alpha
bravo bravo
charlie gamma
delta delta
Run diff and read what comes back very carefully:
$ diff v1.txt v2.txt
3c3
< charlie
---
> gamma
Most people read that as a report: line 3 differs. It is not a report. It is an instruction, and the instruction is addressed to the first file: change line 3 into line 3 of the second file. Lines prefixed < come from file 1, lines prefixed > come from file 2, and --- separates the two halves of a change. The direction is never symmetric — diff v2.txt v1.txt would print 3c3, < gamma, ---, > charlie, a different instruction with the same facts in it.
There are exactly three instruction forms:
L1 a L2,L3— append file 2's lines L2 through L3 after file 1's line L1.L1,L2 c L3,L4— change file 1's lines L1-L2 into file 2's lines L3-L4.L1,L2 d L3— delete file 1's lines L1-L2. (The trailing number is where you would be in file 2 afterwards.)
And one non-form that matters more than any of them: no output at all means the files are identical. diff is silent on success. That silence is the whole reason the round trip below is a proof and not a hope.
The round trip: turn the difference into a program and run it
diff -e emits the same three instructions in the dialect of ed, the original UNIX line editor:
$ diff -e v1.txt v2.txt
3c
gamma
.
That is a fragment, not a program — it changes the buffer but never saves it. Two more ed commands finish the job: w to write and q to quit. So capture the fragment, append them, and run it:
$ diff -e v1.txt v2.txt > script.ed
$ printf 'w\nq\n' >> script.ed
$ cat script.ed
3c
gamma
.
w
q
$ ed v1.txt < script.ed
26
24
ed prints the byte count when it opens the file (26 bytes) and again when it writes it (24 bytes, because gamma is two characters shorter than charlie). Those two numbers are its entire output — ed is famously terse. Now prove it worked:
$ diff v1.txt v2.txt
$
Nothing. The files are identical, and you built the transformation out of four small pieces: a comparison, an editor, and two uses of redirection — > file sends a command's standard output into a file instead of the screen (>> appends rather than replacing), and < file feeds a file into a command's standard input instead of the keyboard. That is the minimum you need to read the round trip above; module 7 develops redirection and pipes properly, descriptor by descriptor. That composability is the UNIX argument, made in four lines.
(The textbook appends w and q interactively with cat >> script.ed, typing the two lines and ending with <Ctrl+D>. printf does the same thing without the interaction, which is what you want inside a script.)
The format people actually read
The ed-script default is a historical artefact. Every tool you will meet — git, code review, patch files, bug reports — uses the unified format, diff -u:
$ diff -u v1.txt v2.txt
--- v1.txt 2026-09-03 09:14:02
+++ v2.txt 2026-09-03 09:14:02
@@ -1,4 +1,4 @@
alpha
bravo
-charlie
+gamma
delta
Read it top to bottom. --- names the old file and its modification time; +++ names the new one. @@ -1,4 +1,4 @@ is a hunk header: the old file contributes 4 lines starting at line 1, and so does the new one. Then the hunk itself, where a leading space means unchanged context, - means removed from the old, and + means added in the new. Three lines of context around each change is the default, which is what makes a unified diff readable without the original files in front of you.
Note the prefix collision, because it catches everyone once: in the default format < and > mark the two files; in the unified format - and + do. Same information, different alphabet.
Counting: wc
wc reports, in this order and no other, lines, words, characters, then the filename:
$ wc v1.txt v2.txt
4 4 26 v1.txt
4 4 24 v2.txt
8 8 50 total
A line is a newline character, a word is a run of non-whitespace, a character is a byte. -l, -w and -c ask for one count each. Given several files it adds a total row, and the totals are exact sums — which gives you a prediction you can check: concatenate the two files and the combined file must have 8 lines, 8 words and 50 characters. If it does not, something ate a newline.