http://www.perlmonks.org?node_id=395990

sashac88 has asked for the wisdom of the Perl Monks concerning the following question:

Hey,Good Monks.
I'm trying to write something simple that will accept
values from <STDIN> only if the value is in IP format,i.e.
only if it's four numbers 0-255 separated by dot.
Actually I only need a way to restrict to input of numbers 0-255 ,since [0-9] or \d won't help me -too wild. Please help .

Janitored by Arunbear - retitled from 'Regular Expression'

Replies are listed 'Best First'.
Re: Regular Expression to Match IP Address
by mawe (Hermit) on Oct 03, 2004 at 13:52 UTC
Re: Regular Expression to Match IP Address
by Dietz (Curate) on Oct 03, 2004 at 13:06 UTC
    This should work for your needs:
    my $range = qr/^ ( (?: # first 3 octets: (?: 2(?:5[0-5]|[0-4][0-9])\. ) # 200 - 255 | # or (?: 1[0-9][0-9]\. ) # 100 - 199 | # or (?: (?:[1-9][0-9]?|[0-9])\. ) # 0 - 99 ) {3} # above: three times (?: # 4th octet: (?: 2(?:5[0-5]|[0-4][0-9]) ) # 200 - 255 | # or (?: 1[0-9][0-9] ) # 100 - 199 | # or (?: [1-9][0-9]?|[0-9] ) # 0 - 99 ) $) /x; print "OK\n" if ($ip =~ /$range/);

    Update:
    Changed (?: [0-9][0-9]? ) to (?: [1-9][0-9]?|[0-9] ) so that it won't accidently match an octet with only 2 or more zeros or an octet with leading zero (i.e. 00.0.0.0 or 192.186.0.01)
    Now this will only match addresses from 0.0.0.0 to 255.255.255.255 (assuming the address in $ip is already chomped)
      if we want a simple version print"\n ip address " if ($s=~/((\d){1,3}\.){3}(\d){1,3}/) ;
Re: Regular Expression to Match IP Address
by CountZero (Bishop) on Oct 03, 2004 at 12:25 UTC
    What about this?

    \b[012]?[0-5]?[0-9]\.[012]?[0-5]?[0-9]\.[012]?[0-5]?[0-9]\.[012]?[0-5]?[0-9]\b

    Update: Forget it, it doesn't work that way: it does not match if the second digit is above 5.

    Just use Regexp::Common::net.

    (lesson learned (again): don't re-invent the wheel!)

    CountZero

    "If you have four groups working on a compiler, you'll get a 4-pass compiler." - Conway's Law

Re: Regular Expression to Match IP Address
by bart (Canon) on Oct 03, 2004 at 20:32 UTC
    In theory that's an ideal task for regexp assertions in Perl code. Unfortunatly such assertions are rather convoluted to do in current day perl, though it does work.

    Let's first try the simplified task to find if a number matches an integer that is between 0 and 255 (incl). The lower border is easy, all you need to do is forbid a minus sign. So all you have to do is test if a matched number is below 256.

    Here's a demo to see if an integer is below 256:

    ($\, $,) = ("\n", "\t"); print $_, /\b(\d+)\b(?(?{$1 >= 256})(?!))/ ? 'Y' : 'N' for 0 .. 1000;
    This prints, summarized:
    0	Y
    1	Y
    2	Y
    ...
    254	Y
    255	Y
    256	N
    257	N
    ...
    1000	N
    

    So how does it work? First of all, we try to match an integer, using

    /\b(\d+)\b/
    The "\b" is desired when using assertions, in order to prevent substrings to match instead of the whole. Otherwise, if you have a string "1234", the substring "123" is less than 256 and would match. We can't use that.

    The next part is a pattern of the form

    (?(condition)yes-pattern)
    which will try to match the "yes-pattern" only if the condition (a regex pattern itself) is met. The condition takes the form here:
    (?{ code })
    which executes code, and which normally always succeeds; but which in this special usage case serves as a switch to conditionally test the following yes-pattern.

    And that yes-pattern takes the form

    (?!)
    which actually is a plain negative lookahead, which essentially says "whatever it is that follows now, it is wrong." So, essentially, it always fails.

    The net effect is the condition has to be the wrong way around: it must return true when we want the math to fail. In this example, we have code to test if what is matched is 256 or above, and if so, fail by trying (and failing) to match /(?!)/.

    As a whole, we can try matching an 4-dotted quad IP address using the regexp:

    /^(\d+)(?(?{$1 >= 256})(?!))\.(\d+)(?(?{$2 >= 256})(?!))\.(\d+)(?(?{$3 + >= 256})(?!))\.(\d+)(?(?{$4 >= 256})(?!))$/
    but we might as well combine the separate conditions into one:
    /^(\d+)\.(\d+)\.(\d+)\.(\d+)$(?(?{$1 >= 256 || $2 > 256 || $3 >= 256 | +|$4 >= 256})(?!))/

    Do note that I can leave off the \b anchors, because I have enough other sideconditions: andchors /^/ and /$/, as well as a need to match /\./ next.

Re: Regular Expression to Match IP Address
by aquarium (Curate) on Oct 03, 2004 at 12:46 UTC
    $input = <STDIN>; chomp $input; $input =~ /\d+\.\d+\.\d+\.\d+/ or die "bad input"; foreach $k($input =~ /\d+/g) { die "bad input" if($k<0 or $k>255); }
    ...or something like that
Re: Regular Expression to Match IP Address
by Anonymous Monk on Oct 03, 2004 at 20:36 UTC
    Did you know that 10.1, 10.0.1, 10.0.0.1, and 167772161 are all valid IPv4 addresses, all referring to the same machine? You may want to check your assumptions and make sure that you have documented which IPv4 address formats you will support.
        I don't see anything there about it, and even if you're correct...

        It looks like wikipedia is not an authoratative source on IP addresses ;) I believe IPv4 is described in detail in RFC 791 (would someone please correct this if I gave the wrong rfc please!) IPv6 is detailed in RFC 2460.

        IPv4 addresses are just 32 bit numbers that are displayed as a dotted quad for our convenience. It is much easier (at least to my eye) to look at and understand "10.0.0.1 netmask 255.255.255.0" than "167772161 netmask 4294967040", even though they are equivalent. Look at the following examples if you don't believe this.

        perl -e 'print "true!" if 10 * 256**3 + 0 * 256**2 + 0 * 256 + 1 == 16 +7772161' # prints true!

        or try this:

        $ ping 167772161 PING 167772161 (10.0.0.1): 56 data bytes ^C --- 167772161 ping statistics --- 2 packets transmitted, 0 packets received, 100% packet loss
        A reply falls below the community's threshold of quality. You may see it by logging in.
Re: Regular Expression to Match IP Address
by TedPride (Priest) on Oct 03, 2004 at 21:02 UTC
    my $ip = &get_IP; sub get_IP { my $ip; do { $ip = <STDIN>; chop($ip); } while $ip !~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ || $1 > 255 || $2 > 255 || $3 > 255 || $4 > 255; return $ip; }
        Interesting, I didn't know that. My bad :)