table of hash: efficiently count hashes matching a key - arrays

I want to check the json output of ip -j adds show eno1 in perl.
I want to count how many ipv4 addr the Nic has. I still need the ipv6 info so I want to avoir running twice /usr/bin/ip command with the -4 and then -6 flag.
For now I can access to the ip information like this:
$nic->{addr_info}[0]->{local}
For each entry, the $nic->{addr_info}[$ip_info_index]->{family} will give the IP type (inet or inet6)
In most cases 2 entries in my table: one for v4 and one for v6, but sometimes I have 2 v4 entries a and want to issue a warning "not supported" in my software.
$nic->{addr_info}[$ip_info_index]->{family} will give the type of entry.
Is there some elegant trick using map and scalar to count how many $nic->{addr_info}[$ip_info_index]->{family} are equal to 'inet' (and not 'inet6')
(I can loop over $ip_info_index , and increment a counter each time I see 'inet', but that seems not elegant).
DB<3> p Dumper($nic)
$VAR1 = {
'txqlen' => 1000,
'address' => '00:26:b9:7d:c0:ee',
'broadcast' => 'ff:ff:ff:ff:ff:ff',
'link_type' => 'ether',
'group' => 'default',
'mtu' => 1500,
'qdisc' => 'mq',
'flags' => [
'BROADCAST',
'MULTICAST',
'UP',
'LOWER_UP'
],
'operstate' => 'UP',
'ifindex' => 2,
'addr_info' => [
{
'valid_life_time' => 30949,
'preferred_life_time' => 30949,
'label' => 'eno1',
'family' => 'inet',
'scope' => 'global',
'noprefixroute' => bless( do{\(my $o = 1)}, 'JSON::PP::Boolean' ),
'prefixlen' => 24,
'local' => '172.16.59.72',
'broadcast' => '172.16.59.255',
'dynamic' => $VAR1->{'addr_info'}[0]{'noprefixroute'}
},
{
'family' => 'inet6',
'local' => 'fe80::226:b9ff:fe7d:c0ee',
'valid_life_time' => 4294967295,
'preferred_life_time' => 4294967295,
'prefixlen' => 64,
'scope' => 'link'
}
],
'ifname' => 'eno1'
};
for addr_info table I wantt to count how many hashes have 'family' => 'inet' (and same for 'inet6').
I need to fail if more that one ipv4 or one ipv6 is set.
if you want to test on a linux system, the $nic is obtained like this:
my $ip_addr_output = `LC_ALL=C /sbin/ip -j addr 2>/dev/null`;

I think this is what you want.
$nic->{addr_info} is a reference to an array where each element describes one of the IP addresses attached to the interface.
So #{ $nic->{addr_info} } dereferences that array so you can now pass it to functions that require arrays or lists. One such function is grep which filters a list and only returns elements in the list which satisfy some criteria. We can therefore get a list of IPv4 addresses using:
grep { $_->{family} eq 'inet' } #{ $nic->{addr_info} }
If you call `grep in scalar context, it doesn't give you the list, it gives you the number of items in the list.
scalar grep { $_->{family} eq 'inet' } #{ $nic->{addr_info} }
So you can use something like this:
say "$nic->{ifname} : ", scalar grep { $_->{family} eq 'inet' } #{ $nic->{addr_info} };

Related

Why an array of array preserves my initial array?

So I have this fragment of code:
push #{$savedcallouts[-1]}, {
$funcnm => {
matches => {%$captures},
flags => [eval { #flags}]
}};
print Dumper \#{$savedcallouts[-1]};
Which gives the following result:
$VAR1 = [
[
{
'normalexpr' => undef
},
{
'normalexpr' => undef
},
{
'ternaryexpr' => undef
}
]
];
But if I remove the square brackets of flags => [eval { #flags}] (i.e. have flags => eval { #flags} - I get this:
$VAR1 = {
'begin_binary' => {
'HASH(0x1038301c0)' => {
'ternaryexpr' => undef
},
'flags' => {
'normalexpr' => undef
},
'matches' => {}
}
};
Any ideas why is this happening and how can I potentially avoid - i.e. have the array as hash field directly without artifacts or being an nested array.
First of all, eval { } is useless here. #flags isn't going to throw any exceptions.[1]
So
flags => eval { #flags }
is a weird way of writing
flags => #flags
flags => #flags
is a shorthand for
"flags", #flags
which is
"flags", $flags[0], $flags[1], $flags[2], ...
which is
"flags" => $flags[0],
$flags[1] => $flags[2],
...
have the array as hash field directly
The values of hash elements are scalars.
You can store a reference to an array in a scalar, but you can't store an array in a scalar.
Well, it's possible to add magic to #flags that throws an exception when accessed. But would you really want to ignore this exception? I can't fathom why you used eval here.

How to iterate on a conditional array in Perl?

I iterate through files in Perl and I'd like to get the correct "Adresse" field in the file. "Adresse" is a hash. Either the file contains only one "Adresse", and I take it, or it contains several "Adresse" and "Adresse" is actually an Array containing several "Adresse", and I just need the one having "type" = "postale".
Here is my code:
my $ad;
my $adresse;
if(ref($doc->{'Organisme'}->{'Adresse'}) eq 'ARRAY') {
print "\nI'M AN ARRAY!\n";
foreach $ad ($doc->{'Organisme'}->{'Adresse'}) {
print Dumper $ad;
if ($ad->{'type'} == 'postale') {
my $adresse = $ad;
}
}
} else {
my $adresse = $doc->{'Organisme'}->{'Adresse'}
}
print $fd $adresse->{'Ligne'};
I get the error:
Not a HASH reference at ./scripts/actualiserDonnees.pl line 35
and line 35 is:
if ($ad->{'type'} == 'postale') {
Apparently the "foreach" doens't iterate through "$doc->{'Organisme'}->{'Adresse'}" when the latter is an array, because the Dumper gives me this:
$VAR1 = [
{
'Localisation' => {
"Pr\x{e9}cision" => '8',
'Longitude' => '1.9751304',
'Latitude' => '43.2279035'
},
'type' => 'physique',
'CodePostal' => '11270',
"Accessibilit\x{e9}" => {
'type' => 'ACC'
},
'NomCommune' => 'Laurac',
'Ligne' => 'Place Blanche-de-Laurac'
},
{
'Ligne' => '8 rue du Pont',
'CodePostal' => '11270',
'type' => 'postale',
'NomCommune' => 'Laurac'
}
];
If I didn't explain myself enough, feel free to ask questions.
Thanks in advance :)
my $adresse creates a new variable. Replace both instances of my $adresse = ... with $adresse = ...
$doc->{'Organisme'}->{'Adresse'} is a scalar (a reference to an array), so foreach $ad ($doc->{'Organisme'}->{'Adresse'}) only loops over one item (the reference to an array). You want foreach $ad (#{ $doc->{'Organisme'}->{'Adresse'} })

Convert array to multidimensional hash

My task is convert array, containing hash with x keys to x-1 dimensional hash.
Example:
use Data::Dumper;
my $arr = [
{
'source' => 'source1',
'group' => 'group1',
'param' => 'prm1',
'value' => 1,
},
{
'source' => 'source1',
'group' => 'group1',
'param' => 'prm2',
'value' => 2,
},
];
my $res;
for my $i (#$arr) {
$res->{ $i->{source} } = {};
$res->{ $i->{source} }{ $i->{group} } = {};
$res->{ $i->{source} }{ $i->{group} }{ $i->{param} } = $i->{value};
}
warn Dumper $res;
my $res_expected = {
'source1' => {
'group1' => {
'prm1' => 1, # wasn't added, why ?
'prm2' => 2
}
}
};
However it doesn't work as expected, 'prm1' => 1 wasn't added. What is wrong and how to solve this task ?
The problem is that you are assigning to the source even if something was there, and you lose it. Just do a ||= instead of = and you'll be fine.
Or even easier, just use the fact that Perl autovivifies and leave that out.
my $res;
for my $i (#$arr) {
$res->{ $i->{source} }{ $i->{group} }{ $i->{param} } = $i->{value};
}
warn Dumper $res;
The first 2 lines in the for loop are what is causing your problem. They assign a new hash reference each iteration of the loop (and erase what was entered in the previous iteration). In perl, there is no need to set a reference as you did. Just eliminate the first 2 lines and your data structure will be as you wish.
The method you chose only shows 'prmt' => 2 because that was the last item entered.

Sorting an array of hashes, based on the value of a particular key within each hash in perl

I've done some searching and struggled to find the answer to this one.
Say I have an array of hashes like this..
my #AoH = (
{ 'ip_type' => 1,
'ip_ref' => 0001,
'ip_address' => '192.168.0.1',
'ip_priority' => '100'
},
{ 'ip_type' => 1,
'ip_ref' => 0002,
'ip_address' => '192.168.0.2',
'ip_priority' => '1'
}
);
In context, these are multiple IP addresses, in which I intend to load balance across. The 'ip_priority' value dictates which is first, and subsequently which is second in pirority. primary and backup, basically.
However, I want to order the array elements (aka the hashes) numerically based on the ip priority value within the hash.
So, preferably the above AoH would be...
my #AoH = (
{ 'ip_type' => 1,
'ip_ref' => 0002,
'ip_address' => '192.168.0.2',
'ip_priority' => '1'
},
{ 'ip_type' => 1,
'ip_ref' => 0001,
'ip_address' => '192.168.0.1',
'ip_priority' => '100'
}
);
so $AoH[0]->{ip_priority} now cotains 1 and $AoH[1]->{ip_priority} contains 100.
How can I perform a sort in order to achieve the second example above? im struggling to find a workable example!
You can use:
my #sorted = sort {$a->{ip_priority} <=> $b->{ip_priority}} #AoH;

Extracting an array of non=sibling hash values from a nested data structure in perl

This is my data structure created by Data::Dumper->Dumper:
$VAR1 = {
'name' => 'genomic',
'class' => [
{
'reference' => [
{
'name' => 'chromosome',
'referenced-type' => 'Chromosome'
},
{
'name' => 'chromosomeLocation',
'referenced-type' => 'Location'
},
{
'name' => 'sequence',
'referenced-type' => 'Sequence'
},
{
'name' => 'sequenceOntologyTerm',
'referenced-type' => 'SOTerm'
}
],
}
],
};
(trimmed for clarity)
I would like to return a reference to an array of each name value under reference in a single line.
Currently I have
$class->[0]{reference}[0..3]{name}
but no avail.
Also this example has four sibling-hashes with indexes 0..3, how can I represent the whole array independent of the number of elements?
There isn't an easy syntax to do that, unfortunately. You'll have to use map:
my $array_ref = [
map { $_->{name} } #{ $class->[0]{reference} }
];
Then, if you dump out $array_ref, you'll see it contains:
$array_ref = [
'chromosome',
'chromosomeLocation',
'sequence',
'sequenceOntologyTerm'
];
If you need references to the original strings (not copies), you just need a backslash before $_ (so it'd be \$_->{name} inside the map).
$class->[0]{reference} is an array reference, so you have to dereference it with #{}:
#{$class->[0]{reference}}
Is the 'whole array', you can then use slice syntax on the end to get a part of it:
#{$class->[0]{reference}}[0..3]
From there you're working with an array of hashrefs, so you'll have to iterate over it with for or map.

Resources