Who Is Reading Your Output?
A robust CLI tool has two distinct audiences with incompatible needs:
- Humans sitting at a terminal who want readable, scannable output.
- Other programs — shell scripts, CI/CD pipelines,
jq,grep— that need structured, parseable data.
Trying to serve both audiences with the same output format serves neither well.
Designing for Humans:
- Use aligned columns or ASCII tables to make data scannable.
- Use colors to highlight status: green for success, red for failure, yellow for warnings. But only when writing to a terminal — never when output is piped to a file or another program. Check
sys.stdout.isatty()before emitting color codes. - Send progress indicators, status messages, and verbose logs to
stderr(Standard Error), notstdout. This keeps the data stream clean.
Designing for Machines:
- Machines cannot reliably parse ASCII tables or colored text.
- Provide a
--jsonor--format=jsonflag that switches output to structured JSON onstdout. - JSON output should be consistent: same keys, same types, every time. A machine consumer breaks if your schema changes unexpectedly.
The Golden Rule of stdout: Keep your data stream clean. If your tool outputs a list of IP addresses, don't print "Here are your IPs:" before the list. Just print the IPs. This allows users to pipe your output directly into grep, xargs, jq, or any other tool without needing to strip your prose first.
The stdout / stderr Split:
stdout → data (the result of the operation)
stderr → everything else (progress, warnings, errors, logs)
This split is what makes Unix pipelines work. When you run mytool list-servers | grep active, the shell connects mytool's stdout to grep's stdin. If your tool prints error messages to stdout, they get passed to grep and corrupt the data stream. Printing errors to stderr means they appear on the terminal for the human to see, while the pipeline continues to work correctly.
A common interview question is: "What's the difference between stdout and stderr, and why does it matter?" The answer goes beyond "one is for errors." The key insight is that the stdout/stderr split is what makes Unix pipelines composable. Stdout is the data channel; stderr is the diagnostic channel. A tool that conflates them breaks every pipeline it's used in.