Nmap’s output formats are more than just different ways to view scan results; they’re fundamentally different lenses through which to understand network visibility.
Let’s see Nmap in action, generating a simple scan and then transforming its output. Imagine you’ve scanned a single host, 192.168.1.100, for open TCP ports 22 and 80.
First, the default "normal" output:
nmap -p 22,80 192.168.1.100
This will produce something like:
Starting Nmap 7.92 ( https://nmap.org ) at 2023-10-27 10:30 PDT
Nmap scan report for 192.168.1.100
Host is up (0.0020s latency).
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 0.50 seconds
It’s human-readable, great for a quick glance. But what if you want to programmatically extract just the open ports? That’s where the other formats shine.
The "grepable" format is designed for simple text processing.
nmap -p 22,80 --grepable 192.168.1.100 -oG nmap_scan.grep
The output file nmap_scan.grep will contain lines like:
# Nmap 7.92 scan initiated Fri Oct 27 10:31:00 2023 as: nmap --grepable -p 22,80 192.168.1.100
Host: 192.168.1.100 (Unknown) Ports: 22/open/ssh/, 80/open/http/
This format is structured with tabs and specific keywords, making it easy to grep, awk, or sed your way to specific pieces of information. For instance, to get just the open ports and their states:
grep -oP 'Ports:\s*\K[^ ]+' nmap_scan.grep | sed 's|/$||'
This would yield:
22/open/ssh
80/open/http
The XML format is the most powerful for structured data. It’s machine-readable and contains a wealth of detail far beyond what the normal or grepable formats offer.
nmap -p 22,80 --xml 192.168.1.100 -oX nmap_scan.xml
The nmap_scan.xml file will be a detailed XML document. Here’s a snippet:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE nmaprun SYSTEM "https://nmap.org/data/nmap.dtd">
<?xml-stylesheet href="file:///usr/share/nmap/nmap.xsl" type="text/xsl"?>
<nmaprun scanner="nmap" args="nmap --xml -p 22,80 192.168.1.100" start="1698391860" startstr="Fri Oct 27 10:31:00 2023" version="7.92" xmloutputversion="1.04">
<scaninfo type="connect" protocol="tcp" numservices="2" services="22,80"/>
<verbose level="0"/>
<debugging level="0"/>
<host starttime="1698391860" endtime="1698391860"><status state="up" reason="echo" reason_ttl="64"/><address addr="192.168.1.100" addrtype="ipv4"/><hostnames><hostname name="unknown" type="PTR"/></hostnames><ports><port protocol="tcp" portid="22"><state state="open" reason="syn-ack" reason_ttl="64"/><service name="ssh" method="table" conf="3"/></port><port protocol="tcp" portid="80"><state state="open" reason="syn-ack" reason_ttl="64"/><service name="http" method="table" conf="3"/></port></ports><times srtt="2000" rttvar="500" to="10000"/></host>
</nmaprun>
This XML structure allows you to use tools like xmllint or Python’s xml.etree.ElementTree to parse it precisely. You can extract not just the open ports, but also the scan arguments used, the host’s uptime, the exact reason a port was deemed open (e.g., syn-ack), and even the service version detection results if you ran those probes. The xmloutputversion tag is particularly useful for ensuring compatibility if you’re processing Nmap XML files years down the line.
The real power comes when you combine these formats with other tools. For example, using the XML output to feed a vulnerability scanner, or using the grepable output to generate an inventory of all SSH servers on your network.
What most people don’t realize is how granular the reason attribute in the XML output can be. It’s not just "open"; it could be syn-ack, ack, reset, unreachable, or others, providing crucial context for why Nmap arrived at a particular port state. This is invaluable for debugging network issues or understanding the nuances of TCP handshakes.
The next step is often to explore Nmap’s scripting engine (NSE) to enrich these scan results further.