BioRod
BioRod

Reputation: 549

Output is not in numerical order

My Perl code is not returning the output in numerical order from the array.

I have this array of numbers

my @luns = qw/
    393645
    393646
    393730
    393731
    393732
    393733
    414632
    433944
/;

I want to execute a command and read the output through a pipe in Perl so I can iterate over the @luns array to search for specific values.

Here is the output of the command which is read through the pipe

primary-vds0     primary                                                  
/dev/dsk/c0txxxxxx393731d0s6
/dev/dsk/c0txxxxxx414632d0s6
/dev/dsk/c0txxxxxx393732d0s6
/dev/dsk/c0txxxxxx393733d0s6
/dev/dsk/c0txxxxxx393645d0s6
/dev/dsk/c0txxxxxx393646d0s6
/dev/dsk/c0txxxxxx393730d0s6
/dev/dsk/c0txxxxxx433944d0s6

Here is my script.

#!/bin/perl -w
use strict;

my @luns = qw/
    393645
    393646
    393730
    393731
    393732
    393733
    414632
    433944
/;

open(my $pipe, "ldm ls-services |") or die "Cannot open process: $!";

my $line;

while ( <$pipe> ) {

    foreach  $line ( @luns ) {

        if ( $_ =~ $line ) {

            print $_;
        }
    }
}

UPDATE

I have tried the suggested comment but it's not returning numerically.

while ( $line = <$pipe> ) {

    foreach ( @luns ) {

        if ($line =~ $_) {

            print "$_\n";
        }
    }
}

OUTPUT from comment is:

393731
414632
393732
393733
393645
393646
393730
433944

Upvotes: 1

Views: 109

Answers (2)

Borodin
Borodin

Reputation: 126742

The simplest approach is to read all of the output from the command into memory. That way it can be searched multiple times to see if any of the @luns array elements appear in the output

If @luns is pre-sorted then the output is in the same order. I've added a sort call to ensure that they are, but if the numbers are not always six digits long then you must also pad them to the correct length using leading zeroes; otherwise shorter LUNs that are substrings of longer ones will raise a false positive

Here, I've added a test to make sure that, if a LUN appears in the command output then it cannot be preceded or followed by another digit. That applies to your sample data but I can't be sure whether it will be generally true

If you have a later version of List::Util then you can use any in place of first

use strict;
use warnings;
use feature 'say';

use List::Util 'first';

my @luns = sort { $a <=>  $b } qw/
    393645
    393646
    393730
    393731
    393732
    393733
    414632
    433944
/;


my @data = <DATA>;
chomp @data;

for my $lun ( @luns ) {

    say $lun if first { /(?<!\d)$lun(?!\d)/ } @data;
}


__DATA__
primary-vds0     primary                                                  
/dev/dsk/c0txxxxxx393731d0s6
/dev/dsk/c0txxxxxx414632d0s6
/dev/dsk/c0txxxxxx393732d0s6
/dev/dsk/c0txxxxxx393733d0s6
/dev/dsk/c0txxxxxx393645d0s6
/dev/dsk/c0txxxxxx393646d0s6
/dev/dsk/c0txxxxxx393730d0s6
/dev/dsk/c0txxxxxx433944d0s6

output

393645
393646
393730
393731
393732
393733
414632
433944

Upvotes: 1

Casimir et Hippolyte
Casimir et Hippolyte

Reputation: 89574

You can't do it with only two nested loops because the records in your file aren't sorted. To solve the problem, you have to store the matching lines in an array and to sort this array. A trick to do that easily consists to concatenate the number at the beginning of each line. Then you only have to use a string comparison to sort the lines:

use strict;
use warnings;

my @luns = qw/
393645
393646
393730
393731
393732
393733
414632
433944
/;

my @result;

while (my $line = <DATA>) {
    for (@luns) {
        push @result, sprintf("%07d%s", $_, $line) if ($line =~ $_);
    }
}

@result = map { substr $_, 7 } sort @result;

print join '', @result;

__DATA__
primary-vds0     primary                                                  
/dev/dsk/c0txxxxxx393731d0s6
/dev/dsk/c0txxxxxx414632d0s6
/dev/dsk/c0txxxxxx393732d0s6
/dev/dsk/c0txxxxxx393733d0s6
/dev/dsk/c0txxxxxx393645d0s6
/dev/dsk/c0txxxxxx393646d0s6
/dev/dsk/c0txxxxxx393730d0s6
/dev/dsk/c0txxxxxx433944d0s6

Depending of the numbers sizes, you have to eventually change the number of leading zeros in the formatted string and the start offset in the substr function.


You can also do the same but this time with a numerical comparison:

my @result;

while (my $line = <DATA>) {
    for (@luns) {
        push @result, [ $_, $line ] if ($line =~ $_);
    }
}

@result = map { $_->[1] } sort { $a->[0] <=> $b->[0] } @result;

print join '', @result;

Upvotes: 1

Related Questions