Memra

A difference is a program, not a report

◈ 10 cards

Read 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,L3append file 2's lines L2 through L3 after file 1's line L1.
  • L1,L2 c L3,L4change file 1's lines L1-L2 into file 2's lines L3-L4.
  • L1,L2 d L3delete 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.

FormWhat it instructsLines printed underneath4a5,6append file 2 lines 5-6after file 1 line 4> lines, from file 23c3change file 1 line 3 intofile 2 line 3< lines, then ---, then >lines2,3d1delete file 1 lines 2-3< lines, from file 1No output at all = the two files are identical.
Read the middle column as a command addressed to file 1. The right column tells you which prefix to expect underneath.
>printf >><verifydiff -e v1 v2emit the ed instructionsscript.ed3c / gamma / .append w and qthe fragment becomes a programed v1.txtapplies it, prints 26 then 24diff v1 v2no output: identical
The last stage is the proof: `diff` is silent when two files are identical, so an empty result at the end means the transformation really was applied.
NORMAL ~/memra/learn/comp-325/comparing-files-with-diff-and-wc utf-8 LF