Filtering nmap outputs using grep, awk, or sed

user3037023 picture user3037023 · Nov 26, 2013 · Viewed 8k times · Source

I using NMAP, I ran a scan on a large network to see open ports. The output file is 2MB, but I want to filter out all of the IP addresses with ALL closed ports.

 Nmap scan report for 10.x.x.x
 Host is up (0.048s latency).
 Not shown: 998 closed ports
 PORT   STATE SERVICE
 22/tcp open  ssh
 23/tcp open  telnet

 Nmap scan report for 10.x.x.x
 Host is up (0.046s latency).
 All 1000 scanned ports on 10.x.x.x are closed

 Nmap scan report for 10.x.x.x
 Host is up (0.045s latency).
 All 1000 scanned ports on 10.x.x.x are closed

Should output to only output to:

 Nmap scan report for 10.x.x.x
 Host is up (0.048s latency).
 Not shown: 998 closed ports
 PORT   STATE SERVICE
 22/tcp open  ssh
 23/tcp open  telnet

EDIT

The results are like

 Nmap scan report for 10.x.x.x
 Host is up (0.048s latency).
 Not shown: 998 closed ports
 PORT   STATE SERVICE
 22/tcp open  ssh
 23/tcp open  telnet
 Nmap scan report for 10.x.x.x
 Host is up (0.046s latency).
 All 1000 scanned ports on 10.x.x.x are closed
 Nmap scan report for 10.x.x.x
 Host is up (0.045s latency).
 All 1000 scanned ports on 10.x.x.x are closed

There are newlines that didn't copy over correctly

EDIT Thanks everyone. I see awk is pretty awesome and easy to do.

Answer

ghoti picture ghoti · Nov 26, 2013

As I see it, you're trying to apply some intelligent filtering to your nmap output, not just a simple "grep".

Since your nmap command (per your comments rather than your question) is pointing at a subnet rather than an individual host, you need to interpret each section of output individually. But this kind of interpretation is too complex for a regular expression. (Might be possible with PREG, but it would be extremely difficult to compose and virtually impossible to read.) A tool like awk is a much better choice for this task.

For example:

nmap 10.10.0.0/16 | awk '
  /^Nmap scan report for/ {
    if (open) {
      print output;
    }
    output="";
    open=0;
  }

  {
    output=output $0 "\n";
  }

  $2 == "open" {
    open=1;
  }

  END {
    if (open) {
      print output;
    }
  }
'

Awk is easy enough to read, but you should know that it operates by matching each line of input against expressions that look like condition { action }. If a condition evaluates to true, the action is executed. So the first one has a condition that is a regular expression to find the beginning of a host section, and the actions are encased in curly braces. The second condition is missing, and so is assumed to be "true" for all lines. The last condition matches after all lines of input have been processed, and is necessary in case the last host scanned includes open ports.

This kind of thing could be expressed much more densely, but I've written it long so that you can see how the logic works more easily. Tighter code will come with practice.

Note that you can put the awk script into a separate file, which you reference using awk's -f option. Read the man page for details.

You could also put this whole thing into its own shell script if you don't want to keep the awk portion in its own file. You should be able to find examples of what that looks like easily enough -- at any rate, it's out of scope for this question.