# Wrapping Nmap in Python: Turning a Manual Tool Into a Repeatable Script

Nmap is powerful on its own, but using it well still means remembering the right flags, typing the full command correctly every time, and manually redirecting output somewhere useful if you actually want to keep a record of what you found. This lab worked through a small but genuinely useful step up from that: wrapping Nmap in a Python script that prompts for the target and scan type, runs the scan, and automatically logs the results to a file, turning a manual command into a small, reusable tool.

## Why Wrap a Tool That Already Works

Modifying or extending an existing security tool doesn't necessarily mean changing the tool's own code. More often, as in this lab, it means building a thin layer around it that handles the repetitive parts, prompting for input, running the command consistently, saving output somewhere predictable, so the person using it doesn't have to reconstruct the full command and remember to redirect output every single time.

## Building the Script

I used `nano` to create `nmap_scanning_script.py` and built it incrementally, understanding each piece before adding the next.

### Giving Python the ability to run external commands

```python
import subprocess
```

Python doesn't run Nmap itself, Nmap is a separate, independent command-line tool installed on the system. The `subprocess` module is what bridges that gap, letting a Python script invoke an external system command and interact with its execution, exactly what's needed to call Nmap programmatically rather than requiring someone to type the command by hand each time.

### Collecting input instead of hardcoding it

```python
def run_nmap():
    target = input("Enter the target IP or domain: ")
    scan_type = input("Enter the scan type (for example, -sS, -sV, -A): ")
```

Wrapping the logic inside a function, rather than writing it as loose top-level code, is a small but meaningful structural choice: it makes the logic reusable and keeps the script organized, especially as more functionality gets added later. Prompting for both the target and the scan type as input, rather than hardcoding either one, is what turns this from a script that only scans one specific thing into a genuinely general-purpose tool, usable against any target with any supported scan type, without ever touching the code itself.

### Running the scan

```python
    print("Running nmap...")
    subprocess.call(["nmap", scan_type, target])
```

`subprocess.call()` takes a list of arguments, structured exactly as they'd appear on the command line, each piece (the program name, the flag, the target) as a separate list element rather than one combined string. That structure matters: passing arguments as a list, rather than building a single command string, avoids a category of bugs and security issues related to how shells interpret spaces and special characters in combined strings.

### Redirecting output to a log file

```python
    log_file = "nmap_scan_log.txt"
    print(f"Logging scan results to {log_file}...")
    subprocess.call(["nmap", scan_type, target], stdout=open(log_file, "w"))
```

This is the actual value-add of wrapping the tool in a script rather than just running Nmap directly. The `stdout=open(log_file, "w")` argument redirects the scan's output, which would otherwise just print to the terminal and then be gone the moment the window closes or scrolls past, into a persistent file instead. That's a small change with real practical consequences: results that can be reviewed later, compared against a previous scan of the same target, or handed off to someone else, rather than output that exists only transiently on screen.

### Making the script runnable directly

```python
if __name__ == "__main__":
    run_nmap()
```

This is a standard Python idiom worth understanding rather than just memorizing: it ensures `run_nmap()` only actually executes when the script is run directly (`python3 nmap_scanning_script.py`), not if this file were ever imported as a module into some other script. That distinction matters for building reusable, well-behaved Python code, a script written this way can later be imported elsewhere without unexpectedly firing off a live scan the moment it's imported.

## Testing Against an Authorized Target

I ran the script with `python3 nmap_scanning_script.py`, and when prompted, entered `scanme.nmap.org`, the Nmap project's own official test target, specifically set up and authorized for exactly this kind of practice, as the scan target, and selected a scan type (one of `-sS` for a SYN scan, `-sV` for version detection, or `-A` for a more aggressive, comprehensive scan).

![](https://cdn.hashnode.com/uploads/covers/631edab3f73f4734bc059f39/16b71ded-9eba-4a4a-b156-692d0bc44f6a.png align="center")

![](https://cdn.hashnode.com/uploads/covers/631edab3f73f4734bc059f39/678ac907-344b-43bf-8bed-a3b5cbf935d6.png align="center")

The script ran the scan, printed its logging message, and confirmed completion. Running `ls` afterward confirmed both files existed in the directory: the script itself, and the newly created `nmap_scan_log.txt`. Running `cat nmap_scan_log.txt` displayed the actual scan output, the exact same results Nmap would have printed directly to the terminal, now sitting durably in a file instead.

![](https://cdn.hashnode.com/uploads/covers/631edab3f73f4734bc059f39/d6f9b330-1661-4cb5-8fb1-d43accbef9fb.png align="center")

![](https://cdn.hashnode.com/uploads/covers/631edab3f73f4734bc059f39/6ee85a1d-0627-432d-99d0-64b73b1062b6.png align="center")

![](https://cdn.hashnode.com/uploads/covers/631edab3f73f4734bc059f39/3589d700-663c-4549-b182-01af4273182b.png align="center")

## Why This Small Pattern Matters Beyond Nmap Specifically

A few things stood out about why this exercise is a genuinely useful pattern, not just a Nmap-specific trick.

**Wrapping a tool in a script reduces the chance of a repeated manual mistake.** Typing a full Nmap command correctly, with the right flags and target, every single time invites small errors: a typo in a flag, forgetting to redirect output, using a slightly different command than last time. A script standardizes the invocation, the same logic runs identically every time it's called, regardless of who's running it or how many times.

**Passing arguments as a list, not a concatenated string, is a real security practice, not just a style preference.** Building a single command string and handing it to a shell to interpret opens the door to injection-style issues, especially if any part of that string ever comes from less-trusted input. Structuring the call as a list of discrete arguments, as `subprocess.call()` expects here, avoids that category of problem by construction.

**Logging output to a file, rather than letting it live only in the terminal, is what actually makes a scan result useful afterward.** A scan whose results exist only on screen is effectively disposable, gone the moment the terminal closes or the output scrolls out of view. A logged result is something you can diff against a later scan of the same target, archive as part of an assessment, or hand off to someone else entirely.

**This script is intentionally a starting point, not a finished tool.** As written, it prompts interactively and writes to one fixed log filename every time, meaning a second scan would simply overwrite the first. A more developed version might accept command-line arguments instead of interactive prompts (useful for actually automating it further, say inside a larger script or a scheduled job), or generate a uniquely named or timestamped log file per run so results don't get silently overwritten. Understanding this simpler version first is exactly what makes those next improvements make sense when you get there.

**This is really a template, not a one-off.** The same basic shape, wrap an existing command-line tool in Python, take structured input instead of hardcoding it, log output persistently instead of letting it vanish, applies to wrapping all sorts of other command-line security tools, not just Nmap specifically.

This lab didn't modify Nmap itself at all, and that's really the point: a small Python wrapper around an existing, already-powerful tool added real value without touching the tool's own code. Structured input instead of manually retyping full commands, consistent execution instead of a slightly different command every time, and persistent logging instead of output that vanishes the moment the terminal scrolls, together turn a manual, one-off command into something closer to a small, dependable, reusable tool.

That's a genuinely useful pattern well beyond this one script: wrap, standardize, and log, three small additions that turn any command-line tool you use repeatedly into something a little more automatable and a lot more trustworthy to run consistently.
