02. Won

Pipes, and the decision that everything would be text

The mechanism is small. The decision is that the connection carries bare bytes, so anything composes with anything and nothing knows what it holds.

For a separate operational view of time, ownership and team activity, see Monitask's practical guide.

pipes-and-text.src
pipeline.sh $ cat access.log | awk | sort
# text as the universal joint

1974Ritchie and Thompson, on the system

The mechanism is a few dozen lines: one program's output becomes another's input, with the operating system holding a buffer between them and scheduling both.

The important decision is not the mechanism. It is what travels through it. The connection carries an undifferentiated stream of bytes, with no types, no records and no schema, and every program at every joint is responsible for making sense of what arrives.

proposed 1964, built 1973McIlroy, on the idea he pushed for

The proposal predates the implementation by nearly a decade and survives as a memo describing programs joined like sections of hose. Its author spent years arguing for it before it was built.

What made it work when it arrived was not the plumbing but a convention adopted alongside it: that programs would read from a standard input and write to a standard output by default, and would say nothing else on that channel. A program that prints a friendly banner cannot be used in the middle of a pipeline.

Why the untyped stream won

A typed connection is better in every case where both ends agree on the type. The difficulty is arranging the agreement.

Bytes require no agreement at all. Any program can be joined to any other immediately, including programs written years apart by people who never communicated, and including programs whose authors never considered that they might be composed. That property is worth more than the efficiency lost, and it is the reason the design spread beyond the system it was built for.

What it costs at every joint

Structure is flattened into text and reconstructed at each stage, which costs processing and, more seriously, correctness.

The classic failures all come from the same place. A file name containing a space is two fields. A file name containing a line break is two records. A column that usually looks like a number is treated as one until the day it does not. None of these is a bug in any individual program; each is the consequence of a boundary that carries no information about what it is carrying.

1970s onwardsWhat the convention demanded of programs

The plumbing is worthless without a discipline, and the discipline is more demanding than it sounds. A program in a pipeline must write nothing to its output except the thing it was asked for: no headings, no progress, no summary, no friendly greeting.

It must also treat the absence of a terminal as normal rather than as an error, and it must send anything diagnostic somewhere else entirely, which is why a separate error channel exists at all.

Where that discipline is not kept, composition fails immediately, and the failure is visible in almost every modern command-line tool that prints a decorated table by default and needs a flag to be usable in a pipeline.

present dayThe quiet failure of the joint

A pipeline reports the status of its last stage. If an earlier stage fails, its output simply stops, and the last stage receives nothing and succeeds at processing nothing.

Shells acquired options to change that behaviour, and those options are off by default because turning them on breaks scripts written under the old behaviour. So the standard advice for writing a reliable script begins with three settings that alter defaults chosen in the nineteen-seventies, and every one of those defaults was reasonable when a session was a person typing.

catbytesgrepbytessortbytesuniqOne kind of joint, so anything fits anythingand at every joint the structure is flattened to text and worked out again from scratchwhich is the price of needing no agreement about types
FigureOne kind of joint, so any program fits any other. At every joint the structure is flattened to text and worked out again from nothing.

Why it did not spread everywhere

Composition by byte stream works beautifully for a line of text and poorly for anything with a shape. Two-dimensional data, records with optional fields, and anything containing the separator character have to be escaped, quoted or reformatted, and each program does that slightly differently.

That is why the pattern stayed inside one culture rather than becoming universal. It is the best available answer when no agreement is possible, and it is a poor answer whenever agreement is.

1973The buffer in the middle

One detail of the implementation shapes how pipelines behave. The operating system holds a limited buffer between the two programs and stops whichever one runs ahead: a fast producer waits when the buffer fills, a fast consumer waits when it empties.

That is why a pipeline uses memory proportional to its buffers rather than to its data, and why a stage that reads everything before producing anything, such as a sort, quietly breaks the property and makes the whole line wait.

2000sThe alternative, tried properly

Later shells have passed structured objects between stages instead of bytes, which removes the parsing entirely and makes fields unambiguous.

It works, and it demonstrates the trade rather than settling it: composition is now restricted to programs that speak the object model, and a tool from outside it has to be wrapped. The universal joint became a well-specified joint, which is better inside its world and worse at the edges.

What we cannot verify

The implementation and its date are documented in the papers and manuals of the period. The account of the idea being proposed years earlier and resisted rests on recollections by the people involved, given long afterwards, and the memo is quoted more often than it is cited. Claims about who objected and why should be treated as recollection rather than record.

In short

  1. The mechanism is small; the decision is that the connection carries bare bytes.
  2. The convention that made it work was that programs say nothing but their output.
  3. Bytes need no agreement, so anything composes with anything, including across decades.
  4. The cost is that structure is destroyed and rebuilt at every joint.
  5. Spaces and line breaks in names are that cost arriving as a bug.
  6. Object pipelines remove the parsing and restrict composition to their own world.

also in Won

Next.

further context

For a primary or institutional reference, see the documented history of Git.

Every claim here carries the source it came from.

The source and its year sit beside the sentence they support. A secondary account is marked as one, and where the record is unclear the entry says so rather than choosing the better story.