Regular expressions to test IPv4 address
Hints, guides and regular expressions, PCRE and POSIX, to test the validity of a IPv4 address in standard and CIDR format.
Published on
TL;DR readable :)
versions to test for valid IPv4 address
# PCRE Test for valid IPv4 address
\b((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\b
# POSIX Test for valid IPv4 address
^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$
# PCRE Test for valid IPv4 address in CIDR format
\b((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)/([1-9]|[1-2]\d|3[0-2])\b
# POSIX Test for valid IPv4 address in CIDR format
^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])/([1-9]|[1-2][0-9]|3[0-2])$
Read on for hints, guides and the full story
Intro
There is Validating IPv4 addresses with regexp but not all perform complete or strict validation. Hard to tell without testing which one to choose.
Well, let’s do it the old and hard way: start with BashGuide/Patterns Regular Expressions to find that Bash uses Extended Regular Expressions
then read about RegularExpression (feel free to check other tutorials, too) then write your own tests or use online testers like DebuggexBeta or regular expressions 101 .
After all those, there are multiple versions, of course. PCRE
character classes (Perl and Java) specifies \b
for Word boundaries
and \d
for Digits
but POSIX
defines [:digit:]
for Digits
and does not have a class for Word boundaries
.
Explanation
25[0-5] | 2[0-4]\d | 1\d\d | [1-9]?\d
is an octet expressed as decimal number:
|
is likeor
logical operator25[0-5]
match 250 - 2552[0-4]\d
match 20 to 24 followed by any digit -> 200 - 2491\d\d
match 1 followed by two digits -> 100 - 199[1-9]?\d
match 1 - 9 at most once followed by a digit -> 10 - 19 and 0 - 9
Let’s call it X
to finish as sane as possible this article.
Now, the condition must be grouped - use (
and )
- and the four octets should be separated by dots. This leads to: (X).(X).(X).(X)
But for regular expressions .
match any character and have to be escaped with \.
so this leads to: (X)\.(X)\.(X)\.(X)
Using {
and }
we can specify how many times a match must occur. The expression can be simplified to: ((X)\.){3}(X)
((X)\.){3}(X)
matches an IPv4 address … but also matches an IPv4 with anything before and after it, like a1.2.3.4b
!
In PCRE
, \b
is a word boundary allowing to perform whole word only match.
Finally, \b((X)\.){3}(X)\b
matches only valid IPv4 addresses.
… except for POSIX
where \b
is not defined. To check a match in bash
I have used the standard ^
and $
anchors to match a whole string.
For POSIX
:
- Inside
X
,\d
must be replaced with[0-9]
or[:digit:]
^((X)\.){3}(X)$
matches a string containing only a valid IPv4 address
Bash test script
#!/bin/bash
check_ipv4_format() {
re='^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])$'
[[ $1 =~ $re ]] || {
printf '%s is not a valid IPv4 address!\n' "$1"
return 2
}
echo "OK $1"
}
check_ipv4_cidr_format() {
re='^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])/([1-9]|[1-2][0-9]|3[0-2])$'
[[ $1 =~ $re ]] || {
printf '%s is not a valid IPv4 address in CIDR notation!\n' "$1"
return 2
}
echo "OK $1"
}
echo 'These should pass:'
check_ipv4_format '0.0.0.0'
check_ipv4_format '0.0.0.1'
check_ipv4_format '127.0.0.1'
check_ipv4_format '192.168.1.1'
check_ipv4_format '192.168.1.255'
check_ipv4_format '11.12.13.17'
check_ipv4_format '1.11.111.1'
check_ipv4_format '255.255.255.255'
check_ipv4_cidr_format '0.0.0.1/1'
check_ipv4_cidr_format '0.0.0.1/12'
check_ipv4_cidr_format '0.0.0.1/22'
check_ipv4_cidr_format '0.0.0.1/3'
check_ipv4_cidr_format '0.0.0.1/4'
check_ipv4_cidr_format '0.0.0.1/32'
echo 'These should fail:'
check_ipv4_format '1.1.1.01'
check_ipv4_format '30.130.1.255.1'
check_ipv4_format '127.1'
check_ipv4_format '192.168.1.256'
check_ipv4_format '-1.2.3.4'
check_ipv4_format '1..1.1'
check_ipv4_format '0.00.0.1'
check_ipv4_format '1.11.111.1111'
check_ipv4_format 'a255.255.255.255'
check_ipv4_format '255.255.255.255a'
check_ipv4_cidr_format '0.0.0.1/'
check_ipv4_cidr_format '0.0.0.1/0'
check_ipv4_cidr_format '0.0.0.1/02'
check_ipv4_cidr_format '0.0.0.1/33'
check_ipv4_cidr_format 'a0.0.0.1/1'
check_ipv4_cidr_format '0.0.0.1/1a'