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
stdincarries input data.stdoutcarries the requested result.stderrcarries 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.