Beefy Boxes and Bandwidth Generously Provided by pair Networks
"be consistent"
 
PerlMonks  

How to sort output?

by dguntner (Novice)
on Jul 22, 2007 at 06:36 UTC ( [id://628086]=perlquestion: print w/replies, xml ) Need Help??

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

Well, I'm down to the nitty-gritty at last! :-) The script to produce a list of hosts pinged is working, but I haven't got a clue as to how to sort the list by ping time. Here's the script in its current form:

#!/usr/bin/perl -w # # Ping a range of IP addresses, and list sorted by ping time. # # This uses the ICMP echo method instead of UDP echo, as # some routers don't pass UDP echo. Also, if the remote host # doesn't have an appropriate service listening on the UDP # echo port, it won't answer. # # D. Guntner, 21-July-2007 # Ver 1.0, 21-July-2007 use Net::Ping; use Net::Netmask; die "I need root privs to run, dude.\n" unless $> == 0; # Get the IP address(es) my $netaddr = shift(@ARGV); usage() unless $netaddr; # Give usage message if no input my $hostname = ""; my $block = new Net::Netmask($netaddr); my @hosts = $block->enumerate(); my $p = Net::Ping->new("icmp"); $p->hires(); # Comment out this line if no Time::HiRes installed # (Or better yet, install Time::HiRes... :-) ) # Create our host list to sort from open OF, ">hostlist.txt" or die "Can't create hostlist.txt: $!"; select OF; $|++; # Unbuffer output file select STDOUT; print("Now scanning $netaddr, please wait....\n"); foreach $host (@hosts) { ($ret, $duration, $ip) = $p->ping($host, 5.5); if ($ret) { printf OF ("%s [ip: $ip] is alive %.2f ms)\n", gethostbyip($host) +, 1000 * $duration); } } close OF; $p->close(); print("Now sorting the list....\n"); open IF, "<hostlist.txt" or die "Can't open hostlist.txt for input: $! +"; my @data = <IF>; close IF; # Don't need the file anymore, so delete it #if (unlink("hostlist.txt") > 0) { # print("Couldn't remove hostlist.txt file for some reason....\n"); #} my @sorted = map {s/(^|\D)0+(\d)(?=\t)/$1$2/g; $_} sort map {s/(\d+)(?=\t)/sprintf"%03.3d",$1/ge; $_} @data; print(@sorted); sub gethostbyip { use Socket; my $hostip = $_[0]; my $iaddr = inet_aton($hostip); my $hostname = gethostbyaddr($iaddr, AF_INET); $hostname = $hostip unless defined $hostname; return $hostname; } sub usage { use File::Basename; my $progname = basename($0); print <<"EO_USAGE"; This script will ping scan a range if IP addresses, and return a list sorted by ping time. Give address in CIDR format. Usage: $progname {IP/NETMASK} Example: $progname 1.2.3.4/24 You *could* put in only a single IP address, but there wouldn't be much point to that, now would there? :-) EO_USAGE exit; }
The "my @sorted" section there I copied as a placeholder from another script that was in one of the messages here, so I'd know the basic formula/idea. However, I don't know what that stuff should be changed to in order to get the sorting order I want. (Oh yea, and the "if" that's checking on the unlink to get rid of the file isn't working, either - the file unlinks fine but I always get the message. *grin*)

The script provides output in the format of:

some.host.name [ip: 111.222.333.444] is alive 70.05 ms)
What I need is to sort that output by that last value there - the latency time. I don't suppose there's a sort wizard here, who can tell me what the sort in my script needs to be changed to, so that it works as needed? :-)

--Dave

Replies are listed 'Best First'.
Re: How to sort output?
by quester (Vicar) on Jul 22, 2007 at 07:00 UTC
    Try replacing your calculation of @sorted with a Schwartzian Transform, something like this:
    my @sorted= map { $_->[0] } sort { $a->[1] cmp $b->[1] } map { [$_, ( / (\d*\.\d*) ms/ and $1 or 0 ) ] } @data;
    The part of the "expensive function" in merlyn's note is played by
    / (\d*\.\d*) ms/ and $1 or 0
    which just returns the first (and in this case only) thing that looks like a ping time in the current string. (Update: I should mention the "or 0" is to make sure any lines with no valid ping time sort to the bottom.)

    IMPORTANT NOTE: GrandFather++ is right about my absent-mindedly using "cmp" instead of "<=>". It should have been

    my @sorted= map { $_->[0] } sort { $a->[1] <=> $b->[1] } map { [$_, ( / (\d*\.\d*) ms/ and $1 or 0 ) ] } @data;

      It's a numeric sort so the spaceship (<=>) is appropriate rather than the string cmp operator. Consider how '2', '100' and '11' sort for example.


      DWIM is Perl's answer to Gödel
        Thanks, I've put that into play (with the suggested <=> replacement), and it seems to be working great!

        Thanks to everybody who responded!

        --Dave

Re: How to sort output?
by Zaxo (Archbishop) on Jul 22, 2007 at 07:11 UTC

    This is a good use for a Schwarzian transformation, though you could have set it up previously. Replacing line 50:

    my @sorted = map { $_->[1] } sort { $a->[0] <=> $b->[0] } map { [ /alive\s+(\d*\.\d*)\s+ms$/ ? ($1) : (), $_ ] } <IF>;
    Untested.

    The idea is to bundle the raw data with the extracted data to sort on, do the sort, then strip out the raw data again. It would be more thoughtful to keep the sort data from earlier. The regex I use could be slimmed down, it's a first cut.

    All the lines which don't match get grouped together at the start with my construction.

    After Compline,
    Zaxo

Re: How to sort output?
by BrowserUk (Patriarch) on Jul 22, 2007 at 07:14 UTC

    This (untested) code should do the trick. It uses a regex to extract the numbers and a GRT (see A brief tutorial on Perl's native sorting facilities.) to make the sort reasonably efficient.

    ## some.host.name [ip: 111.222.333.444] is alive 70.05 ms) ## Updated: switched 'N' for 'd'; real data. my @sorted = map { unpack 'x[d] A*', $_ } sort map { my( $time ) = m[([\d.]+) ms)$]; pack 'd A*', $time, $_; } @data;

    The regex will need tweaking if the latency can produce numbers in other units. Eg. whole seconds or microseconds instead of milliseconds.

    You might need something like:

    my( $time, $units ) = m[([\d.]+) (ms|us|secs)]; my $scaler = $units eq 'us' ? 0.1 : $units eq 'secs' ? 1000 : 1; $time *= $scaler;

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: How to sort output?
by FunkyMonk (Chancellor) on Jul 22, 2007 at 07:25 UTC
    (Oh yea, and the "if" that's checking on the unlink to get rid of the file isn't working, either - the file unlinks fine but I always get the message. *grin*)
    Usually, if a Perl feature isn't working as I expect, my first port of call will be Perl's extensive documentation. Try typing perldoc -f unlink in your shell, or from a browser here.

    From unlink:

    Deletes a list of files. Returns the number of files successfully deleted
      Oh, foo. I totally read the example (that I found in an online tutorial site) backwards. Oops. :-) Thanks for straightening me out on that, it's working now (well, it worked before - it's just not giving me a false failure message anymore *grin*).

      --Dave

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://628086]
Approved by Zaxo
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having a coffee break in the Monastery: (4)
As of 2024-03-28 15:50 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found