Search code examples
bashnmaptee

How do I make these multiple Nmap commands more elegant in bash script?


I have a small project script I have been working on that scans my network for new MAC Addresses, and notifies me if there is an unfamiliar device on my network.

I've introduced the ability to automatically perform an OS scan on the unknown MAC address if there is one. However, I can only get this to work by saving the grepped portion of my ping scan into a temporary file (tmpmac.txt), and then grepping the IP address out of that file into an additional temporary file (tmpip.txt), as the -O Nmap option requires an IP to scan:

        nmap -sn 192.168.0.0/24 | grep -B 2 "$address" > /home/pi/tmpmac.txt
        cat /home/pi/tmpmac.txt | grep "Nmap" | awk {'print $5'} > /home/pi/tmpip.txt
        unknown_mac=$( cat /home/pi/tmpip.txt )
        nmap -O $unknown_mac | tee -a /home/pi/mac_scan.log | mutt -s "OS Scan for $address" [email protected]

Entire Script:

echo "<<---AUTOMATIC SCAN--->>" >>/home/pi/mac_scan.log
date >>/home/pi/mac_scan.log
nmap -sn 192.168.0.0/24 | grep "MAC" | awk '{print $3}'| sort > /home/pi/arp.txt

readarray -t mac </home/pi/arp.txt

foundall=true

for address in "${mac[@]}"; do
    if ! grep -Fxq "$address" /home/pi/arp_table.txt;
    then
        echo "WARNING: $address is an unknown device on the network" >> /home/pi/mac_scan.log
        nmap -sn 192.168.0.0/24 | grep -B 2 "$address" | tee -a /home/pi/mac_scan.log | mutt -s "Unknown Devices on Network" [email protected]
        echo ""
        echo "MAC SEARCH RESULTS: $address" >> /home/pi/mac_scan.log
        nmap -sn 192.168.0.0/24 | grep -B 2 "$address" > /home/pi/tmpmac.txt
        cat /home/pi/tmpmac.txt | grep "Nmap" | awk {'print $5'} > /home/pi/tmpip.txt
        unknown_mac=$( cat /home/pi/tmpip.txt )
        nmap -O $unknown_mac | tee -a /home/pi/mac_scan.log | mutt -s "OS Scan for $address" [email protected]
        foundall=false
    fi
done

[[ "${foundall}" == 'true' ]] && echo "All Devices are familiar on your network" | tee -a /home/pi/mac_scan.log

Script in current form performs how it should, but I was curious if there was a more elegant way to accomplish this.


Solution

  • My nmap apparently has different output from yours so I can't test this, but I don't see any reason not to just skip all the files, and put the entire pipeline in a var=$(command) structure:

    unknown_mac=$( nmap -sn 192.168.0.0/24 |
                   grep -B 2 "$address" |
                   grep "Nmap" |
                   awk {'print $5'} )
    

    However, the overall script runs nmap -sn 192.168.0.0/24 multiple times (once at the beginning, then twice for each new host); why not just store it in a variable (or maybe file) at the beginning, and then use that whenever needed?

    hostscan=$(nmap -sn 192.168.0.0/24)
    ...
    echo "$hostscan" | othercommand
    # or:
    othercommand <<<"$hostscan"
    # or if you prefer:
    <<<"$hostscan" othercommand
    

    Note that it's critical to double-quote the variable when you use it. Actually, you should almost always double-quote variable references; you can sometimes get away with skipping the double-quotes, but understanding when it's safe is more trouble than it's worth. There are a bunch of other places in the script where you should do this. shellcheck.net is good at pointing them out, along with other common mistakes; I recommend running your script through it and fixing what it points out.

    You can also collapse grep "somestring" | awk '{print $1}' to just awk '/somestring/ {print $1}. With all those changes, capturing the unknown MAC is just:

    unknown_mac=$(<<<"$hostscan" grep -B 2 "$address" | awk '{/Nmap/ print $5}')
    

    You can also use a here-string (<<<) with readarray (note: putting readarray at the end of a pipeline won't work, because things in pipelines run in subshells):

    readarray -t mac <<<"$(<<<"$hostscan" awk '/MAC/ {print $3}' | sort)"