<!--order=5-->

<h1>Python: Command-Line Tools with argparse</h1>
<p>Command-line programs are a natural fit for Linux and small computers. Python's standard-library <code>argparse</code> module handles options, help text, and input errors without needing an extra package.</p>

<h2>A Small Command-Line Program</h2>
<p>Save this as <code>temperature.py</code>:</p>
<pre>
#!/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()
</pre>

<h2>Running It</h2>
<pre>
$ python3 temperature.py 18
64.4 F

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

Convert a Celsius temperature to Fahrenheit.
...
</pre>
<p>Good help text matters when a script is used months after it was written or by someone else on the team.</p>

<h2>Optional Arguments</h2>
<pre>
parser.add_argument(
    "--output",
    default="screen",
    choices=["screen", "file"],
    help="where to write the result",
)
</pre>
<p>Use <code>required=True</code> for options that must be supplied. Use <code>choices</code> when only a known set of values is valid.</p>

<h2>Validate at the Boundary</h2>
<p>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.</p>
<pre>
parser.add_argument(
    "--retries",
    type=int,
    default=3,
    help="number of attempts",
)
</pre>

<h2>Make It Executable</h2>
<pre>
$ chmod +x temperature.py
$ ./temperature.py 18
64.4 F
</pre>
<p>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.</p>

<h2>Standard Input and Output</h2>
<p>Unix tools are easier to combine when they read from standard input and write normal results to standard output. Diagnostics belong on standard error.</p>
<pre>
import sys

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

<h2>Exit Codes</h2>
<pre>
import sys

def main():
    # do work here
    return 0

if __name__ == "__main__":
    sys.exit(main())
</pre>
<p>Returning an explicit status makes a tool dependable in cron jobs and larger shell scripts. Reserve non-zero values for failures or invalid input.</p>

<h2>Logging Instead of Debug Prints</h2>
<pre>
import logging

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

logging.info("starting report")
logging.warning("battery level is low")
</pre>
<p>Logging can later be redirected to a file or collected by systemd without rewriting every diagnostic message.</p>

<h2>References</h2>
<ul>
  <li><a href="https://docs.python.org/3/library/argparse.html" target="_blank" rel="noopener">Python argparse ↗</a></li>
  <li><a href="https://docs.python.org/3/howto/argparse.html" target="_blank" rel="noopener">Argparse Tutorial ↗</a></li>
</ul>