Skip to main content

Command-Line Program Boundaries

A command-line program communicates through arguments, environment variables, standard streams, files, and exit status. Treat each as a public interface that other programs may depend on.

Streams

  • stdin carries input data.
  • stdout carries the requested result.
  • stderr carries diagnostics and progress that should not corrupt a pipeline.

input() reads a line from stdin after optionally writing a prompt. It suits interactive tools, not data pipelines. For streaming input, iterate over sys.stdin; for user-facing output use print, and for operational events use logging.

import sys

for line in sys.stdin:
sys.stdout.write(transform(line))

When output has a machine-readable contract, keep it free of headings, colors, and incidental logs. Offer a separate structured mode such as JSON when scripts will consume it.

Arguments and configuration

Use argparse rather than manually indexing sys.argv:

import argparse
from pathlib import Path

parser = argparse.ArgumentParser()
parser.add_argument("input", type=Path)
parser.add_argument("--format", choices=("text", "json"), default="text")
args = parser.parse_args()

Arguments are explicit and visible in process listings and shell history. They are unsuitable for secrets. Environment variables are useful for inherited deployment configuration, but they are strings, can also leak through process inspection or diagnostics, and are not a secret store.

import os

endpoint = os.environ.get("APP_ENDPOINT", "https://example.invalid")

Parse and validate configuration once near program startup. Distinguish an unset variable from an empty value when the domain cares about that difference. Do not mutate the process-wide environment deep inside library code.

Exit status

Return zero for success and a documented nonzero status for failure. A main function makes that contract testable:

def main() -> int:
...
return 0

if __name__ == "__main__":
raise SystemExit(main())

Exceptions are an internal control-flow mechanism; exit status and stderr are the external CLI contract. Catch exceptions only at the boundary when the tool can translate them into a useful diagnostic and stable status. Preserve enough context for debugging and avoid printing secrets or entire sensitive payloads.

Source