Python: Command-Line Tools with argparse

Command-line programs are a natural fit for Linux and small computers. Python's standard-library argparse module handles options, help text, and input errors without needing an extra package.

A Small Command-Line Program

Save this as temperature.py:

#!/usr/bin/env python3

import argparse

def main():
    parser = argparse.ArgumentParser(
        description="Convert a Celsius temperature to Fahrenheit."
    )
    parser.add_argument(
        "celsius",
        type=float,
        help="temperature in degrees Celsius",
    )
    args = parser.parse_args()

    fahrenheit = args.celsius * 9 / 5 + 32
    print(f"{fahrenheit:.1f} F")

if __name__ == "__main__":
    main()

Running It

$ python3 temperature.py 18
64.4 F

$ python3 temperature.py --help
usage: temperature.py [-h] celsius

Convert a Celsius temperature to Fahrenheit.
...

Good help text matters when a script is used months after it was written or by someone else on the team.

Optional Arguments

parser.add_argument(
    "--output",
    default="screen",
    choices=["screen", "file"],
    help="where to write the result",
)

Use required=True for options that must be supplied. Use choices when only a known set of values is valid.

Validate at the Boundary

Convert and validate command-line values as they enter the program. That keeps the rest of the code working with the right types instead of checking strings everywhere.

parser.add_argument(
    "--retries",
    type=int,
    default=3,
    help="number of attempts",
)

Make It Executable

$ chmod +x temperature.py
$ ./temperature.py 18
64.4 F

The first line tells Unix which interpreter should run the file. Keeping scripts small, documented, and predictable makes them good building blocks for cron jobs and system services.

Standard Input and Output

Unix tools are easier to combine when they read from standard input and write normal results to standard output. Diagnostics belong on standard error.

import sys

for line in sys.stdin:
    cleaned = line.strip()
    if cleaned:
        print(cleaned.upper())
$ printf 'one\ntwo\n' | python3 uppercase.py
ONE
TWO

Exit Codes

import sys

def main():
    # do work here
    return 0

if __name__ == "__main__":
    sys.exit(main())

Returning an explicit status makes a tool dependable in cron jobs and larger shell scripts. Reserve non-zero values for failures or invalid input.

Logging Instead of Debug Prints

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
)

logging.info("starting report")
logging.warning("battery level is low")

Logging can later be redirected to a file or collected by systemd without rewriting every diagnostic message.

References