regexbashvalidationcidr

Bash check if CIDR address is valid


I just can't seem to wrap my head around this. I have a regex that check if a string contains a valid CIDR notation address.

(((?:25[012345]|2[0-4]\d|1?\d\d?)\.){3}(?:25[012345]|2[0-4]\d|1?\d\d?))(?:\/([1-9]|[1-2][0-9]|3[0-2]))(?![.\d])

This thing works in Perl, PHP, Javascript, and matches x.x.x.x/8 to y.y.y.y/32.

I've tried to change those \d to [[:digit:]] and to \\d Nothing :(

The test script used to test:

#!/bin/bash

if [ "$1" = "" ]
then
    echo "Usage: $( basename $0) 123.456.789.0/12"
    exit
fi
REGEX1='(((?:25[012345]|2[0-4]\d|1?\d\d?)\.){3}(?:25[012345]|2[0-4]\d|1?\d\d?))(?:\/([1-9]|[1-2][0-9]|3[0-2]))(?![.\d])'
REGEX2='(((?:25[012345]|2[0-4]\\d|1?\\d\\d?)\.){3}(?:25[012345]|2[0-4]\\d|1?\\d\\d?))(?:\\/([1-9]|[1-2][0-9]|3[0-2]))(?![.\\d])'
REGEX3='(((?:25[012345]|2[0-4][[:digit:]]|1?[[:digit:]][[:digit:]]?)\\.){3}(?:25[012345]|2[0-4][[:digit:]]|1?[[:digit:]][[:digit:]]?))(?:\\/([1-9]|[1-2][0-9]|3[0-2]))(?![.[[:digit:]]])'

REGEX=$REGEX3

if [[ $1 =~ $REGEX ]]
then
    echo "$1 OK!"
else
    echo "$1 Not OK! $REGEX"
fi

Any ideas to where to go from here?

Updated. Added working script:

#!/bin/bash

if [ "$1" = "" ]
then
    echo "Usage: $( basename $0) 123.456.789.0/12"
    exit
fi

REGEX='(((25[0-5]|2[0-4][0-9]|1?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|1?[0-9][0-9]?))(\/([8-9]|[1-2][0-9]|3[0-2]))([^0-9.]|$)'

if [[ $1 =~ $REGEX ]]
then
    echo "$1 OK!"
else
    echo "$1 Not OK!"
fi

if echo $1 | grep -Pq $REGEX
then
    echo "grep $1 OK!"
else
    echo "grep $1 Not OK!"
fi

Solution

  • The shortest path to success is GNU grep, which also supports PCRE:

    #!/bin/sh
    
    if echo "$CIDR" | grep -qP "$REGEX"
    then
      echo "$CIDR OK!"
      exit 0
    else
      echo "$CIDR NOT OK!"
      exit 1
    fi
    

    grep's -q makes it silent and relies on the exit code to determine success. -P is PCRE.

    But I should point out that your regex does not fully match that something is a valid CIDR range; rather, you're matching a valid IP address followed by a slash and a number n ∈ 1-32. An additional requirement to CIDR ranges is that the 32-n lower bits of the address are zero, e.g.:

    #!/bin/sh
    
    valid_cidr() {
      CIDR="$1"
    
      # Parse "a.b.c.d/n" into five separate variables
      IFS="./" read -r ip1 ip2 ip3 ip4 N <<< "$CIDR"
    
      # Convert IP address from quad notation to integer
      ip=$(($ip1 * 256 ** 3 + $ip2 * 256 ** 2 + $ip3 * 256 + $ip4))
    
      # Remove upper bits and check that all $N lower bits are 0
      if [ $(($ip % 2**(32-$N))) = 0 ]
      then
        return 0 # CIDR OK!
      else
        return 1 # CIDR NOT OK!
      fi
    }
    

    Test this with e.g. 127.0.0.0/24, 127.1.0.0, 127.1.1.0/24.

    Or more odd ranges: 10.10.10.8/29, 127.0.0.0/8, 127.3.0.0/10, 192.168.248.0/21.