Four rules from 1978
Doug McIlroy summarized Unix design in four lines that still hold:
- Make each program do one thing well.
- Expect the output of every program to become the input to another, as yet unknown, program.
- Design and build software, even operating systems, to be tried early, ideally within weeks.
- Use tools in preference to unskilled help to lighten a programming task, even if you have to detour to build the tools and expect to throw some of them out after you've finished using them.
Why this still matters
Modern equivalents of these rules: microservices (1), JSON pipelines (2), MVPs and continuous deployment (3), build scripts and CI tooling (4). Every dev practice you do today is descended from these four bullet points.
Concrete habits
- Plain text everywhere. Logs, configs, data — text first, binary only when forced. Text travels through pipes; binary doesn't.
- One command, one job. If your shell function is 300 lines long, you've built a tool that doesn't compose. Split it.
- Output is contract. Programs that print stable, well-formed output (header, columns, fixed delimiters) are pipeable. Pretty-printing for humans goes through a separate flag.
- Errors to stderr, data to stdout. Always.
The exception: jq + structured data
The line-oriented pipe model breaks for nested JSON. Modern Unix accepts this — jq, yq, and dasel are first-class citizens now, exactly because they let JSON streams compose. The philosophy didn't change, the data shape did.
A small tool should have a small responsibility
Clear inputs and outputs make stages replaceable and testable, but every new process and parsing boundary also costs something. Split work into units whose data and failures you can explain, not into the largest possible number of commands.
Verify the text-stream assumption
Classic pipelines suit line-oriented text. JSON, binary data, and filenames containing NUL or newlines need other record boundaries. Converting everything to spaces and lines can discard information.