The first Linux box I ever ran came on a stack of CDs in a paper sleeve — RedHat 6.2, borrowed from a friend in high school. The install took an hour and burned through two coasters. When it finally booted to a console prompt, I had no idea what to type. I learned ls. I learned cd. Then I tried something a book I had showed: cat /var/log/messages | grep error. Two commands, a vertical bar, and the answer fell out faster than any GUI tool on the machine could’ve produced it. I’ve been chasing that feeling ever since.
Pipes are not a feature. They’re a worldview. The bar between two commands says whatever the first one wrote, the second one can read. No adapter, no integration, no plugin. The OS is the integration.
That night with the RedHat CDs, I didn’t have the vocabulary for it. I knew the answer arrived faster than it should have. A few months later I was chaining four commands. A year later I was writing shell scripts that wrapped the chains. The shape never left.
Every Unix tool speaks lines. Stdin is a stream of bytes; stdout is a stream of bytes; the shell wires them together. That one rule replaces a thousand integrations. You don’t need a “GitHub for Jira” plugin if gh prints JSON and jq reads it. You don’t need a database GUI if psql prints rows and awk does the math.
The cost is real; text is a lossy carrier, and a tool that emits structured data through plain text always loses some shape on the way. The payoff is bigger. A new tool that emits lines slots into every existing pipeline I’ve ever written, the day it ships. No vendor has to bless the integration. The shell already did.
The first time I wrote a Makefile was for a C class in college. make called gcc, gcc produced a binary, the binary ran. The build command was make. The test command was make test. I never had to remember the flags.
Over ten years later, the Makefile in the project I’m working on right now calls docker compose instead of gcc. Different decade, different tool under the hood. Same two-word command in my muscle memory. make run. make test. make migrate. The compiler changed; the wrapper did not.
This is the part most people miss about IDEs. Every IDE button is a shell command underneath. The button is a convenience. The command is the contract. When I write the command into a make target, I get the same convenience — one keystroke — without the dependency on someone else’s UI shipping that button in the next release.
test: docker compose run --rm app pytestrun: docker compose upshell: docker compose run --rm app bash
Three targets, eight lines. The IDE button that does the same thing is buried four menus deep and breaks when the project layout changes. The Makefile breaks the same day too, but I can fix it in the same window I work in.
My hands stay on the home row. fzf to find a file. gd in Neovim to jump to a definition. A tmux pane for Claude, a pane for the test runner, a pane for git. No alt-tab. No mouse hand-off. No window manager wrestling.
The reason isn’t speed in the stopwatch sense. It’s latency between thought and action. The five-click version of a task gives the brain five chances to lose the thread. The aliased one-keystroke version gives it zero. A task I do every week (pull main, rebase my branch, push, open the PR) collapses into make pr. The script behind it is about a dozen lines I wrote over 8 years ago and have edited a handful of times since.
alias gco='git checkout'alias gp='git push'alias gst='git status -sb'alias k='kubectl'alias dc='docker compose'
Five aliases, hundreds of fewer keystrokes a week. The math compounds the longer you stay.
The terminal isn’t dogma. I open Chrome for the web. I open Figma when a designer hands me a file. I screenshare in Zoom because nobody wants to read my tmux layout. The test is whether the GUI returns something the CLI can’t: a rendered page, a pixel-perfect layout, or a human face. When the answer is yes, I use the GUI without ceremony. When the answer is no, I’m back in the terminal before the window finishes opening.
The split isn’t terminal good, GUI bad. It’s return value. Most engineering work returns text: code, logs, configs, queries, diffs, and so on. Text belongs in a text environment. The terminal is a text environment that’s been refined for fifty years. It’s not going to lose that race in my lifetime.
Pick a task you do every week with a mouse. Could be running a specific test suite. Could be tailing a log on a remote box. Could be the four-step git dance before every PR.
Look up the command-line version. It exists. Every IDE feature has a CLI underneath; that’s how the IDE talks to the tool. Wrap the command in a make target or a shell function in your dotfiles. Alias the alias if the name is still too long. Then do the task that way for a week. If the new version is slower or worse, throw it out. If it’s faster, keep it. Then pick the next click.
That’s the whole practice. No philosophy quiz at the end, no Unix beard required. One click replaced per week, fifty-two clicks a year, every one of them now a thing your future self can script, log, version, and share. The RedHat-CDs kid figured this out by accident in an hour. You can do it on purpose this afternoon.