C:\ANCIENT\UNIX.TXT

1969

Unix: Small Pieces Need Clear Connections

Unix grew from a small 1969 system into an environment of connected tools; original models of shared file objects and text pipelines make those connections explicit.

7 min readCovers 1969 PDP-7 origins; later early-1970s developmentSoftware Before 1980

A useful tool doesn’t have to do everything. It does, however, need a usable way to receive work and hand the result onward.

Likewise, a file name can be convenient without being the file’s contents. Separating the name from the stored object allows several references to lead to the same material, with consequences that copying alone wouldn’t have.

Unix is often presented as a collection of small tools. I find the connections between those tools and objects just as interesting. They determine whether small pieces can actually cooperate.

Begin with the small system

Dennis Ritchie’s creator retrospective dates Unix’s beginning to 1969 on the PDP-7, with Ken Thompson central to implementation and contributions from Ritchie, McIlroy, Ossanna and Canaday. The early filesystem had directory names referring to stored file objects but lacked later convenient pathnames.1

Development continued. Ritchie dates pipes to 1972 in the PDP-11 period and the kernel’s C rewrite to 1973. Neither belongs in a list of features already present at the 1969 beginning.1

The linked account was presented in 1979 and is based on a later reprint. It is historical testimony, not a 1969 programming manual.

Our worked examples use invented records, object identifiers and stage notation. They explain selected relationships while avoiding a modern shell command dressed as a first-version demonstration.

A name points somewhere

Start with a deliberately tiny directory model. The directory contains two entries:

report -> object 7
backup -> object 7

Object seven contains the text Initial report. The names differ, but both resolve to the same stored object.

Read through report and obtain Initial report. Read through backup and obtain the same text. There are two names and one content object.

Now change object seven’s content through the report reference to Revised report. Reading backup also obtains Revised report, because it resolves to the object just changed.

That behavior can be useful or surprising. The word backup is merely a chosen name; it doesn’t convert a shared reference into an independent saved copy. The storage relationship supplies the behavior, regardless of the reassuring vocabulary.

Our checker verifies that both directory entries resolve to the same object identifier, then checks the changed content through both names.

Original diagram of two names referring to one stored object Original explanatory SVG, created in 2026. Chosen identifiers and reference policy; not a historical directory dump or filesystem image.

Ritchie describes directory entries and links in the early system.1 The model’s dictionaries and chosen object seven are our own representation.

Removing a name is a separate operation

Delete the report directory entry. The backup entry still leads to object seven, so its contents remain reachable in our model.

Deleting a name hasn’t automatically deleted every other route to the same object. The checker tests that backup still reads Revised report while report no longer resolves.

Contrast an original independent-copy model. Create object eight containing a copy of Initial report and point backup at eight. Change object seven, and eight still contains Initial report.

The copied result and the shared-reference result are both understandable once their contracts are stated. They shouldn’t be confused merely because both arrangements show two similar names in a directory listing.

Our model doesn’t simulate open files, reclaimed blocks, permissions or the historical kernel’s full deletion rules. It focuses on a narrower distinction: names can be separate while content identity is shared.

That distinction becomes useful whenever an interface shows labels more readily than the underlying relationships.

Three stages, one answer

For a separate original pipeline example, choose four text records:

approved:120
pending:70
approved:30
approved:5

The protocol is intentionally plain: a status, a colon and a nonnegative integer amount. There are no quoted separators, embedded newlines or fractional currency rules in this teaching input.

Stage one keeps only approved records. Stage two extracts the amount and emits it as integer text. Stage three totals the amounts, starting at zero.

The amounts arriving at the final stage are one hundred twenty, thirty and five. Their total is one hundred fifty-five.

The pipeline contains three responsibilities: selecting, translating the selected format and accumulating. Each stage can be checked with a small input/output example before checking the composed result.

A different report could use the selected amounts to calculate a count instead of a total. The count is three. It is not one hundred fifty-five, even though both computations read the same three values.

Original three-stage text pipeline with selected records and total Original explanatory SVG, created in 2026. Invented records and stage responsibilities; not a historical shell listing or command trace.

Pipes are a later part of Unix’s early development.1 This conceptual notation describes their broad composition idea without claiming to recover the first pipe syntax.

The stage contract matters

The extractor expects a selected record containing a colon and a valid amount. The total stage expects integer text. Those expectations have to agree with what their predecessors emit.

Suppose the extractor instead writes Amount: 120. That is pleasant for a human, but our chosen total parser no longer accepts it as integer text. Changing presentation changed the protocol.

The checker deliberately rejects a malformed amount and negative values under our chosen nonnegative policy. A complete application could accept negatives, decimal values or richer records; it would need to define those conventions consistently.

A stream carries data. It doesn’t automatically turn the data into structured objects or validate their business meaning. The colon is understood because our stages share a stated text convention.

Filtering before extraction also supplies a chosen policy. A malformed pending amount is discarded before the amount parser runs. A malformed approved amount reaches the parser and fails.

Our checker tests that difference explicitly. It prevents a reader from assuming that every input field was validated merely because the final total was correct for ordinary input.

Preserve order when the work needs it

The filter retains approved records in their original order. The extractor therefore produces 120, 30, 5, with partial totals 120, 150, 155.

For exact integer addition, reordering these terms would leave the final sum unchanged. The intermediate trace would differ, however, and other stages might care about sequence.

A stage that reports the first approved amount should produce 120 for this input. Sorting the amounts before that report would alter the answer. Composition alone doesn’t grant permission to rearrange records.

The checker compares both the final total and the amount sequence. It also implements the same selection and total as a direct calculation and requires the two approaches to agree.

Agreement verifies the chosen example. It isn’t proof that arbitrary stages can be exchanged without changing meaning.

A buffer has finite room

To make transport visible, our second pipeline model transfers the text of the three selected amounts through a bounded character buffer. The producer can add at most three characters per turn; the consumer can remove at most two. The buffer can hold four characters.

If the producer’s next chunk doesn’t fit, it waits for the consumer to make room. That chosen scheduling policy preserves the stream instead of overwriting unread characters.

The consumer may receive part of a record. For example, the first two characters of 120 are 12, with the remaining zero arriving later. A newline marks the record boundary in our protocol; a read boundary doesn’t necessarily coincide with it.

Our checker collects characters across reads, splits only complete newline-terminated records and reconstructs exactly 120, 30 and 5. It verifies that occupancy never exceeds four and that no characters are dropped or reordered.

This is an invented transport model, not the old Unix pipe buffer size or scheduler. It illustrates why receiving a chunk of data isn’t always the same as receiving a complete logical record.

Empty now, finished later

An empty buffer can mean that the producer hasn’t supplied the next characters yet. It can also occur after the producer has finished and the consumer has drained all remaining data.

Those cases require different behavior. Temporary emptiness calls for waiting under our policy. Completion allows the consumer to terminate.

The model keeps a separate producer-finished condition, rather than interpreting every empty buffer as the end. Its checker tests final exhaustion and reconstructs the chosen text before accepting the result.

Once again, these modern checks don’t execute a historical kernel or command. They verify the obligations we assigned to names, stage formats and a finite stream transport.

Two boundary inputs sharpen the pipeline contract further. An input with only pending records produces no selected amounts. Our chosen total still reports zero, because zero is the starting value for addition. One approved record with amount zero also totals zero, but its count is one rather than zero. Those reports have the same sum and different meanings. The checker covers both, so it can distinguish an empty result from a selected zero-valued record. A summary that prints only the sum would conceal that distinction; a caller that needs it should request the count as well.

Small pieces can cooperate

The file example separates labels from content identity. The pipeline example separates selection, representation and accumulation. The transport example separates read chunks from record boundaries.

Those distinctions are the practical work behind a system of small components. A component can stay modest because its connections make clear what it receives, owns and produces.

Unix grew through several stages of early development. Its useful lesson here is how much a simple interface depends on a precise agreement at the connection.

Footnotes

  1. Dennis M. Ritchie, “The Evolution of the Unix Time-sharing System”, presented 1979, HTML based on 1984 reprint. Origins, PDP-7 filesystem, pipes and high-level-language sections inspected. Source recollection explicitly dates pipes to 1972; no 1969 pipe claim. All worked objects, records and transport policies are original. ↩ ↩2 ↩3 ↩4

software-historyunixoperating-systemsfilesystemspipelines