13

There is IP address: 66.102.13.19, and from this address as that received this address

http://1113984275

But how? And how I can make this with the help of bash. For example, this service can do it, but I do not understand the algorithm.

KarlsD
  • 649
  • 1
  • 6
  • 12

7 Answers7

39
ip=66.102.13.19
IFS=. read -r a b c d <<< "$ip"
printf '%s%d\n' "http://" "$((a * 256 ** 3 + b * 256 ** 2 + c * 256 + d))"

By the way, the number in your question doesn't match the IP address.

To convert a decimal to an IP:

#!/bin/bash
dec2ip () {
    local ip delim dec=$@
    for e in {3..0}
    do
        ((octet = dec / (256 ** e) ))
        ((dec -= octet * 256 ** e))
        ip+=$delim$octet
        delim=.
    done
    printf '%s\n' "$ip"
}

dec2ip "$@"

To convert an IP to a decimal:

#!/bin/bash
ip2dec () {
    local a b c d ip=$@
    IFS=. read -r a b c d <<< "$ip"
    printf '%d\n' "$((a * 256 ** 3 + b * 256 ** 2 + c * 256 + d))"
}

ip2dec "$@"

Demo:

$ ./dec2ip 1113984275
66.102.13.19

$ ./ip2dec 66.102.13.19
1113984275

These two scripts rely on features of Bash that aren't present in some Bourne-derived shells. Here are AWK versions to use instead:

#!/usr/bin/awk -f
# dec2ip
BEGIN {
    dec = ARGV[1]
    for (e = 3; e >= 0; e--) {
        octet = int(dec / (256 ^ e))
        dec -= octet * 256 ^ e
        ip = ip delim octet
        delim = "."
    }
    printf("%s\n", ip)
}

and

#!/usr/bin/awk -f
# ip2dec
BEGIN {
    ip = ARGV[1]
    split(ip, octets, ".")
    for (i = 1; i <= 4; i++) {
        dec += octets[i] * 256 ** (4 - i)
    }
    printf("%i\n", dec)
}

They can be called in the same manner as the Bash scripts above.

Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • @AshBurlaczenko: The decimal number for the given IP address is 1113984275 – Dennis Williamson May 26 '12 at 17:43
  • Thank you for your decision, more clear, I corrected the error. And how do the inverse operation? From the 1113984275 to get 66.102.13.19. – KarlsD May 26 '12 at 17:54
  • @ArmenB.: I'm not sure what you're asking, but yes, typically anything which can be done in a shell script can be done at the command line and vice versa. There are exceptions or cases where things have to be done a little differently. In this case, you could include the functions (just the function definition omitting the shebang and the call to the function) in a file such as `~/bin/functions` and source that from your `~/.bashrc` using `. $HOME/bin/functions` (notice the dot). Then you could call the functions from the command line like this: `ip2dec 66.102.13.19`, for example. – Dennis Williamson Sep 06 '13 at 06:16
  • If you mean a different shell other than Bash, then yes, but it might have to be rewritten if the other shell doesn't have the same features as Bash which are used here. – Dennis Williamson Sep 06 '13 at 06:18
  • @Dennis: Yes by other other shell I meant `sh` not `bash`. I'm looking for a way to do this is vanilla shell. – ArmenB Sep 06 '13 at 16:02
  • @ArmenB.: Unfortunately, the Bourne shell doesn't support arrays (although you could use `set` and the positional parameters) and neither it nor `expr` support exponentiation. Also, different versions of Bourne-derived shells support (or not) a variety of other features. So to get something usable, I've added AWK versions. Hopefully they work with the version of AWK you're using. – Dennis Williamson Sep 06 '13 at 16:49
  • I think this would be better-expressed with binary functions (<<) rather than exponents. – Reinderien Feb 11 '15 at 22:50
  • Notice from ip to decimal to init the dec value before the loop, otherwise it continue to increase :) – shacharsol May 21 '15 at 00:21
  • @shacharsol: If you're referring to the AWK version of `ip2dec`, `dec` is initialized automatically to a null value when the `BEGIN` clause first encounters it. If you use it outside `BEGIN` perhaps by defining a function then you probably would need to explicitly initialize it. – Dennis Williamson May 21 '15 at 00:43
  • Multiplication is slow, a faster alternative is to use bit shifting (a one opcode command to most CPUs) `printf '%d\n' "$(( (a<<24) + (b<<16) + (c<<8) + d ))"` –  Sep 09 '19 at 22:43
  • @Isaac: But this isn't C, this is Bash. Did you actually test the difference? For me, in Bash 4.4, my multiplication is about 15% faster than your shifts. `time for i in {1..1000000}; do printf '%s%d\n' "http://" "$((a * 256 ** 3 + b * 256 ** 2 + c * 256 + d))"; done > /dev/null` – Dennis Williamson Sep 09 '19 at 23:21
  • I should have tested longer. Was faster in ksh. Here is a comparison for shells: [Try it online!](https://tio.run/##dVJbboMwEPznFCPLyIaI0pA2rRLSI/QE/QEMAhUMxU4@@jg7tR3yqtSRsHa9u7O7g/NM1dMk@m7faoAHXxjGRuoKzFe@eJMMpNZ62MQxAaGcZwiRPK4RhlhhgfziJsYtZncBEQRkix/PE72qm0pfU5MjNflDDZ6lafIQmGqep@ly7awiTZ@tIXBknORueW/hYYZX9SNUjUYiN9vg3Xyfqt5C9C7HhvtBN720KfOq81jnJAuls1HvKBeZLrGwAqxeWXAOW1DDHRVglrOxdJSr8gOMUElYcMN2golxURZtNpaIBrM9PQ5DAsK27Oyxm0LRyxIviEV5iOW@bS/RUor/R5wFjg4g416eqJ26pi5yCxoZvVMLz1ndjprsozDuQs0XTiLvipj5a4UN/GWi5uPpblX5vnsnRhvbqrOHck1RFnUPwmkXURXEVIXm1xF8Iy8QtTCDeHaK6Rc "Bash – Try It Online") –  Sep 13 '19 at 18:14
  • It shows an small improvement in ksh and zsh, but not so big. In any case, it doesn't work in dash (which I didn't mention before) because dash doesn't have the exponentiation operator (`**`). Maybe you will like the dash compatible version of `d + (c + (b + a * 256)*256)*256` –  Sep 13 '19 at 18:40
  • @Isaac: Wow, zsh is slow! – Dennis Williamson Sep 13 '19 at 19:50
  • You want a Fast shell?: try dash [Try it online!](http://shorturl.at/mLV04) –  Sep 14 '19 at 21:42
  • There's a problem with your ip2dec script. It adds a trailing zero at the end. – madacoda Dec 05 '19 at 18:31
  • @madacoda: Is it the Bash version or the Awk one? I'm not getting one in either version. Can you give me an example IP that results in an extra trailing zero? That bug would require either concatenations (which aren't used in either script) or multiplication (directly or indirectly) by 10 (also not present). I did, however, just discover a bug in my Bash version of `dec2ip`. It adds a leading decimal point on subsequent executions because I didn't do `local delim`. I've edited my answer for that issue. – Dennis Williamson Jun 29 '22 at 16:09
  • I tried but can't replicate the issue anymore. it was 3 years ago anyway so maybe I just dont remember the specifics – madacoda Aug 03 '22 at 14:16
15

IP address -> Number:

echo 66.102.13.19 | tr . '\n' | awk '{s = s*256 + $1} END{print s}'

Number -> IP address:

(export ip=1113984275; for i in {1..4}; do s='.'$((ip%256))$s && ((ip>>=8)); done; echo ${s:1})
Hongbo Liu
  • 2,818
  • 1
  • 24
  • 18
  • That's great, but how do we do it the other way. i.e. IP Number --> IP Address – bsmoo Dec 11 '14 at 16:38
  • 1
    I have update the method of how to convert IP number to IP address. :) – Hongbo Liu Dec 12 '14 at 03:15
  • 2
    You can avoid the call to `tr` by specifying the record separator, ie.: `awk 'BEGIN{RS="."}...` or `awk '{s = s*256 + $1} END{print s}' RS=.` – bufh Mar 14 '16 at 15:31
4

Binary shifting is always faster than multiplying or dividing.
Using a binary AND is faster than mod.

ip2dec(){ # Convert an IPv4 IP number to its decimal equivalent.
          declare -i a b c d;
          IFS=. read a b c d <<<"$*";
          echo "$(((a<<24)+(b<<16)+(c<<8)+d))";
        }
dec2ip(){ # Convert an IPv4 decimal IP value to an IPv4 IP.
          declare -i a=$((0xff)) b=$1; 
          c="$((b>>24&a)).$((b>>16&a)).$((b>>8&a)).$((b&a))";
          echo "$c";
        }

ip=66.102.13.19
a=$(ip2dec "$ip")
b=$(dec2ip "$a")
echo "$ip DecIP=$a IPv4=$b "

Note: This system will fail with values like 0008.
Bash thinks it is an octal number.

To solve that, each value will have to be cleaned with something like:

IsDecInt()(     # Is the value given on $1 a decimal integer (with sign)?
                declare n=$1; set --
                if [[ $n =~ ^([+-]?)((0)|0*([0-9]*))$ ]]; then
                    set -- "${BASH_REMATCH[@]:1}"
                else
                    exit 1
                fi
                echo "$1$3$4";
          )

  a=$(IsDecInt "$a")||{ Echo "Value $a is not an integer" >&2; exit 1; }

For (very) old shells: bash since 2.04, or dash or any (reasonable) shell:

ip2dec(){ # Convert an IPv4 IP number to its decimal equivalent.
          local a b c d;
          IFS=. read a b c d <<-_EOF_
$1
_EOF_
          echo "$(((a<<24)+(b<<16)+(c<<8)+d))";
        }

dec2ip(){   # Convert an IPv4 decimal IP value to an IPv4 IP.
            local a=$((~(-1<<8))) b=$1; 
            c="$((b>>24&a)).$((b>>16&a)).$((b>>8&a)).$((b&a))";
            echo "$c";
        }

ip=$1
a=$(ip2dec "$ip")
b=$(dec2ip "$a")
echo "$ip DecIP=$a IPv4=$b "
  • To cleanup variables `a`, `b`, `c` and `d` from leading `0` and prevent octal interpretation, you can do: `((a=10#$a,b=10#$b,c=10#$c,d=10#$d))`. See: https://stackoverflow.com/questions/11123717/removing-leading-zeros-before-passing-a-shell-variable-to-another-command#answer-11130324 Or alternatively do not `declare -i` but `echo "$(((10#$a<<24)+(10#$b<<16)+(10#$c<<8)+10#$d))";` – Léa Gris Jul 01 '19 at 17:13
3

my version for int to ip conversion:

echo 3232235521| awk {'print rshift(and($1, 0xFF000000), 24) "." rshift(and($1, 0x00FF0000), 16) "." rshift(and($1, 0x0000FF00), 8) "." and($1, 0x000000FF) '}

vik
  • 61
  • 5
3

Here's my take:

$ host google.com
google.com has address 216.58.216.142
$ ./ip2d.sh 216.58.216.142
3627735182
$ ./d2ip.sh 3627735182
216.58.216.142

ip2d.sh:

#!/bin/bash

IFS=.
set -- $*
echo $(( ($1*256**3) + ($2*256**2) + ($3*256) + ($4) ))

d2ip.sh:

#!/bin/bash

IFS=" " read -r a b c d  <<< $(echo  "obase=256 ; $1" |bc)
echo ${a#0}.${b#0}.${c#0}.${d#0}
evuraan
  • 31
  • 2
  • Love it. ip2d could be a (bash) function as `ip2d() { IFS=. eval 'set -- $1'; echo ...` and d2ip could also be written `IFS=\ read -r a b c d < <(echo "obase=256;$1" |bc)` – bufh Mar 14 '16 at 15:41
  • And shorter again: `A=($(dc -e "256o$1p"));IFS=. eval 'echo "${A[*]#0}"'` (have the same issue than the original when a quad has two leading 0). – bufh Mar 14 '16 at 16:01
2

I think there's a simpler solution that also handles an arbitrary number of octets with no references to fixed offsets etc.

echo 66.102.13.19 |
    tr . '\n' |
    while read octet; do
        printf "%.08d" $(echo "obase=2;$octet" | bc)
    done |
    echo $((2#$(cat)))

output: 1113984275

Brian Chrisman
  • 3,482
  • 1
  • 15
  • 16
  • Hello, I have a question on your answer. Can you explain the meaning of the `$((2#$(cat)))` – bwangel Dec 07 '16 at 02:45
  • 1
    @yundongxu The `cat` command reads the data piped into standard input which is a string of binary digits. The expression `$((2#...))` converts "..." from base 2 to decimal. – Eric Pruitt Dec 08 '16 at 02:49
2

Simple int to IP conversion for bash

dec2ip ()
{
   local v=$1
   local i1=$((v>>24&255))
   local i2=$((v>>16&255))
   local i3=$((v>>8&255))
   local i4=$((v&255))
   printf '%d.%d.%d.%d\n' $i1 $i2 $i3 $i4
}
ken
  • 301
  • 3
  • 6