5  Build a Basic CLI with argparse

Introduction

The first step in building a tool, if you ask me, is to create a command centre: a way for your user to interact with your tool. You may have used command-line applications where you pile a bunch of directives and options onto an application, hit enter, and bam, you’ve got your answers.

In this lesson, you’ll build the smallest useful command-line interface for the subdomain bruteforcer. We’re not building the whole tool yet; we’re accepting one required value from the terminal and making that value available to the rest of the program.

Concepts Covered

  • Command-line interfaces
  • argparse.ArgumentParser
  • Required positional arguments
  • Simulating command-line input inside a notebook
  • Letting argparse generate help and usage text for you

Why This Matters

Security tools are often run from terminals, scripts, notes, and repeatable test commands. A clear command-line interface gives the tool a predictable front door. Today that front door only needs a domain name; later we’ll add more locks, handles, and little labels so people know what to push.

Objective

Build an argument parser that accepts one required domain, then read that value from the parsed arguments.

A Tiny Parser

Python has a built-in parser for command-line options called argparse. Older Python examples sometimes mention optparse, but for modern Python, argparse is the tool we want.

The benefit of using argparse over rolling your own command-line parser is that argparse will figure out how to parse sys.argv (user input), allowing you to focus on building tools. argparse will also help automatically generate help and usage messages and issue errors when the user provides invalid arguments.

In a real terminal, parse_args() reads from sys.argv. In a notebook, we pass a short list to parse_args() so the example stays deterministic and doesn’t accidentally consume the notebook server’s own arguments.

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("domain", help="domain to test, e.g. example.test")

args = parser.parse_args(["example.test"])
print(args.domain)
1
Create a parser object, which stores the command-line contract.
2
Add one required positional argument named domain.
3
Simulate running the script with example.test as the terminal input.
example.test

Here’s what’s happening: argparse turns the user-facing command-line text into a small object your program can read. We’ve done very little so far, but if this were a real script, argparse would already know how to produce -h / --help output. That’s the first useful thing it gives you for free; the rest of the help text is our job.

A Parser Function

To get started we want to create a function to build and handle arguments passed by the user. Keeping the parser inside a function makes the script easier to read and easier to test. Later lessons will add options without changing the rest of the program.

This function doesn’t run the whole application. It builds the parser and returns it, which means the rest of the code can decide whether it wants real terminal input or a tiny pretend argument list for a notebook test.

import argparse

def build_argument_parser():
    parser = argparse.ArgumentParser()
    parser.add_argument("domain", help="domain to test, e.g. example.test")
    return parser

parser = build_argument_parser()
args = parser.parse_args(["example.test"])
print(f"Target domain: {args.domain}")
Target domain: example.test

Checkpoint

Create your own build_argument_parser() function. It should return a parser that accepts one required domain argument.

When you test it with parse_args(["example.test"]), your output should look like this:

example.test

Now poke it a little. What happens if you ask the parser for format_help()? What would happen in a real terminal if you ran your application with -h or --help? This is the moment where argparse starts earning its keep.

import argparse

def build_argument_parser():
    parser = argparse.ArgumentParser()
    # Add the required domain argument here.
    return parser

# After you add the argument, uncomment these lines to test your parser.
# args = build_argument_parser().parse_args(["example.test"])
# print(args.domain)

Extension Activity

Change the help text for domain so it tells the user that this course uses fixture-backed domains such as example.test. Pretend Future You is tired, squinting at a terminal, and needs the help message to be kind.

Answer

References