A Bash Script to Generate a List of IPs for a Subnet
Posted on Sun 26 April 2015 in Tech Blog
As a part of what I do, I often need to document network infrastructure where little or no records have been kept. One of the biggest challenges in doing this is finding all the network devices. I got to thinking recently how handy it would be to have a Linux utility that would generate a list of all available IP addresses in a subnet. The list could then be used to automate tasks like pinging each address in the network to see if a device responds.
A check of the internet revealed several attempts at bash scripts that could do this; however, the ones I tested all seemed to have problems handling certain inputs. What I needed was a tool that could accurately generate IP addresses for a network with a mask of 16 to 30 bits; the scripts I happened to test often had problems with mask values of less than 24 bits; or they generated inaccurate data when the IP address given to them was not a subnet address. I decided to see if I could do better. And incidentally, I also learned a lot about newer features in bash, such as integer arrays, and math mode. Enjoy!
#!/bin/bash
# Copyright 2015 by Duane Penzien
# All rights reserved.
#
# Redistribution and use of this script, with or without modification,
# Redistribution and use of this script, with or without modification,
# is permitted provided that the following conditions are met:
#
# 1. Redistributions of this script must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# -----------------------------------------------------------------------------
#####################
# Globals
#####################
SCRIPT=`basename $0`
noSB= #flag: if set, do not output subnet or bcast ips. (-a)
details= #flag: if set, display subnet information. (-d)
noList= #flag: if set, do not output address list (-n)
#inaddr= #host ip part of cidr array
#subIP= #subnet ip array
#brIP= #broadcast ip array
#subMask= #subnet mask array
#invMask= #inverted subnet mask array
declare -a -i inaddr subIP brIP subMask invMask
declare -i ipcount
#####################
# Functions
#####################
function helpMsg() {
echo -e "\n $SCRIPT, a IPV4 subnet tool." >&2
echo " Use: $SCRIPT [-a -d -h] cidr [cidr]..." >&2
echo " -a = no subnet or broadcast ip in output" >&2
echo " -d = describe the subnet" >&2
echo " -n = do not output addresses" >&2
echo -e " -h = this help message\n" >&2
exit 2
}
# Check for a properly formatted cidr string. Requires cidr passes as parameter.
#Return false if invalid, true if valid.
function checkCidr() {
if [[ ! $1 =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]{1,2}$ ]]; then
echo "ERROR: Invalid cidr string $1" >&2
return `false`
fi
local arr=($(echo $cidr | tr '/' "\n"))
local arr2=($(echo ${arr[0]} | tr '.' "\n"))
local x
local y=1
for (( x = 0; x &2
return `false`
fi
(( y++ ))
}
if [ ${arr[1]} -lt 8 ] || [ ${arr[1]} -gt 32 ]; then
echo "ERROR: cidr subnet value out of rance, must be between 8 and 32." >&2
return `false`
fi
return `true`
}
# Split an IP in dotted decimal notation into an array. Requires parameter
# with address to split, updates global inaddr[] array.
function splitIP {
(( inaddr[0] = inaddr[1] = inaddr[2] = inaddr[3] = 0 ))
inaddr=($(echo $1 | tr '.' "\n"))
}
# Generate a subnet mask from the cidr bit count. Requires bit count passed
# to function. Updates global sub[] array.
function genMaskFromBitcount() {
local c=0
local m=$1
(( sub[0] = sub[1] = sub[2] = sub[3] = 0 ))
while [ $m -gt 0 ]; do
if [ $m -ge 8 ]; then
let sub[$c]=255
let m=m-8
else
let m=8-m
let m=256-2**$m
let sub[$c]=$m
let m=0
fi
let c=c+1
done
}
# Calculate the address of the subnet (base addrs ess) by doing a bitwise
# AND of the supplied ip (in inaddr[]) and the subnet mask in sub[].
# Updates global array subIP[].
function calcSubnetAddress() {
local x
for (( x=0 ; x = 0 ; x-- ));do
ipcount+=$(( invMask[$x] * 256**$y ))
let y=y+1
done
ipcount=ipcount+1
}
# Echo the current value of subIP[] to stdout.
function echoIP() {
echo "${subIP[0]}.${subIP[1]}.${subIP[2]}.${subIP[3]}"
}
# Increment the IP address in subIP[] by one. Used when generating address list.
function incrementIP() {
local x=3
while [ $x -ge 0 ];do
(( subIP[$x]++ ))
if [ ${subIP[$x]} -lt 256 ]; then
x=-1
else
(( subIP[$x]=0 ))
let x=x-1
fi
done
}
######################
# Main Program
######################
while getopts ":adnh" opt; do
case $opt in
a)
noSB='t'
;;
d)
details='t'
;;
n)
noList='t'
;;
h)
helpMsg;
;;
*)
echo -e "\n ****** Invalid Option: -$OPTARG ******" >&2
helpMsg;
;;
esac
done
shift $((OPTIND-1))
for cidr in $@; do
if ( ! checkCidr $cidr ); then
echo "Not processing string $cidr" >&2
continue
fi
x=
# split the cidr into ip and bit count in x[].
x=($(echo $cidr | tr '/' "\n"))
splitIP ${x[0]}
genMaskFromBitcount ${x[1]}
calcSubnetAddress
invertMask
calcBroadcast
countIP
if [ -n "$details" ] || [ -n "$noList" ]; then
echo "Processing CIDR: $cidr"
echo "Subnet Mask: ${sub[0]}.${sub[1]}.${sub[2]}.${sub[3]}"
echo "Subnet IP: ${subIP[0]}.${subIP[1]}.${subIP[2]}.${subIP[3]}"
echo "Broadcast IP: ${brIP[0]}.${brIP[1]}.${brIP[2]}.${brIP[3]}"
echo "Number of IPs: $ipcount"
fi
if [ -z "$noList" ]; then
#Output the addresses. Strip subnet and broadcast if -a option.
nosub=-1
nobroad=-1
if [ -n "$noSB" ]; then
nosub=0
nobroad=$(( ipcount - 1 ))
fi
for (( x = 0; x < $ipcount ; x++ )) {
if [ $x -ne $nosub ] && [ $x -ne $nobroad ]; then
echoIP
fi
incrementIP
}
fi
done
Till Next Time,