7

Quite simply I'd like to verify that a string represents a valid IP address in a Bash script. I think like many others I've fallen into the pitfall of trying to do this with a regular expression; while this works well enough for IPv4, IPv6 is more complex (as it supports compression of zeroes) and it's just a complicated and not especially readable solution.

However, Unix and Linux clearly understand IP addresses well enough, so I'm wondering if there is a better way that I can validate IP addresses in bash? Preferably a reasonably common tool that can be used to do-so.

I'd like to avoid using other languages such as Python to do this, as I am in fact hoping to replace a current solution which is to use PHP's filter_var function. It works, but as with regular expressions, it's introducing a second language where I hope to avoid using one.

Lastly, if there's a solution that can also handle IP address ranges (such as 0.0.0.0-255.255.255.255 or 0.0.0.0/16 etc.) that'd be ideal, but I can handle these myself.

4
  • 1
    Are you looking to handle non-standard cases also? Try ssh 2130706433 to confirm that many 32-bit ints are valid IP addresses. Two 16-bit ints separated by a dot are also valid, etc.
    – doneal24
    Dec 10, 2015 at 17:21
  • 2
    IPv6 is more complex (as it supports compression of zeroes). Just IPv6? Try a ping 127.1 and see the result from IP v4
    – Hennes
    Dec 10, 2015 at 17:47
  • @DougO'Neal Not sure what you mean by non-standard? Really all I need is to verify that an IP address is valid syntactically (I don't need to verify that it exists, that'll happen elsewhere). @ Hennes I stand corrected, thanks, I suppose it's just something I've never seen with the shorter IPv4 address :)
    – Haravikk
    Dec 10, 2015 at 22:51
  • Actually, mentioning ping I'm wondering if I could use that? It seems to return status 2 if given an invalid address (like 'foo'), but status 1 if a ping was sent (valid address) but no response was received. Is this reliable enough to use? i.e- status 0 and status 1 = well formed IP?
    – Haravikk
    Dec 10, 2015 at 22:56

5 Answers 5

4

How about using ip command;

ip route get <ip-address>

If it's not valid, the return value would be 1.

(2022/9/17) To answer @MaXi32's comment, I quickly checked this on my Linux laptop without an internet connection. The ip command returns 1 for invalid IP and 2 for valid IP when the network is down.

For valid IPs:

$ ip route get 192.168.0.255 ; echo $?
RTNETLINK answers: Network is unreachable
2
$ ip route get fd00::192.168.0.255 ; echo $?
RTNETLINK answers: Network is unreachable
2

For invalid IPs:

$ ip route get 192.168.0.256 ; echo $?
Error: any valid prefix is expected rather than "192.168.0.256".
1
$ ip route get fd00::192.168.0.256 ; echo $?
Error: any valid prefix is expected rather than "fd00::192.168.0.256".
1

Version of ip command:

$ ip -V
ip utility, iproute2-5.9.0, libbpf 0.3.0

(2022/9/17#2) One can write a small program using inet_pton. I think that's even better. The following code from the example of inet_pton manpage tries to convert IPs from text to binary and raise error if the IPs are not valid:

   #include <arpa/inet.h>
   #include <stdio.h>
   #include <stdlib.h>
   #include <string.h>

   int
   main(int argc, char *argv[])
   {
       unsigned char buf[sizeof(struct in6_addr)];
       int domain, s;
       char str[INET6_ADDRSTRLEN];

       if (argc != 3) {
           fprintf(stderr, "Usage: %s {i4|i6|<num>} string\n", argv[0]);
           exit(EXIT_FAILURE);
       }

       domain = (strcmp(argv[1], "i4") == 0) ? AF_INET :
                (strcmp(argv[1], "i6") == 0) ? AF_INET6 : atoi(argv[1]);

       s = inet_pton(domain, argv[2], buf);
       if (s <= 0) {
           if (s == 0)
               fprintf(stderr, "Not in presentation format");
           else
               perror("inet_pton");
           exit(EXIT_FAILURE);
       }

       if (inet_ntop(domain, buf, str, INET6_ADDRSTRLEN) == NULL) {
           perror("inet_ntop");
           exit(EXIT_FAILURE);
       }

       printf("%s\n", str);

       exit(EXIT_SUCCESS);
   }
3
  • 1
    This is not a good solution where the IP needs to be reachable on the network before it gets validated.
    – MaXi32
    Aug 27, 2021 at 16:01
  • 1
    @MaXi32 It will return 1 for invalid IP regardless of connection status. It will return 2 for valid IPs when it's not reachable. So your claim seems wrong.
    – ktaka
    Sep 17, 2022 at 6:37
  • I'm not sure if they updated the new version for the return code to be 2, because you replied 1 year after my comment. But whatever it is, that still a bad solution because, it still requires an internet connection to have a success code which is 0 if a valid IP is provided. Anything than 0 is considered an error code. Whatever the return code is, my strong claim was about the syntax requires internet connection to have a success code which should be 0.. You got the idea this is not a good solution because it needs internet connection.
    – MaXi32
    Oct 23, 2022 at 6:53
2

Based on the answer from @ktaka I don't recommend using ip route get to validate IP addresses unless your script depends heavily on the network connection. The reason is because, if your system does not have a good internet connection or is completely disconnected from the internet, then when you use ip route get, your script will always return error code (other than 0) even the IP is correctly defined.

Solution:

I looked up the manual link given by @Rolf Rander and I was confused why my ipcalc binary on Debian 11 does not have the -c option, and also my previous ipcalc binary does not support ipv6. So, I searched on Google the author username taken from the manual page with this keyword dcantrell ipcalc and I got the official link here:

https://gitlab.com/ipcalc/ipcalc

So the above is the official ipcalc that supports both IPv4 and IPv6 IP addresses. To install this, they recommend using the modern build with meson (but they also provided old make build if you don't want to use meson)

So, this is how I setup ipcalc on Debian 11:

Remove existing ipcalc (the existing version is not good)

apt-get -y remove ipcalc
rm -rf "/usr/bin/ipcalc"

Now install ipcalc from the official repo here:

apt -y install meson
git clone https://gitlab.com/ipcalc/ipcalc.git
cd ipcalc
meson setup build --buildtype=release
ninja -C build
cp build/ipcalc /usr/bin
apt -y remove meson

ipcalc --version

1.0.1

Now you can use this ipcalc to check both IPv4 and IPv6:

#!/bin/bash
IP="1.1.1.1"
if ipcalc -s -c $IP; then
  echo "valid IPv4 or IPv6"
else
  echo "Invalid IPv4 or Ipv6"
fi

The -s is to ignore error message, the -c is for checking without output

If you want to check specific IPv4 use -4 or use -6 to check IPv6. Example:

#!/bin/bash

# IP is a valid IPv6
IP="::1"

# But we want to check only IPv4

if ipcalc -s -4 -c $IP; then
  echo "valid IPv4"
else
  echo "Invalid IPv4"
fi

Output:

Invalid IPv4

I have been using sipcalc and ipv6calc long time ago and they can do both IPv4 and IPv6 checking, but I do like more on ipcalc because most of the output are simple and it has json output for other purposes.

EDIT

(23/10/2022):

Based on the latest comment from @ktaka, my main point was that when using ip route get without internet connection like below:

$ ip route get 192.168.0.255 ; echo $?
RTNETLINK answers: Network is unreachable
2

it is not going to return a valid IP if your system is behind firewall or you have a bad connection. The IP is valid, and it should return the success code of 0, instead of telling unnecessary things like unreachable network.

1

bash probably lacks direct access to the relevant inet_pton(3) system call, so you'll probably need to call something that does, e.g. sipcalc (which should be in the ports or packages tree for various unix). Higher level languages can probably also get at the inet_pton(3) call, or equivalent, but the details will be language specific, e.g. I've used NetAddr::IP for subnet related code in Perl.

3
0

Ipcalc can help you:

ipcalc -c <ip-address>
6
  • Strangely, ipcalc (0.41)'s help has: -c --color Display ANSI color codes (default) and -c --class Just print bit-count-mask of given address. Dec 10, 2015 at 21:28
  • there must be different versions of ipcalc... Checking my linux-server, I see the same thing, this doesn't look anything like the man-page I linked to! Dec 10, 2015 at 21:35
  • -c flag doesn't seem to do anything on the version I got via aptitude (on Ubuntu Server 14.04), but it works well enough for IPv4. It doesn't seem to support IPv6 though, at least not the version I've got, are there others?
    – Haravikk
    Dec 10, 2015 at 22:48
  • The ipcalc packages on Debian based distros differ from RHEL/CentOS - they are different programs with different behavior and different switches. On my Debian -c prints bit-count mask of the given address, while on RHEL -c validates ('checks') the IP address.
    – bryn
    Jun 8, 2017 at 22:36
  • Didn't work with IPv6 (on Debian), although the OP specifically asked for it.
    – Calimo
    Aug 26, 2019 at 14:52
0

This will do the trick for ipv4

# check IPv4 syntax
if [[ "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
    echo 'Valid IP'
else
    echo "Invalid IP: $ip" >&2
    exit 1
fi

I'm implementing ipv6 into the script I extracted this from anyway, once I have that done, I'll update this post.

2
  • 3
    That regex is too broad and will accept IP addresses that aren't valid.
    – Anthony
    Oct 11, 2016 at 22:03
  • Depends on what one wants to achieve. A simple syntax check can sometimes be enough. Nov 27, 2019 at 9:52

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.