2

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?

1

2 Answers 2

5

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.

1
  • thanks a lot for your help Stephane Chazelas and @Kusalananda Commented Mar 11, 2019 at 9:20
0

thats what i use:

#!/bin/bash
function checkCidrFormat {
  local ipCidr="${1}"
  local validIpCidr
  validIpCidr='(^([1-9]|[1-9][0-9]|[1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5])\.([0-9]|[1-9][0-9]|[1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5])\.([0-9]|[1-9][0-9]|[1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5])\.([0-9]|[1-9][0-9]|[1][0-9][0-9]|[2][0-4][0-9]|[2][5][0-5])\/([1-9]|[1-2][0-9]|[3][0-2]))$'
  if [[ $ipCidr =~ ^$validIpCidr ]]; then
    echo "valid format"
    return 0
  else
    echo "not valid format"
    return 1
  fi
}

while true;
do
read -rp "ip cidr (eg. 172.16.16.32/27): " cidr
if checkCidrFormat "${cidr}"; then
  echo "do something"
fi
done
3
  • Hi, thanks for your feedback. I am pretty new to scripting and this was something i dont understood until now. Added shebang. Commented Apr 6, 2023 at 19:42
  • Nice :-) The shebang tells the system which shell to use for running the script. For example Debian's default shell is dash, so #!/bin/sh or #!/usr/bin/sh would cause the script to fail as it's not fully POSIX-compliant. These are just symlinks to fully POSIX-compliant dash. Don't know which Linux you run, but at least in Debian installing debscripts brings checkbashisms which can be used to, well, check for bashisms (i.e. non-POSIX bash-specific expressions / options / etc.) in a script. Commented Apr 7, 2023 at 5:59
  • Thanks - I found this useful. I did change the last "[1-9]" in the regex to be "[0-9]" so that CIDRs such as 0.0.0.0/28 would be acceptable. I also used this website to check CIDRs: ipaddressguide.com/cidr. Commented Jul 26, 2024 at 21:55

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.