Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.
echo "1.1.1.1" | awk '/[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*/ {print $1}'

How can I filter IPv4 addresses in a script, and how can I filter IPv6 addresses.

I mean more precisely then this so the oneliner should only output VALID IPv4 addresses or another script to output only VALID IPv6 addr. In different languages? like: awk, perl?

ty!

share|improve this question
    
What exactly do you consider valid? Is 241.57.97.2 valid (it's in an unassigned range)? What about 198.51.100.2 (it's reserved for documentation)? Are 6-to-4 IPv6 addresses to be considered valid IPv6 addresses? –  Gilles Aug 28 '11 at 17:43
    
yes, there are VALID too! :) –  LanceBaynes Aug 28 '11 at 17:46
add comment

3 Answers

up vote 4 down vote accepted

There are several usual notations for IPv4 and IPv6 addresses. Here's an extended regular expression, suitable for Perl m//x, that captures the usual notations. If you remove the comments and whitespace, you can use it with grep -E, awk, or any other utility that uses extended regular expressions (ERE).

^(
  ([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]|0+[0-3][0-7][0-7]|0x0*[0-9a-fA-F][0-9a-fA-F])
  (\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]|0+[0-3][0-7][0-7]|0x0*[0-9a-fA-F][0-9a-fA-F])){3}   # IPv4 dotted quad
| 0x[0-9a-fA-F]{1-8}             # IPv4 hexadecimal
| 0+[0-9]{0-10} | 0+[1-3]{11}    # IPv4 octal
| [1-9][0-9]{1-8}                # IPv4 decimal, small
| [1-3][0-9]{9}                  # IPv4 decimal, medium
| 4[0-9]{9}                      # IPv4 decimal, large (needs a further range check)
| [0-9a-fA-F]{1-4}(:[0-9a-fA-F]{1-4}){7}            # IPv6 with all groups
| ([0-9a-fA-F]{1-4}:){1-1}(:[0-9a-fA-F]{1-4}){1-6}  # IPv6 with 1-6 middle groups omitted
| ([0-9a-fA-F]{1-4}:){1-2}(:[0-9a-fA-F]{1-4}){1-5}  # IPv6 with 1-6 middle groups omitted
| ([0-9a-fA-F]{1-4}:){1-3}(:[0-9a-fA-F]{1-4}){1-4}  # IPv6 with 1-6 middle groups omitted
| ([0-9a-fA-F]{1-4}:){1-4}(:[0-9a-fA-F]{1-4}){1-3}  # IPv6 with 1-6 middle groups omitted
| ([0-9a-fA-F]{1-4}:){1-5}(:[0-9a-fA-F]{1-4}){1-2}  # IPv6 with 1-6 middle groups omitted
| ([0-9a-fA-F]{1-4}:){1-6}(:[0-9a-fA-F]{1-4}){1-1}  # IPv6 with 1-6 middle groups omitted
)$

In case of a decimal value, you need a further range check (you can make a regexp of it, but it would be big):

if (!/[^0-9]/ && /^[^0]/) { # if it's a decimal number
    die if $_ > 4294967295 # reject numbers above 2^32-1
}

If the tool you use only supports 32-bit numbers, you can do the test only if the number starts with 4, and strip the 4 before doing the check.

if (!/[^0-9]/ && /^4/) { # if it's a decimal number beginning with 4
    my $tmp = $_;
    $tmp =~ s/^4//;
    die if $tmp > 294967295;
}
share|improve this answer
    
...And you are not checking the range of values :) –  rozcietrzewiacz Aug 28 '11 at 18:40
    
@rozcietrzewiacz I'm not? I wrote this quickly and didn't test it, so I may have made a mistake. What bad value gets through? I've made the test a bit more precise and added a way to test the range for decimals. –  Gilles Aug 28 '11 at 18:52
    
Oh, right - you are checking the range... Only you didn't add separators at the beginning and end, so things like 999.0.0.999 seem to pass. –  rozcietrzewiacz Aug 28 '11 at 18:54
    
I'd add [^0-9] at the beginning and the end of each... –  rozcietrzewiacz Aug 28 '11 at 19:01
add comment

In bash:

validIP4 () 
{ 
    IFS='.' read na nb nc nd;
    for n in "$na" "$nb" "$nc" "$nd";
    do
        [[ ${#n} -le 3 ]] && [[ "${n//[^0-9]/}" = "$n" ]] && [[ $n -lt 256 ]] || return 1;
    done
    echo OK
}

Usage examples:

  • echo 142.24.045.33 | validIP4
  • echo sd3.3.4.6 | validIP4 || echo "nope :("
  • echo 342.0.0.2 | validIP4 || echo "Noo...."
  • echo 3.2.1.0 | validIP4 && echo "Yes, sir."

As Gilles noted, this verifies only the most popular dot-decimal notation of an IPv4 address.

IPv6 can be done in a similar manner, but needs much more checking because (1) it uses hex and (2) some sections of all-zeros can be entirely omitted.

share|improve this answer
    
You're only allowing dotted quads, and excluding hexadecimal and some octal numbers. –  Gilles Aug 28 '11 at 18:35
    
True - I added a not about this. –  rozcietrzewiacz Aug 28 '11 at 18:43
add comment

First, tests:

IPv4 0.0.0.0 # Standard IP
IPv4 1.2.3.4 # Standard IP
IPv4 255.255.255.255 # Standard IP
IPv4 8.08.008.0008 # Should not treat leading zero as octal
IPv4 1.1.1 || true # Too short
IPv4 1.1.1.1.1 || true # Too long
IPv4 a.b.c.d || true # Non-numeric
IPv4 0.0.0.-1 || true # Negative

Anything from 0.0.0.0 to 255.255.255.255 in shell:

IPv4() {
    quads=0
    while IFS= read -r quad
    do
        let quads++
        while [[ $quad = 0* ]]
        do
            quad=${quad#0}
        done
        if [[ $quads -gt 4 || ! "${quad:-0}" =~ [0-9]+ || "${quad:-0}" -lt 0 || "${quad:-0}" -gt 255 ]]
        then
            return 1
        fi
    done < <(echo "$1" | tr '.' '\n')
    if [[ ! $quads -eq 4 ]]
    then
        return 1
    fi
}
share|improve this answer
    
You are incorrectly treating numbers with leading zeroes as decimal in dotted quads: POSIX (the relevant standard on a unix ) inet_ntoa specifies that in the “IPv4 dotted decimal notation”, despite its name, “a leading '0' implies octal”. And why don't you let me write ping 134744072? And what about IPv6? –  Gilles Sep 28 '11 at 20:42
    
I simply have never, in seven years of professional development, seen anyone use anything other than dotted decimal for IPv4. I have, on the other hand, seen people confused that their 08 was interpreted as octal. I never said my code was complete, but it's simple and works for the canonical case. –  l0b0 Sep 29 '11 at 8:10
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.