> ## Content Index
> Fetch the complete content index at: https://www.pinglabz.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Nmap Output Formats: -oN, -oX, -oG, -oA and Parsing Results
- URL: https://www.pinglabz.com/nmap-output-formats/
- Published: 2026-07-06T00:00:54.000Z
- Updated: 2026-08-01T19:31:05.000Z
- Author: Jaime
- Tags: Nmap, Network Security, Cisco Modeling Labs, #Import 2026-08-01 19:54

A scan you cannot reproduce is a story, not evidence. The moment an Nmap result matters - a firewall change you signed off on, a rogue service you found on a production VLAN, an asset inventory the audit team will lean on - you need it written to a file in a format that survives the terminal scrollback. Saved output lets you diff a segment week over week to catch new or closed ports, feed the results into other tooling, and prove what the network looked like at a specific timestamp. This guide is part of [the complete Nmap guide](https://www.pinglabz.com/nmap/), and it covers the output formats Nmap can write, when each one earns its place, and the short shell one-liners that turn a saved scan into an answer.

## Why you save output at all

Running Nmap and reading the screen is fine for a quick look. Everything past that wants a file. Four reasons come up constantly in network work. First, **evidence**: a timestamped scan file is defensible in a change record or an incident timeline in a way that a screenshot never is. Second, **diffing over time**: if you scan the same subnet on a schedule and store each run, you can compare them and immediately see what changed - a new listener that appeared overnight, a port that closed after a patch. Third, **feeding other tools**: XML output is the input format for a whole ecosystem of parsers, importers, and reporting engines. Fourth, **asset inventories**: grepable output is trivial to fold into a spreadsheet or a CMDB import. Pick the format that matches the downstream job, and Nmap will write it in the same pass as the scan.

## The output formats

Nmap writes several formats, each selected by a flag that takes a filename. You can request more than one at a time, and `-oA` is a shortcut for "give me all the main ones."

\-oN (normal)

The human-readable report you already see on screen, saved to a file.

Best for people. Read it, paste it in a ticket, archive it.

\-oX (XML)

Structured XML with full detail: ports, states, services, CPEs.

Best for programmatic parsing, ndiff, and importing into tools.

\-oG (grepable)

One line per host, so grep and awk can slice it directly.

Best for quick command-line pipelines and ad-hoc inventories.

\-oA (all)

Writes normal, XML, and grepable at once from one basename.

The safe default when you are not sure which you will need later.

\-oS (script kiddie)

Normal output mangled into l33t-speak. A joke Nmap actually ships.

Never use it for real work. It exists purely as an aside.

\- (stdout)

Pass a single dash as the filename to send a format to stdout.

Lets you pipe grepable output straight into grep without a temp file.

The two you will reach for most are `-oA basename` when you want a full record on disk, and `-oG -` when you want to pipe grepable output straight into a command. The dash-as-filename trick works for any format flag, so `-oX -` streams XML to stdout for a downstream parser without ever touching disk.

## Normal output, for humans

Normal output is exactly what Nmap prints to the terminal, and it is what most engineers read. Here is a version-detection scan of localhost on a Debian VM, saved with `-oA scandemo` (which produced `scandemo.nmap`, `scandemo.gnmap`, and `scandemo.xml` in one shot). This is the `.nmap` file:

```
# Nmap 7.95 scan initiated Sun Jul  5 15:05:24 2026 as: nmap -sV -p21,22,80 -oA scandemo 127.0.0.1
Nmap scan report for localhost (127.0.0.1)
Host is up (0.000090s latency).

PORT   STATE SERVICE VERSION
21/tcp open  ftp     vsftpd 3.0.5
22/tcp open  ssh     OpenSSH 10.0p2 Debian 7+deb13u4 (protocol 2.0)
80/tcp open  http    nginx
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Jul  5 15:05:30 2026 -- 1 IP address (1 host up) scanned in 6.84 seconds
```

This reads cleanly: three open ports, each with a service and a version string that `-sV` probed out (that is [Nmap version detection](https://www.pinglabz.com/nmap-version-detection/) at work). It is the format to paste into a change ticket or an incident note, because a human can read it without any tooling. What it is not good for is machine parsing - the layout is aligned for eyes, not for `awk`.

## Grepable output, for pipelines

Grepable output collapses each host onto a single line, which is precisely what makes it easy to slice with standard Unix tools. Here is the `scandemo.gnmap` file from the same run:

```
# Nmap 7.95 scan initiated Sun Jul  5 15:05:24 2026 as: nmap -sV -p21,22,80 -oA scandemo 127.0.0.1
Host: 127.0.0.1 (localhost)	Status: Up
Host: 127.0.0.1 (localhost)	Ports: 21/open/tcp//ftp//vsftpd 3.0.5/, 22/open/tcp//ssh//OpenSSH 10.0p2 Debian 7+deb13u4 (protocol 2.0)/, 80/open/tcp//http//nginx/
# Nmap done at Sun Jul  5 15:05:30 2026 -- 1 IP address (1 host up) scanned in 6.84 seconds
```

Notice the single `Host: ... Ports:` line packs every port for that host into one field, comma-separated, each in a `port/state/proto//service//version/` shape. That regular structure is the whole appeal: because it is one line per host, you can `grep` for a state or `awk` for a field without wrestling with a multi-line report. From the CML lab, the shorter two-port form shows the same pattern across two hosts at once:

```
$ nmap -oG - -p 22,80 10.10.10.1 10.10.10.21
# Nmap 7.95 scan initiated Sun Jun 28 07:12:51 2026 as: nmap -oG - -p 22,80 10.10.10.1 10.10.10.21
Host: 10.10.10.1 ()	Status: Up
Host: 10.10.10.1 ()	Ports: 22/open/tcp//ssh///, 80/closed/tcp//http///
Host: 10.10.10.21 ()	Status: Up
Host: 10.10.10.21 ()	Ports: 22/closed/tcp//ssh///, 80/open/tcp//http///
# Nmap done at Sun Jun 28 07:12:51 2026 -- 2 IP addresses (2 hosts up) scanned in 0.24 seconds
```

Here `-oG -` streamed straight to stdout. The router (10.10.10.1) has SSH open and HTTP closed (it answered with a RST, not a filter), and the web server (10.10.10.21) is the mirror image. One line per host, ready to pipe.

## XML output, for tools

XML is the format you feed to software. It carries everything the scan learned in a strict, parseable structure, and it is the input for `ndiff`, for importers, and for reporting engines. Here is the ports section of `scandemo.xml` from the same localhost scan, trimmed to the structure that matters:

```
<ports><port protocol="tcp" portid="21"><state state="open" reason="syn-ack" reason_ttl="64"/><service name="ftp" product="vsftpd" version="3.0.5" ostype="Unix" method="probed" conf="10"><cpe>cpe:/a:vsftpd:vsftpd:3.0.5</cpe></service></port>
<port protocol="tcp" portid="22"><state state="open" reason="syn-ack" reason_ttl="64"/><service name="ssh" product="OpenSSH" version="10.0p2 Debian 7+deb13u4" extrainfo="protocol 2.0" ostype="Linux" method="probed" conf="10"><cpe>cpe:/a:openbsd:openssh:10.0p2</cpe><cpe>cpe:/o:linux:linux_kernel</cpe></service></port>
<port protocol="tcp" portid="80"><state state="open" reason="syn-ack" reason_ttl="64"/><service name="http" product="nginx" method="probed" conf="10"><cpe>cpe:/a:igor_sysoev:nginx</cpe></service></port>
</ports>
```

Every fact from the normal report is here as an attribute a parser can pull without guessing: each `<port>` has a `<state>` element (with the reason and TTL) and a `<service>` element carrying the product, version, and one or more `<cpe>` entries. Those CPE strings (`cpe:/a:vsftpd:vsftpd:3.0.5`, `cpe:/a:openbsd:openssh:10.0p2`) are the standardized identifiers that vulnerability tooling matches against a CVE database, which is why XML is the format to save when the scan feeds anything automated. You would never read this by eye, and you would never `grep` it reliably - you hand it to a parser.

## Parsing the saved output

The reason grepable output exists is to make quick answers a one-liner. These operate on the saved files above and are standard, everyday usage. To list every open service across a saved `.gnmap`, split on the comma-separated port field and keep the ones marked open:

```
$ grep -oE '[0-9]+/open/[^,]+' scandemo.gnmap
21/open/tcp//ftp//vsftpd 3.0.5/
22/open/tcp//ssh//OpenSSH 10.0p2 Debian 7+deb13u4 (protocol 2.0)/
80/open/tcp//http//nginx/
```

To pull a clean host-plus-open-port inventory across a multi-host grepable file, a short `awk` walks each host line and prints the port numbers that are open:

```
$ awk '/Ports:/{ip=$2; n=split($0,p,", "); for(i=1;i<=n;i++) if(p[i]~/\/open\//){split(p[i],f,"/"); print ip, f[1]}}' scan.gnmap
```

That is the payoff of one-line-per-host: no XML library, no scripting language, just the tools already on every box. For anything more structured than a grep - correlating services to CVEs, building a report, diffing two runs - switch to XML and let a real parser do it.

## Verbosity, progress, and diffing over time

A few flags shape what lands in the output and how much you see while a long scan runs. `-v` (and `-vv` for more) raises verbosity so Nmap reports open ports as it finds them rather than only at the end. `--stats-every 10s` prints a progress line on a fixed interval, which is a lifesaver on a big sweep so you know it has not hung. `--open` trims the report to only open ports, cutting the closed and filtered noise when you only care about what is listening. And `--reason` adds the *why* behind each state (the `syn-ack`, `reset`, and `no-response` values you saw earlier), which is worth saving whenever the state of a port might be questioned later.

The real long-term value of saved output is **diffing**. Nmap ships `ndiff`, which takes two XML scan files and reports exactly what changed between them - hosts that appeared or vanished, ports that opened or closed, services that changed version. Scan a subnet on a schedule, save each run with `-oX`, and `ndiff yesterday.xml today.xml` surfaces the new listener that showed up overnight without you rereading a single line. This is how you turn Nmap from a point-in-time tool into a change-detection one. If you would rather grow one running record than keep dated files, `--append-output` tells Nmap to append to an existing output file instead of overwriting it, though for diffing you generally want discrete per-run files that `ndiff` can compare cleanly.

The same idea works in the other direction, and hardly anyone bothers. Baselining `show ip access-lists` on a schedule and diffing the hit counts is [the cheapest way to catch enumeration that arrives too slowly to trip anything else](https://www.pinglabz.com/detecting-nmap-scans-cisco-blue-team/), because an ACE counter is cumulative and does not care whether the packets took four seconds or four days.

## Choosing a format in practice

The decision is quick once the downstream job is clear. Reading it yourself or pasting into a ticket? `-oN`. Grepping or awking a fast inventory? `-oG`, or `-oG -` to skip the temp file. Feeding a parser, matching CPEs to CVEs, or diffing with `ndiff`? `-oX`. Not sure which you will need later, or building an audit trail? `-oA` and keep all three. The runtime cost of writing extra formats is effectively zero - the scan does the work, and serializing the results is trivial - so on any scan that matters, defaulting to `-oA` costs nothing and saves you re-running later. How fast the scan itself finishes is a separate question covered in [Nmap timing and performance](https://www.pinglabz.com/nmap-timing-performance/).

## Key takeaways

Save your scans, because a result you cannot reproduce or diff is not evidence. Normal output (`-oN`) is for humans, grepable output (`-oG`) collapses one line per host so `grep` and `awk` can slice it in a pipeline, and XML (`-oX`) carries the full structured detail - states, services, and CPE identifiers - that parsers, importers, and `ndiff` depend on. When in doubt, `-oA basename` writes all three at once for nothing extra, and the dash-as-filename trick (`-oG -`) streams a format straight into a pipe. Layer on `-v`, `--stats-every`, `--open`, and `--reason` to control what you capture and how much you see mid-scan, then use `ndiff` across dated XML files to catch new or closed ports over time and `--append-output` when you want one growing record. Pair saved output with solid [version detection](https://www.pinglabz.com/nmap-version-detection/) and you have a repeatable, auditable view of what is really running on your network. For how this fits with discovery, scanning, timing, and the rest of the toolkit, work through [the complete Nmap guide](https://www.pinglabz.com/nmap/).