0

I want to check if the user input is a valid IP address/CIDR in bash and I am using regular expressions to do that. So valid CIDR should be 0-32 and for IP from (1-254).(1-255).(1-255).(1-255)/(1-32) So my code currently is:

read -p "Input: " ip_address

if [[ $ip_address =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$ ]];
    then
    echo "VALID"
else
    echo "NOT VALID"
fi

But it is too generous and accepts some not valid combinations. So a valid IP address/CIDR combination should be: 10.11.11.11/24 or 254.255.255.255/23 and invalid will be 256.19.11.11/24 because the first octet is higher than 255 or 222.222.222.222/33 here the CIDR is is higher than 32. Is there another way apart of regex to check for valid IP address/CIDR?

2

To properly handle validation of an IP address or CIDR, use a library function specifically made for this, such as the cidrvalidate() Perl function in the Net::CIDR module:

$ perl -MNet::CIDR=cidrvalidate -e 'printf("%s\n", cidrvalidate($ARGV[0]) ? "valid" : "invalid")' -- 1.2.3.0/24
valid

$ perl -MNet::CIDR=cidrvalidate -e 'printf("%s\n", cidrvalidate($ARGV[0]) ? "valid" : "invalid")' -- 1.2.3.0/2
invalid

$ perl -MNet::CIDR=cidrvalidate -e 'printf("%s\n", cidrvalidate($ARGV[0]) ? "valid" : "invalid")' -- 1.2.3.0
valid

See perldoc Net::CIDR for what else this library can do.

The -- are not necessary in the examples above but would be for arbitrary input from the user as otherwise, that input would be taken as an option by perl if it started with -.

The approaches below are variations on your attempt, which does not care about invalid netmasks.


A positive decimal integer between 0 and 255 may be matched by

[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]

A positive decimal integer between 0 and 32 may be matched by

[0-9]|[12][0-9]|3[012]

Using this:

#!/bin/bash

n='([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
m='([0-9]|[12][0-9]|3[012])'

IFS= read -rp 'Input: ' ipaddr

if [[ $ipaddr =~ ^$n(\.$n){3}/$m$ ]]; then
    printf '"%s" is a valid CIDR\n' "$ipaddr"
else
    printf '"%s" is not valid\n' "$ipaddr"
fi

The expression

^$n(\.$n){3}/$m$

would expand to the full regular expression for a valid CIDR spanning the complete length of the given string.


The other obvious way to do this is to read the numbers in the given string and test whether the first four are in the 0-255 range and whether the fifth is in the range 0-32:

#!/bin/bash

IFS='./' read -rp 'Input: ' a b c d e

for var in "$a" "$b" "$c" "$d" "$e"; do
    case $var in
        ""|*[!0123456789]*) 
            printf 'not a valid number: %s\n' "$var"
            exit 1
    esac
done

ipaddr="$a.$b.$c.$d/$e"

if [ "$a" -ge 0 ] && [ "$a" -le 255 ] &&
   [ "$b" -ge 0 ] && [ "$b" -le 255 ] &&
   [ "$c" -ge 0 ] && [ "$c" -le 255 ] &&
   [ "$d" -ge 0 ] && [ "$d" -le 255 ] &&
   [ "$e" -ge 0 ] && [ "$e" -le 32  ]
then
    printf '"%s" is a valid CIDR\n' "$ipaddr"
else
    printf '"%s" is not valid\n' "$ipaddr"
fi

Here we read five words into five variables. The inputted string is split into words on . and / when read (which means 3/3/3/3.2 would be parsed as valid, but see $ipaddr in the code). Any non-integer data read would trigger the script to exit. We then start testing their values against the valid ranges. If any test fail, the address entered is not valid.

  • thanks a lot for your help Stephane Chazelas and @Kusalananda – Georgе Stoyanov Mar 11 at 9:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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