http://www.perlmonks.org?node_id=74513
Category: Miscellaneous
Author/Contact Info ybiC
Description: Convert a dotted-quad decimal IP address to it's dq octal form, and give a slightly rude retort if you don't know what constitutes a valid decimal dq.   Inspired by bruddaP's observation that pinging an IP address that's been leading-zero-padded results in ICMP echo request sent to unexpected host.

Thanks to Petruchio for the great code review below.   Thanks also to jeffa, chipmunk and buckaduck for number-range-matching help.   And to tye for further insight on calling subroutines.   Oh yeah, and to some guy named vroom.

Suggestions for improvement welcome and appreciated.

Updated:
2001-04-24
    Implemented most^W even more of Pfunk's suggestions.
    perldoc ipdec2oct.pl for details.

#!/usr/bin/perl -w

# ipdec2oct.pl
# pod at tail

use strict;

my @octets8;
my $decdq = shift;

unless (defined $decdq) {
    print("\n  Hey you, enter a decimal dotted-quad!\n");
    Usage();
    }

my @octets10 = (split /\./, $decdq);
my $error    = qq/\n  Hey you, "$decdq" is baaad input:/;
unless (@octets10 == 4) {
    print($error);
    print("\n  It's not precisely 4 octets!\n");
    Usage();
    exit;
    }
for (@octets10) {
    unless (/^\d{1,3}$/) {
        print($error);
        print(qq/\n  "$_" is not a 1-3 digit positive integer!\n/);
        Usage();
        }
    if ($_ > 255) {
        print($error);
        print(qq/\n  "$_" is greater than 255!\n/);
        Usage();
        }
    my $octet8 = sprintf "%lo", $_;
    push @octets8, ($octet8 > 7) ? "0$octet8" : $octet8;
    }

print("\n decimal $decdq",
      "\n octal   ", join('.', @octets8),
      "\n\n");

######################################################################
+####
sub Usage {
    print("\n  Usage:  ipdec2oct.pl dottedquad_decimal_ipaddr\n");
    print("\n  $0\n  Perl $]\n  $^O\n\n");
    exit;
    }
######################################################################
+####

=head1 Name

 ipdec2oct.pl

=head1 Description

 convert decimal dotted-quad IP address to octal dq

=head1 Usage

 ipdec2oct.pl dottedquad_decimal_ipaddr

=head1 Tested

 Perl 5.00601   Win2kPro       Cygwin
 Perl 5.00503   Win2kPro       zshell(UnxUtils) and cmd.exe
 Perl 5.00503   Debian 2.2r3

=head1 Updated

 2001-04-24   08:20
     Moved 'unless (@octets10 == 4)' before 'for (@octets10)' loop
       for Great Justice.  Er, for slight optimization.
     Minor format tweaks.

 2001-04-23   16:15
     Removed comments as err msg's sufficient. 
     Mixed-case subroutine name, called with parens, w/o ampersan.
     Add $err to reduce redundant message text.

 2001-04-22
     Added '1-3 digit' to errmsg of 'positive interger'.
     Simplify octal-output padding with trinary op '? :'.
     Simplify octet parsing with $_ not declared scalar.
     Keep @octet10 otherwise need counter scalar.
     Replace %address with $decdq.
     Eliminate $address{octal} to simplify output code.
     Format for max of 80 character line.
     Check for (4) dot-separated octets in decimal input,
     Eliminate counter scalar by checking array length.
     Eliminate global vars by not using 'use vars qw()'.
     Use qq// to avoid leaning-toothpicks.
     Tweak is-octet-a-number regex for positive integers,
        so also eliminate if($_<0) loop.
     Minor format tweaks.

                      
 2001-04-21
     Validate decimal dq input with numeric comparison,
        instead of only-partially-effective regexp.
     Confirm *something* entered with 'unless defined'.
     Pad octal output w/leading zero.
     Add comments and pod.
     Post to Perl Monks.
                      
 2001-03-20
     Initial working code.

=head1 Todos

 Sit back and chuckle over time+synapses applied to a mostly useless s
+cript.
 Set to memory goodies learned of
   split    join    oct
   qq    ?:    array-length
   defined    regexen    padding
   global-vs-lexical   calling&naming-subs    optimization.

=head1 Author

 ybiC

=head1 Credits

 Thanks to Petruchio for greatly appreciated code review,
   and for interesting observation that inspired this exercise.
 And to jeffa, chipmunk and buckaduck for number-range-matching help.
 And to tye for further insight on calling subroutines.
 Oh yeah, and to some guy named vroom.

=cut
Replies are listed 'Best First'.
Re: IP address - decimal to octal
by Petruchio (Vicar) on Apr 23, 2001 at 01:21 UTC
    Update: Like I said, style's contentious. :-) Thanks to tye for interesting criticism on subroutine calls... read one of his posts for a well-thought-out opinion on the topic.


    Update: Good call updating to make sure there are four octets, ybiC. I'm not bothering to update likewise. Rather than a counter, though, you can just check to see if @octets10 == 4.


    I was going to do something cool for my 100th post, but I got tired of not posting while I worked on stuff. Oh, well. I never liked base-10 anyway. ;-)

    I have come into the habit of abusing ybiC's posts in the ChatterBox via /msg... at ybiC's request. The conversation often deals with style as much as anything. These conversations, however, have outgrown the CB, so I'm posting this one, and rewriting the script according to my tastes. Some of it may be of general interest as well, but the style opinions are contentious.

    Like 80 character lines. Darn it, ybiC, 75 Saves Lives! ;-)

    Personally, I don't use & to denote subroutines except when I'm not supplying an argument list, and I want to make my current @_ available to the sub (check perlsub for details). So right off, s/&USAGE/USAGE/; I also don't much care for uppercase subroutine names, so I changed that too. What the heck.

    It is not necessary to declare @octets8 and $octetpadded as global variables... they'll serve just fine as lexically scoped variables. Better not to thwart strict.

    my(@octets8,$octetpadded);

    This regex is somewhat less than optimal:

    $octet10 =~ /\d{1,3}/

    It matches octets which contain between 1 and 3 digits, but which may contain other characters as well. Thus when I enter:

    ./ipdec2oct.pl 10.m55.000.001

    I get an incorrect octal address back:

    octal 012.0.0.1

    Since you've got warnings enabled, I also get a message complaining that

    Argument "m55" isn't numeric in lt at ./ipdec2oct.pl line 22.

    and hopefully that will lead me in the right direction as a user. But it's a problem.

    Now interestingly, if you change your regex to match octets consisting solely of between one and three digits...

    $octet10 =~ /^\d{1,3}$/

    you not only take out the problem of extraneous characters, you prevent the possibility of an octet of less than zero. So you can remove the lines

    if ( $octet10 < 0 ) { &USAGE("\"$address{decimal}\" don't cut it: \"$octet10\" is l +ess than 0"); }

    On to your usage of USAGE. First off, let me point out the glory of qq(). You're using double quotes to contain your argument to &USAGE, because you need variables to interpolate within the argument... only problem is, you want double quotes within the argument. This leads to the frequent use of awkward escape sequences...

    &USAGE("\"$address{decimal}\" don't cut it: \"$octet10\" is greater t +han 255");

    Enter the qq operator. qq acts in the same way as double-quoting a string, and makes use of whatever delimiters you specify (q does likewise for single quoting). So this function call could be rewritten as

    &USAGE(qq["$address{decimal}" don't cut it: "$octet10" is greater tha +n 255]);

    or

    &USAGE(qq."$address{decimal}" don't cut it: "$octet10" is greater tha +n 255.);

    However, I'm not so sure that USAGE needs an argument. Why not just print your error message, and then call USAGE to print the usage message.

    if ( $octet10 > 255 ) { print qq/"$address{decimal}" don't cut it: /, qq/"$octet10" is greater than 255./; &USAGE; }

    As it turned out, the way I tweaked things qq() wasn't even needed; but it's still good to know.

    Instead of saying

    if ($octet8 > 7) { $octetpadded = "0$octet8"; } else { $octetpadded = $octet8; }

    You could say

    $octetpadded = ($octet8 > 7) ? "0$octet8" : $octet8;

    which is more readable to me, at least.

    For that matter, instead of saying

    $octetpadded = ($octet8 > 7) ? "0$octet8" : $octet8; push @octets8, $octetpadded;

    You could say

    push @octets8, ($octet8 > 7) ? "0$octet8" : $octet8;

    and eliminate the $octetpadded variable entirely. I don't know whether that's more readable, but it goes a long way towards explaining why I like Perl. ;-)

    Looking at your foreach loop... a few things. First, as perhaps you know, foreach and for are synonyms. I mention this simply because it's worth mentioning, and I'm changing foreach to for simply because it's shorter and I prefer it.

    for my $octet10(@octets10) {

    Second, as perhaps you know as well, you could omit the my $octet10 part. If you do, the particular member referenced will be assigned to $_ instead. Whether you should do this in any particular circumstance is a matter of taste and debate. Here, it could be argued that the for(each) loop is long enough that using a more descriptive name enhances clarity. On the other hand, it could be argued that use of $_ reduces clutter, especially since there are no nested for(each) loops or other constructions which would assign to $_. Again, I'm going to opt for brevity, partially because that's how I'd do it, and partially because it's interesting to change things to see a different way.

    for (@octets10) {

    Instead of saying

    my @octets10 = split /\./, $address{decimal}; for (@octets10) {

    you have the option to say,

    for (split /\./, $address{decimal}) {

    You needn't, of course. I just like to because I'm a bit of a syntactic pervert and I'm into that sort of thing.

    Likewise, I replaced

    $address{octal} = join '.', @octets8; print "\n decimal $address{decimal}\n octal $address{octal}\n\n";

    with

    print "\n decimal ", $address{decimal}, "\n octal ", join('.',@octets8), "\n\n";

    Of course, that means that there's no longer a $address{octal}, only an $address{decimal}... which means a hash is no longer useful. Dang it! Just after I'd finally gotten you using hashes. ;-)

    To complete the nit-picking, let me point out that "aint" requires an apostrophe. ;-)

    Anyway, with a few more modifications I've not mentioned, here's my stripped-down version. Not quite how I'd do it, but that's not the point; it's still your script at heart. Again, not all the changes are necessarily improvements.

    #!/usr/bin/perl -w use strict; my @octets8; my $ip = shift; unless (defined $ip) { print 'Enter *something* for input'; usage(); } for (split /\./, $ip) { my $err = "$ip don't cut it: $_"; if ( !/^\d{1,3}$/ ) { print "$err ain't a number."; usage(); } elsif ( $_ > 255 ) { print "$err is greater than 255."; usage(); } else { my $oct = sprintf "%lo", $_; push @octets8, ($oct > 7) ? "0$oct" : $oct; } } print "\n decimal ", $ip, "\n octal ", join('.',@octets8), "\n\n"; sub usage { print join "\n ", "\n Usage: ipdec2oct.pl dottedquad_decimal_ipaddr\n", $0, "Perl $]", $^O, "\n"; exit; }

      If you are going to drop & then you shouldn't be using all lowercase for your subroutine names. -w should really warn about this but the problem kind of snuck in. I wouldn't use all uppercase either.

      Update: The reason using all-lowercase subroutine names is a problem (if you don't also use &) is that:

      sub dump { # ... } dump( $data );
      is going to be very confusing. OK, everyone raise their hand if they have memorized the complete list of Perl reserved words. OK, you freaks with your hands up, keep them up if you know all of the future reserved words of Perl.

      I really think there should be a warning for calling a subroutine with an all-lowercase name w/o using & (with wording about "possible future reserved word") and another warning for declaring a subroutine whose name is already a reserved word (and now I think I'd also warn for all-uppercase names since we have quite a few reserved words in that category now). These cases slipped through the cracks during the Perl4-to-Perl5 transition when & became optional (and eventually even discouraged) but the practical consequences of that weren't well noticed, IMHO.

              - tye (but my friends call me "Tye")