The Quiet Terminal

← Practical Tips

argparse: give your scripts a real command line

2026-05-22 · 3 min read

Most of my scripts start life taking one argument, and the first version always looks the same: sys.argv[1] and a prayer. That's fine on the day I write it and incomprehensible six months later, when I've forgotten the argument order and there's no help to be had. Here's a real script — tail the last lines of a log file — being dragged into the argparse era.

Version one: sys.argv[1]

# taillog.py — first version
import sys

with open(sys.argv[1], encoding="utf-8") as f:
    lines = f.readlines()

for line in lines[-10:]:
    print(line, end="")

It works. The hidden costs: run it with no argument and you get a raw IndexError traceback instead of a hint; the 10 is buried in the code where only reading the source will find it; and there is no way to discover how to use the script except reading the source. All three of these are really the same problem — the command line isn't defined anywhere, it just happens.

Version two: argparse

# taillog.py — second version
import argparse
from pathlib import Path

parser = argparse.ArgumentParser(description="Print the last lines of a file.")
parser.add_argument("filename", help="the file to read")
parser.add_argument("-n", "--count", type=int, default=10,
                    help="how many lines to show (default: 10)")
parser.add_argument("--verbose", action="store_true",
                    help="also show the total line count")
args = parser.parse_args()

lines = Path(args.filename).read_text(encoding="utf-8").splitlines()
start = max(len(lines) - args.count, 0)
if args.verbose:
    print(f"{len(lines)} lines total")
for line in lines[start:]:
    print(line)

What each piece buys:

-h for free

usage: taillog.py [-h] [-n COUNT] [--verbose] filename

Print the last lines of a file.

positional arguments:
  filename              the file to read

options:
  -h, --help            show this help message and exit
  -n COUNT, --count COUNT
                        how many lines to show (default: 10)
  --verbose             also show the total line count

That entire screen is generated from the six add_argument lines — I wrote none of it. Six months from now, my future self runs taillog.py -h and knows everything the script can do.

One bug the obvious version has

The slice looks like it should be lines[-args.count:], and it almost is — until someone passes -n 0, because lines[-0:] means "from zero steps before the end", which is the whole file. The max(len(lines) - args.count, 0) version behaves for every count, including zero. I found this out by passing 0 on purpose, which is a habit I recommend.

The honest opinion

argparse is wordy — six lines of setup for a two-flag script feels like overhead, and for a truly throwaway script, skip it. But the help text and the validation are precisely the parts I never write by hand and precisely the parts my future self needs. Ten minutes per script, once, and the script stops being a note to self.