18

In a bash script I have an IP address like 192.168.1.15 and a netmask like 255.255.0.0. I now want to calculate the start address of this network, that means using the &-operator on both addresses. In the example, the result would be 192.168.0.0. Does someone have something like this ready? I'm looking for an elegant way to deal with ip addresses from bash

Christian
  • 2,903
  • 4
  • 31
  • 34
  • 2
    You may consider `ipcalc`. – dwalter Mar 15 '13 at 10:24
  • ipcalc is a perl cgi script that can be downloaded. Thanks, that is a great source for anyone who wants to implement it in PERL. I was looking for a solution in bash though, but perl would work in a shell script, too. – Christian Mar 15 '13 at 10:36
  • 1
    no I mean the unix command line to ipcalc. see http://answers.oreilly.com/topic/411-how-to-calculate-subnets-with-ipcalc/ for more information. As an alternative you could also use sipcalc (also a cli tool) which supports IPv6 – dwalter Mar 15 '13 at 10:37
  • I would have added this as a comment if I had the rep... Usage for cevings answer... # usage: netmask #bits # eg. netmask 24 => 255.255.255.0 # usage: broadcast address mask # eg. broadcast 192.168.0.1 24 => 192.168.0.255 # usage: network address mask # eg. network 192.168.0.123 24 => 192.168.0.0 – Andrew Sharpe Apr 23 '18 at 04:25

7 Answers7

45

Use bitwise & (AND) operator:

$ IFS=. read -r i1 i2 i3 i4 <<< "192.168.1.15"
$ IFS=. read -r m1 m2 m3 m4 <<< "255.255.0.0"
$ printf "%d.%d.%d.%d\n" "$((i1 & m1))" "$((i2 & m2))" "$((i3 & m3))" "$((i4 & m4))"
192.168.0.0

Example with another IP and mask:

$ IFS=. read -r i1 i2 i3 i4 <<< "10.0.14.97"
$ IFS=. read -r m1 m2 m3 m4 <<< "255.255.255.248"
$ printf "%d.%d.%d.%d\n" "$((i1 & m1))" "$((i2 & m2))" "$((i3 & m3))" "$((i4 & m4))"
10.0.14.96
kamituel
  • 34,606
  • 6
  • 81
  • 98
16

Some Bash functions summarizing all other answers.

ip2int()
{
    local a b c d
    { IFS=. read a b c d; } <<< $1
    echo $(((((((a << 8) | b) << 8) | c) << 8) | d))
}

int2ip()
{
    local ui32=$1; shift
    local ip n
    for n in 1 2 3 4; do
        ip=$((ui32 & 0xff))${ip:+.}$ip
        ui32=$((ui32 >> 8))
    done
    echo $ip
}

netmask()
# Example: netmask 24 => 255.255.255.0
{
    local mask=$((0xffffffff << (32 - $1))); shift
    int2ip $mask
}


broadcast()
# Example: broadcast 192.0.2.0 24 => 192.0.2.255
{
    local addr=$(ip2int $1); shift
    local mask=$((0xffffffff << (32 -$1))); shift
    int2ip $((addr | ~mask))
}

network()
# Example: network 192.0.2.0 24 => 192.0.2.0
{
    local addr=$(ip2int $1); shift
    local mask=$((0xffffffff << (32 -$1))); shift
    int2ip $((addr & mask))
}
r2evans
  • 141,215
  • 6
  • 77
  • 149
ceving
  • 21,900
  • 13
  • 104
  • 178
  • 6
    Nice but an example line would have saved me 15mins of figuring out your parameter expectations.. "network 192.168.8.1 31" or "netmask 16" – A. Binzxxxxxx May 11 '16 at 17:07
  • So be it, back to the way it was. – r2evans Apr 27 '20 at 13:56
  • in broadcast and network functions there are two arguments. Shouldn't the bit `32 - $1` be `32 - $2` instead? I added the possibility of use cidr notation with something like `[ $# -eq 1 ] && echo "$1" | grep -E '[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}/[0-9]{1,2}' && IFS=/ read -ra cidr <<< "$1"` to be able to use it with the output of `ip -o -f inet addr show dev enp3s0 | awk '/scope global/ {print $4}'` – badc0de Feb 08 '22 at 12:03
  • @badc0de The call of `shift` makes `$2` to `$1`. – ceving Feb 08 '22 at 12:19
  • Ah! I missed that. I have my own version of the script and at some point I must have changed that for clarity. Thanks anyway for your answer and code – badc0de Feb 09 '22 at 09:22
10

Just adding an alternative if you have only network prefix available (no netmask):

IP=10.20.30.240
PREFIX=26
IFS=. read -r i1 i2 i3 i4 <<< $IP
IFS=. read -r xx m1 m2 m3 m4 <<< $(for a in $(seq 1 32); do if [ $(((a - 1) % 8)) -eq 0 ]; then echo -n .; fi; if [ $a -le $PREFIX ]; then echo -n 1; else echo -n 0; fi; done)
printf "%d.%d.%d.%d\n" "$((i1 & (2#$m1)))" "$((i2 & (2#$m2)))" "$((i3 & (2#$m3)))" "$((i4 & (2#$m4)))"
Janci
  • 101
  • 1
  • 3
3

For people who hit this while googling and need an answer that works in ash, the sh that's included in BusyBox and therefore on many routers, here's something for that case:

IP=10.20.30.240
MASK=255.255.252.0
IFS=. read -r i1 i2 i3 i4 << EOF
$IP
EOF
IFS=. read -r m1 m2 m3 m4 << EOF
$MASK
EOF
read masked << EOF
$(( $i1 & $m1 )).$(( $i2 & $m2 )).$(( $i3 & $m3 )).$(( $i4 & $m4 ))
EOF
echo $masked

And here's what to do if you only have the prefix length:

IP=10.20.30.240
PREFIX=22
IFS=. read -r i1 i2 i3 i4 << EOF
$IP
EOF
mask=$(( ((1<<32)-1) & (((1<<32)-1) << (32 - $PREFIX)) ))
read masked << EOF
$(( $i1 & ($mask>>24) )).$(( $i2 & ($mask>>16) )).$(( $i3 & ($mask>>8) )).$(( $i4 & $mask ))
EOF
echo $masked
Daniel Martin
  • 23,083
  • 6
  • 50
  • 70
1

Great answer, though minor typo in answer above.

$ printf "%d.%d.%d.%d\n" "$((i1 & m1))" "$(($i2  <-- $i2 should be i2

If anyone knows how to calculate the broadcast address (XOR the network), then calculate the usable nodes between network and broadcast I'd be interested in those next steps. I have to find addresses in a list within a /23.

user3126740
  • 121
  • 1
  • 1
  • 8
0

In addition to @Janci answer

IP=10.20.30.240
PREFIX=26
IFS=. read -r i1 i2 i3 i4 <<< $IP
D2B=({0..1}{0..1}{0..1}{0..1}{0..1}{0..1}{0..1}{0..1})
binIP=${D2B[$i1]}${D2B[$i2]}${D2B[$i3]}${D2B[$i4]}
binIP0=${binIP::$PREFIX}$(printf '0%.0s' $(seq 1 $((32-$PREFIX))))
# binIP1=${binIP::$PREFIX}$(printf '0%.0s' $(seq 1 $((31-$PREFIX))))1
echo $((2#${binIP0::8})).$((2#${binIP0:8:8})).$((2#${binIP0:16:8})).$((2#${binIP0:24:8}))
Mikhail
  • 71
  • 8
0

Based on https://stackoverflow.com/a/64877749/2716218 I tried to simplify and came up with

IP=10.20.30.240
PREFIX=22
IFS=. read -r i1 i2 i3 i4 <<< $IP
mask=$(( ((1<<32)-1) & (((1<<32)-1) << (32 - $PREFIX)) ))
masked=$(( $i1 & ($mask>>24) )).$(( $i2 & ($mask>>16) )).$(( $i3 & ($mask>>8) )).$(( $i4 & $mask ))
echo $masked
NiKiZe
  • 1,256
  • 10
  • 26