26

I'm trying to create script that I can input a set of prefixes, which will then list all IP addresses within the prefixes (including network/host/broadcast).

An example would be:

./convert-prefix-to-IPs.sh 192.168.0.0/23 203.20.0.0/16
192.168.0.0
192.168.0.1
... 
192.168.0.255
192.168.1.0
.. 
192.168.1.255
203.20.0.0
..
203.20.255.255

There are some python/perl scripts which can do this, but I'm hoping to have a simple bash script, as it may be used on systems without perl/python (yes.. i know.. )

Fredrik Pihl
  • 44,604
  • 7
  • 83
  • 130
user2463938
  • 369
  • 1
  • 3
  • 4
  • 7
    You can use bash brace expansion, for example try `echo 192.168.{1..255}.{1..255}` and calculate the ranges to use from the netmask – Jacopofar Jun 07 '13 at 14:51
  • Though that seems simple, you might end up with a more complex script. I think a simple loop with some basic math should do the trick. – Karoly Horvath Jun 07 '13 at 16:07
  • 1
    `echo 192.168.{1..255}.{1..255} | tr ' ' '\012'` will put them on separate lines. Good luck to all. – shellter Jun 07 '13 at 16:58

11 Answers11

60

Here is what I use to generate all the IP addresses in a given CIDR block

nmap -sL -n 10.10.64.0/27 | awk '/Nmap scan report/{print $NF}'

From the nmap man page, the flags are:

-sL: List Scan - simply list targets to scan
-n: Never do DNS resolution

Just that simple

The above command outputs this

10.10.64.0
10.10.64.1
10.10.64.2
10.10.64.3
10.10.64.4
10.10.64.5
10.10.64.6
10.10.64.7
10.10.64.8
10.10.64.9
10.10.64.10
10.10.64.11
10.10.64.12
10.10.64.13
10.10.64.14
10.10.64.15
10.10.64.16
10.10.64.17
10.10.64.18
10.10.64.19
10.10.64.20
10.10.64.21
10.10.64.22
10.10.64.23
10.10.64.24
10.10.64.25
10.10.64.26
10.10.64.27
10.10.64.28
10.10.64.29
10.10.64.30
10.10.64.31
datu-puti
  • 1,306
  • 14
  • 33
John Vossler
  • 601
  • 5
  • 2
23

I too was looking for this solution and found that @scherand script worked great. I also have added to this script to give you more option. Help File below.

THIS SCRIPT WILL EXPAND A CIDR ADDRESS.

SYNOPSIS

./cidr-to-ip.sh [OPTION(only one)] [STRING/FILENAME]

DESCRIPTION

-h Displays this help screen

-f Forces a check for network boundary when given a STRING(s)

-i Will read from an Input file (file should contain one CIDR per line) (no network boundary check)

-b Will do the same as –i but with network boundary check

EXAMPLES

./cidr-to-ip.sh 192.168.0.1/24

./cidr-to-ip.sh 192.168.0.1/24 10.10.0.0/28

./cidr-to-ip.sh -f 192.168.0.0/16

./cidr-to-ip.sh -i inputfile.txt

./cidr-to-ip.sh -b inputfile.txt

#!/bin/bash    

############################
##  Methods
############################   
prefix_to_bit_netmask() {
    prefix=$1;
    shift=$(( 32 - prefix ));

    bitmask=""
    for (( i=0; i < 32; i++ )); do
        num=0
        if [ $i -lt $prefix ]; then
            num=1
        fi

        space=
        if [ $(( i % 8 )) -eq 0 ]; then
            space=" ";
        fi

        bitmask="${bitmask}${space}${num}"
    done
    echo $bitmask
}

bit_netmask_to_wildcard_netmask() {
    bitmask=$1;
    wildcard_mask=
    for octet in $bitmask; do
        wildcard_mask="${wildcard_mask} $(( 255 - 2#$octet ))"
    done
    echo $wildcard_mask;
}

check_net_boundary() {
    net=$1;
    wildcard_mask=$2;
    is_correct=1;
    for (( i = 1; i <= 4; i++ )); do
        net_octet=$(echo $net | cut -d '.' -f $i)
        mask_octet=$(echo $wildcard_mask | cut -d ' ' -f $i)
        if [ $mask_octet -gt 0 ]; then
            if [ $(( $net_octet&$mask_octet )) -ne 0 ]; then
                is_correct=0;
            fi
        fi
    done
    echo $is_correct;
}

#######################
##  MAIN
#######################
OPTIND=1;
getopts "fibh" force;

shift $((OPTIND-1))
if [ $force = 'h' ]; then
    echo ""
    echo -e "THIS SCRIPT WILL EXPAND A CIDR ADDRESS.\n\nSYNOPSIS\n  ./cidr-to-ip.sh [OPTION(only one)] [STRING/FILENAME]\nDESCRIPTION\n -h  Displays this help screen\n -f  Forces a check for network boundary when given a STRING(s)\n    -i  Will read from an Input file (no network boundary check)\n  -b  Will do the same as –i but with network boundary check\n\nEXAMPLES\n    ./cidr-to-ip.sh  192.168.0.1/24\n   ./cidr-to-ip.sh  192.168.0.1/24 10.10.0.0/28\n  ./cidr-to-ip.sh  -f 192.168.0.0/16\n    ./cidr-to-ip.sh  -i inputfile.txt\n ./cidr-to-ip.sh  -b inputfile.txt\n"
    exit
fi

if [ $force = 'i' ] || [ $force = 'b' ]; then

    old_IPS=$IPS
    IPS=$'\n'
    lines=($(cat $1)) # array
    IPS=$old_IPS
        else
            lines=$@
fi

for ip in ${lines[@]}; do
    net=$(echo $ip | cut -d '/' -f 1);
    prefix=$(echo $ip | cut -d '/' -f 2);
    do_processing=1;

    bit_netmask=$(prefix_to_bit_netmask $prefix);

    wildcard_mask=$(bit_netmask_to_wildcard_netmask "$bit_netmask");
    is_net_boundary=$(check_net_boundary $net "$wildcard_mask");

    if [ $force = 'f' ] && [ $is_net_boundary -ne 1 ] || [ $force = 'b' ] && [ $is_net_boundary -ne 1 ] ; then
        read -p "Not a network boundary! Continue anyway (y/N)? " -n 1 -r
        echo    ## move to a new line
        if [[ $REPLY =~ ^[Yy]$ ]]; then
            do_processing=1;
        else
            do_processing=0;
        fi
    fi  

    if [ $do_processing -eq 1 ]; then
        str=
        for (( i = 1; i <= 4; i++ )); do
            range=$(echo $net | cut -d '.' -f $i)
            mask_octet=$(echo $wildcard_mask | cut -d ' ' -f $i)
            if [ $mask_octet -gt 0 ]; then
                range="{$range..$(( $range | $mask_octet ))}";
            fi
            str="${str} $range"
        done
        ips=$(echo $str | sed "s, ,\\.,g"); ## replace spaces with periods, a join...

        eval echo $ips | tr ' ' '\n'
else
exit
    fi

done
Kyoungs
  • 231
  • 2
  • 4
12

nmap is useful, but an overkill.

You can use prips instead. Saves you the hassle of grepping out the extra output from nmap and using awk.

Calling prips 192.168.0.0/23 will print what you need.

I use the following to skip the network address and broadcast: prips "$subnet" | sed -e '1d; $d'

Prips also has other useful options, e.g. being able to sample every n-th IP.

It's available via apt,brew,rpm and as tar.gz.

Jakub Bochenski
  • 3,113
  • 4
  • 33
  • 61
  • 2
    See similar: [`cidrl`](https://github.com/emden-norfolk/cidrl), which also works with IPv6. – Damien Jan 19 '23 at 15:18
11

This short script will print all the IP addresses in a CIDR range in a few lines of Bash. (I named it prips after the Ubuntu command of the same name. Obviously, if that command is available, use that.)

prips() {
  local cidr="$1" ; local lo hi a b c d e f g h

  # range is bounded by network (-n) & broadcast (-b) addresses.
  lo="$(ipcalc -n "$cidr" | cut -f2 -d=)"
  hi="$(ipcalc -b "$cidr" | cut -f2 -d=)"

  IFS=. read -r a b c d <<< "$lo"
  IFS=. read -r e f g h <<< "$hi"

  eval "echo {$a..$e}.{$b..$f}.{$c..$g}.{$d..$h}"
}

Note that I assume the RedHat Linux (Erik Troan, Preston Brown) version of ipcalc, not the Krischan Jodies version that is installed on some platforms (e.g. Mac OS X).

Examples:

$ prips 10.0.0.128/27
10.0.0.128 10.0.0.129 10.0.0.130 10.0.0.131 10.0.0.132 10.0.0.133 10.0.0.134 10.0.0.135 10.0.0.136 10.0.0.137 10.0.0.138 10.0.0.139 10.0.0.140 10.0.0.141 10.0.0.142 10.0.0.143 10.0.0.144 10.0.0.145 10.0.0.146 10.0.0.147 10.0.0.148 10.0.0.149 10.0.0.150 10.0.0.151 10.0.0.152 10.0.0.153 10.0.0.154 10.0.0.155 10.0.0.156 10.0.0.157 10.0.0.158 10.0.0.159

Calculates correct number of addresses in a /23 networks:

$ prips 10.0.0.0/23 | wc -w 
512

Inspecting a few of those addresses using cut:

$ prips 10.0.0.0/23 | cut -f1-10,256-266 -d' '
10.0.0.0 10.0.0.1 10.0.0.2 10.0.0.3 10.0.0.4 10.0.0.5 10.0.0.6 10.0.0.7 10.0.0.8 10.0.0.9 10.0.0.255 10.0.1.0 10.0.1.1 10.0.1.2 10.0.1.3 10.0.1.4 10.0.1.5 10.0.1.6 10.0.1.7 10.0.1.8 10.0.1.9

And maybe too slow but also correctly generates the 16 million addresses in a /8 network:

$ date ; prips 10.0.0.0/8 | wc -w ; date 
Sat May 20 18:06:00 AEST 2017
16777216
Sat May 20 18:06:41 AEST 2017
Alex Harvey
  • 14,494
  • 5
  • 61
  • 97
  • 1
    Very clever! Generally I trust the venerable nmap, but this is nice if you don't have it! Short and sweet. – Mike S Jun 27 '18 at 18:13
  • 1
    particularly useful in enterprise environs where one has redhat but definitely not nmap! – volvox Mar 29 '19 at 15:05
7

I recently wrote a function to generate all IP addresses from a given network address. The function takes the network address as argument and accepts CIDR and subnet masks. The script then stores all IPs in the array variable $ips.

Code

network_address_to_ips() {
  # create array containing network address and subnet
  local network=(${1//\// })
  # split network address by dot
  local iparr=(${network[0]//./ })
  # if no mask given it's the same as /32
  local mask=32
  [[ $((${#network[@]})) -gt 1 ]] && mask=${network[1]}

  # convert dot-notation subnet mask or convert CIDR to an array like (255 255 255 0)
  local maskarr
  if [[ ${mask} =~ '.' ]]; then  # already mask format like 255.255.255.0
    maskarr=(${mask//./ })
  else                           # assume CIDR like /24, convert to mask
    if [[ $((mask)) -lt 8 ]]; then
      maskarr=($((256-2**(8-mask))) 0 0 0)
    elif  [[ $((mask)) -lt 16 ]]; then
      maskarr=(255 $((256-2**(16-mask))) 0 0)
    elif  [[ $((mask)) -lt 24 ]]; then
      maskarr=(255 255 $((256-2**(24-mask))) 0)
    elif [[ $((mask)) -lt 32 ]]; then
      maskarr=(255 255 255 $((256-2**(32-mask))))
    elif [[ ${mask} == 32 ]]; then
      maskarr=(255 255 255 255)
    fi
  fi

  # correct wrong subnet masks (e.g. 240.192.255.0 to 255.255.255.0)
  [[ ${maskarr[2]} == 255 ]] && maskarr[1]=255
  [[ ${maskarr[1]} == 255 ]] && maskarr[0]=255

  # generate list of ip addresses
  local bytes=(0 0 0 0)
  for i in $(seq 0 $((255-maskarr[0]))); do
    bytes[0]="$(( i+(iparr[0] & maskarr[0]) ))"
    for j in $(seq 0 $((255-maskarr[1]))); do
      bytes[1]="$(( j+(iparr[1] & maskarr[1]) ))"
      for k in $(seq 0 $((255-maskarr[2]))); do
        bytes[2]="$(( k+(iparr[2] & maskarr[2]) ))"
        for l in $(seq 1 $((255-maskarr[3]))); do
          bytes[3]="$(( l+(iparr[3] & maskarr[3]) ))"
          printf "%d.%d.%d.%d\n" "${bytes[@]}"
        done
      done
    done
  done
}

Example

network_address_to_ips 10.0.1.0/255.255.255.240
network_address_to_ips 10.1.0.0/24
Florian Feldhaus
  • 5,567
  • 2
  • 38
  • 46
  • `line 14: 256-2**(8-24): exponent less than 0 (error token is ")")` `GNU bash, version 4.2.37` – lifeofguenter Apr 05 '15 at 06:03
  • Please post the function call you are using where the error occurs. The `if` statement `if [[ $((8-${network[1]})) > 0 ]]; then` should ensure that the exponent is always greater than 0. For me the example statements run fine with Bash 4.3.30 – Florian Feldhaus Apr 09 '15 at 05:55
  • @Florian I'm having the same problem as @lifeofguenter. Command is: `network_address_to_ips 10.1.0.0/30`. Bash 4.3. – niieani May 01 '16 at 23:41
  • Even though it does work for me in Bash 4.3 on Mac OS X, I found out that `>` (alphabetical comparison) should be `-gt` (numerical comparison). I changed it. Can you try again? – Florian Feldhaus May 02 '16 at 04:51
  • 1
    This is a fantastic solution bc it's very compact and works with CIDR and netmasks. That said, `shellcheck` turned up a bunch of issues, and I also simplified things like `[[ $((8-${network[1]})) -gt 0 ]]` into [[ $((network[1])) -gt 8 ]]` see: https://gist.github.com/thom-nic/2556a6cc3865fba6330f61b802438c05 – thom_nic Dec 11 '20 at 03:15
7

This script should do. It's (almost) pure Bash. The seq part can be replaced if a completely pure bash is required.

Since Bash apparently uses signed two-complement 4-byte integers, the script is limited to /8 mask maximum. I found ranges larger than /16 impractical anyway so this doesn't bother me at all. If someone knows a simple way to overcome this, please share :)

#!/usr/bin/env bash

BASE_IP=${1%/*}
IP_CIDR=${1#*/}

if [ ${IP_CIDR} -lt 8 ]; then
    echo "Max range is /8."
    exit
fi

IP_MASK=$((0xFFFFFFFF << (32 - ${IP_CIDR})))

IFS=. read a b c d <<<${BASE_IP}

ip=$((($b << 16) + ($c << 8) + $d))

ipstart=$((${ip} & ${IP_MASK}))
ipend=$(((${ipstart} | ~${IP_MASK}) & 0x7FFFFFFF))

seq ${ipstart} ${ipend} | while read i; do
    echo $a.$((($i & 0xFF0000) >> 16)).$((($i & 0xFF00) >> 8)).$(($i & 0x00FF))
done

Usage:

./script.sh 192.168.13.55/22

Tested with Bash version 4.4.23. YMMV.

U. Windl
  • 3,480
  • 26
  • 54
jficz
  • 271
  • 3
  • 8
2

I think this little script I hacked together does the trick. If not, it's definitely a starting point! Good luck.

#!/bin/bash                                                                                                                                                                                                                                                              

############################                                                                                                                                                                                                                                             
##  Methods                                                                                                                                                                                                                                                              
############################                                                                                                                                                                                                                                             
prefix_to_bit_netmask() {
    prefix=$1;
    shift=$(( 32 - prefix ));

    bitmask=""
    for (( i=0; i < 32; i++ )); do
        num=0
        if [ $i -lt $prefix ]; then
            num=1
        fi

        space=
        if [ $(( i % 8 )) -eq 0 ]; then
            space=" ";
        fi

        bitmask="${bitmask}${space}${num}"
    done
    echo $bitmask
}

bit_netmask_to_wildcard_netmask() {
    bitmask=$1;
    wildcard_mask=
    for octet in $bitmask; do
        wildcard_mask="${wildcard_mask} $(( 255 - 2#$octet ))"
    done
    echo $wildcard_mask;
}



#######################                                                                                                                                                                                                                                                  
##  MAIN                                                                                                                                                                                                                                                                 
#######################                                                                                                                                                                                                                                                  
for ip in $@; do
    net=$(echo $ip | cut -d '/' -f 1);
    prefix=$(echo $ip | cut -d '/' -f 2);

    bit_netmask=$(prefix_to_bit_netmask $prefix);

    wildcard_mask=$(bit_netmask_to_wildcard_netmask "$bit_netmask");

    str=
    for (( i = 1; i <= 4; i++ )); do
        range=$(echo $net | cut -d '.' -f $i)
        mask_octet=$(echo $wildcard_mask | cut -d ' ' -f $i)
        if [ $mask_octet -gt 0 ]; then
            range="{0..$mask_octet}";
        fi
        str="${str} $range"
    done
    ips=$(echo $str | sed "s, ,\\.,g"); ## replace spaces with periods, a join...                                                                                                                                                                                        
    eval echo $ips | tr ' ' '\012'

done
rberg
  • 61
  • 1
  • 1
  • 3
  • `[[` is a Bash keyword similar to (but more powerful than) the `[` command. See [Bash FAQ 31](http://mywiki.wooledge.org/BashFAQ/031) and [Tests And Conditionals](http://mywiki.wooledge.org/BashGuide/TestsAndConditionals). Unless you're writing for POSIX sh, I recommend `[[`. – Rany Albeg Wein Dec 31 '18 at 00:20
1

I have extended @rberg script a little.

  • check if the "network" you provide really is a network (use -f to skip the check)
  • handle netmasks greater than /24

Maybe this is of use for someone.

#!/bin/bash

############################
##  Methods
############################   
prefix_to_bit_netmask() {
    prefix=$1;
    shift=$(( 32 - prefix ));

    bitmask=""
    for (( i=0; i < 32; i++ )); do
        num=0
        if [ $i -lt $prefix ]; then
            num=1
        fi

        space=
        if [ $(( i % 8 )) -eq 0 ]; then
            space=" ";
        fi

        bitmask="${bitmask}${space}${num}"
    done
    echo $bitmask
}

bit_netmask_to_wildcard_netmask() {
    bitmask=$1;
    wildcard_mask=
    for octet in $bitmask; do
        wildcard_mask="${wildcard_mask} $(( 255 - 2#$octet ))"
    done
    echo $wildcard_mask;
}

check_net_boundary() {
    net=$1;
    wildcard_mask=$2;
    is_correct=1;
    for (( i = 1; i <= 4; i++ )); do
        net_octet=$(echo $net | cut -d '.' -f $i)
        mask_octet=$(echo $wildcard_mask | cut -d ' ' -f $i)
        if [ $mask_octet -gt 0 ]; then
            if [ $(( $net_octet&$mask_octet )) -ne 0 ]; then
                is_correct=0;
            fi
        fi
    done
    echo $is_correct;
}

#######################
##  MAIN
#######################
OPTIND=1;
getopts "f" force;
shift $(( OPTIND-1 ));

for ip in $@; do
    net=$(echo $ip | cut -d '/' -f 1);
    prefix=$(echo $ip | cut -d '/' -f 2);
    do_processing=1;

    bit_netmask=$(prefix_to_bit_netmask $prefix);

    wildcard_mask=$(bit_netmask_to_wildcard_netmask "$bit_netmask");
    is_net_boundary=$(check_net_boundary $net "$wildcard_mask");

    if [ $force != 'f' ] && [ $is_net_boundary -ne 1 ]; then
        read -p "Not a network boundary! Continue anyway (y/N)? " -n 1 -r
        echo    ## move to a new line
        if [[ $REPLY =~ ^[Yy]$ ]]; then
            do_processing=1;
        else
            do_processing=0;
        fi
    fi  

    if [ $do_processing -eq 1 ]; then
        str=
        for (( i = 1; i <= 4; i++ )); do
            range=$(echo $net | cut -d '.' -f $i)
            mask_octet=$(echo $wildcard_mask | cut -d ' ' -f $i)
            if [ $mask_octet -gt 0 ]; then
                range="{$range..$(( $range | $mask_octet ))}";
            fi
            str="${str} $range"
        done
        ips=$(echo $str | sed "s, ,\\.,g"); ## replace spaces with periods, a join...

        eval echo $ips | tr ' ' '\012'
    fi

done
scherand
  • 2,298
  • 20
  • 27
1

Wanted to comment on an answer above but don't have the rep yet.

Using the top solution with NMAP I added this to my .bashrc

expand-ip() {
  nmap -sL -n -iL "$1" | awk '/Nmap scan report/{print $NF}'
}

Now I can use this with just expand-ip targs.

Alex Harvey
  • 14,494
  • 5
  • 61
  • 97
0

You can use this script
(you need to have "bc" installed on your system):

for ip in $@ ;do
        net=$(echo $ip | cut -d '/' -f 1);
        prefix=$(echo $ip | cut -d '/' -f 2);
        o1=$(echo $net | cut -d '.' -f4);
        o2=$(echo $net | cut -d '.' -f3);
        o3=$(echo $net | cut -d '.' -f2);
        o4=$(echo $net | cut -d '.' -f1);
        len=$(echo "2^(32 - $prefix)"|bc);
        for i in `seq $len`;do
                echo "$o4.$o3.$o2.$o1";
                o1=$(echo "$o1+1"|bc);
                if [ $o1 -eq 256 ]; then
                        o1=0;
                        o2=$(echo "$o2+1"|bc);
                        if [ $o2 -eq 256 ]; then
                                o2=0;
                                o3=$(echo "$o3+1"|bc);
                                if [ $o3 -eq 256 ]; then
                                        o3=0;
                                        o4=$(echo "$o4+1"|bc);
                                fi
                        fi
                fi
        done
done
Alireza
  • 71
  • 1
  • 4
  • "Double quote" every literal that contains spaces/metacharacters and **every** expansion: `"$var"`, `"$(command "$var")"`, `"${array[@]}"`, `"a & b"`. See http://mywiki.wooledge.org/Quotes, http://mywiki.wooledge.org/Arguments and http://wiki.bash-hackers.org/syntax/words . – Rany Albeg Wein Dec 31 '18 at 00:21
0
fping -Aaqgr 1 10.1.1.0/24

Simplicity works best

George
  • 6,006
  • 6
  • 48
  • 68
  • 1
    This will actually ping each IP in the subnet. Not what the question is asking. Only list. If this was a /23 or larger subnet, this will take a while. – R J Feb 28 '19 at 14:24