Perl accessing a hash within a hash and looping through each to extract the value - perl

I am trying to access a hash within a hash and loop through it to get the values. here is an example of the data
$VAR1 = {
'24.40.53.143' => {
'ServStat' => {
'1.18.118.115.95.99.119.98.98.112.109.45.97.112.95.104.116.116.112.115' => 'vs_cgggbpm-ap_https',
'1.17.118.115.95.99.119.98.115.102.97.45.97.112.95.104.116.116.112' => 'vs_cddedsfa-ap_http',
'20.18.118.115.95.99.119.98.116.119.98.45.98.112.95.104.116.116.112.115' => '0',
'19.17.118.115.95.99.119.98.119.115.45.97.112.95.104.116.116.112.115' => '0',
'2.18.118.115.95.99.119.98.116.119.98.45.98.112.95.104.116.116.112.115' => '0',
'24.18.118.115.95.99.119.98.116.119.98.45.97.112.95.104.116.116.112.115' => '0',
'17.17.118.115.95.99.119.98.119.98.45.97.112.95.104.116.116.112.115' => '0',
'29.17.118.115.95.99.119.98.116.119.112.45.98.112.95.104.116.116.112' => '0',
I would like to loop through 'ServStat' and extract each values. How would I reference the hash 'ServStat' so that I can do a foreach on the contents? Something like this:
foreach {ServStat} {
my ( $num, $char, $vs ) = (/(\d+)\.(\d+)\.(.+)/ );
if ($num == 1) {
print {ServStat}->$value
}
}
Thank you in advance for any advise you can offer!

To get the keys, you can use the function keys on the hash.
my $data = {
'24.40.53.143' => {
'ServStat' => {'1.18.118.115.95.99.119.98.98.112.109.45.97.112.95.104.116.116.112.115' => 'vs_cgggbpm-ap_https'}
}
};
my $ServStat = $data->{24.40.53.143}{ServStat};
foreach my $key (keys %{$ServStat}) { # you need the {} to dereference as $ServStat is a hash reference
...#Now, in $key, you have the key 1.18.118.115.95.99.119.98.98.112.109.45.97.112.95.104.116.116.112.115
}
If you just want all values, just use the function values on the hash
my #values = values %{$ServStat};

Related

Working with structures and hashes in Perl

Consider the following structure in Perl: (let's call it declaration A)
my $json_struct = {
name => $name,
time => $time,
};
I have a hash %hash which contains custom fields (I don't know how many). It looks something like this:
$VAR1 = {
'key2' => '123',
'key1' => 'abc',
'key3' => 'xwz'
};
I would like to loop through the hash keys and insert those keys into the structure, so I thought that I can do something like this:
foreach my $key (keys %hash) {
push #{ $json_struct }, { $key => $hash{$key} };
}
I'm not sure that it is working as expected. Also, is there a cleaner way to do so? Maybe I can combine it in one or two lines while declaring A.?
Expected output: (order does not matter)
$VAR1 = {
'name' => $name,
'time' => $time,
'key2' => '123',
'key1' => 'abc',
'key3' => 'xwz'
};
$json_struct is a hash reference, but #{ $json_struct } performs array dereferencing on $json_struct, so that is not going to work.
There is no push operator for hashes; you just insert new data by assigning values to new keys. For your structure, you would just want to say
foreach my $key (keys %hash) {
$json_struct->{$key} = $hash{$key};
}
Now you can also use the #{...} operator to specify a hash slice, which may be what you were thinking of. Hash slices can be used to operate on several keys of a hash simultaneously. The syntax that will work for you for that operation is
#{$json_struct}{keys %hash} = values %hash;
The easiest way to join hashes is like this:
my $foo = {
name => $name,
time => $time,
};
my $bar = {
'key2' => '123',
'key1' => 'abc',
'key3' => 'xwz'
};
my $combined = {
%{$foo},
%{$bar},
};

Perl get value of a key

I have been stuck in trying to create an array of keys (example_com,example_ca ..etc) if they are set to 1, I have tried using for loop and foreach loop, but keep getting ARRAY# error.
$VAR1 = [
{
'example_com' => '1',
'example_ca' => '1'
}
];
Thanks
This will be because you have an array containing a hash. The array is one element long.
So you 'get' to the hash, by dereferencing element zero.
Thus:
my $hash_ref = $VAR1->[0];
print join "\n", keys %{$hash_ref},"\n";
foreach my $key ( keys %{$VAR1->[0]} ) {
print "$key => $VAR1->[0]{$key}\n";
}
Exactly for you source data:
my #array_of_keys = ();
for( keys %{ $VAR1->[0] } ) {
push #array_of_keys, $_ if $VAR1->[0]{ $_ } eq '1';
}
print "Keys with 1: #array_of_keys";
An expanded example of how to get an array of keys if you have multiple hashes in your container array:
my $VAR1 = [
{
'example_com' => '1',
'example_ca' => '1',
'not_set' => '0'
},
{
'EXAMPLE_com' => '1',
'EXAMPLE_ca' => '1',
'NOT_SET' => '0',
}
];
my #arrayOfHashes = #{$VAR1};
foreach my $array (#arrayOfHashes)
{
my #onlyOnes;
my #arrayOfKeys = sort keys %{$array};
foreach my $key (#arrayOfKeys)
{
next if ($array->{$key} ne 1);
push #onlyOnes, $key;
}
print "\nKey names:\n";
foreach my $key (#onlyOnes)
{
print "$key\n";
}
}
output:
Key names:
example_ca
example_com
Key names:
EXAMPLE_ca
EXAMPLE_com

Nested Perl Hash

I'm trying to take the value from the hash and use it as the key for the next level hash, this code works, but I am thinking there has to be a better way to do this.
Is there a way to avoid the K* array variables?
my #key = keys %data3;
my $deviceType = $key[0];
my #K = keys %{$data3 {$deviceType} }; ## Uber Ugly, find a better way.
my $measInfoID = $K[0];
my #K1 = keys %{$data3 {$deviceType} {$measInfoID}};
my $deviceID = $K1[0];
my #K2 = keys %{$data3 {$deviceType} {$measInfoID} {$deviceID}};
my $location = $K2[0];
my #K3 = %{$data3 {$deviceType} {$measInfoID} {$deviceID} {$location}};
my $measObjectLdn = $K3[0];
print ("Data: $deviceType, $measInfoID, $deviceID, $location, $measObjectLdn\n");
foreach my $m ( keys %{ $data3 {$deviceType} {$measInfoID} {$deviceID} {$location} {$measObjectLdn} } ){
print("OK: $m\n");
}
%data3 looks like this:
$VAR1 = 'NTHLRFE';
$VAR2 = {
'DIA' => {
'NJBB-HLR-2' => {
'NJBB' => {
'LTE-1/DIA-1' => {
'DiaUnknownAVPs' => '0',
'DiaCerRejSysInOvl' => '0',
'DiaProtocolErrors' => '0',
'DiaWrongProtType' => '0',
'DiaMessageRcvd' => '0',
'DiaOctetSent' => '0',
'DiaCerRejPrNtInWhtLst' => '0',
'DiaOctetRcvd' => '0',
'DiaMessageDscrd' => '0',
'DiaCerRejConAvailable' => '0',
'DiaMessageSent' => '0',
'DiaCerRejMaxConExcd' => '0'
}
}
}
}
};
Thanks!
Keys aren't sorted, so the "first key" won't always be the same.
That said, if you want to compact this a bit, the following should work:
my $deviceType = (keys %data3)[0];
....
my $measInfoID = (keys $data3{$deviceType})[0];
....
my $deviceID = (keys $data3{$deviceType}{$measInfoID})[0];
This is ugly and I would not want to be the guy who comes after you reading this.
Keys will work on hash refs as well as hashes starting with Perl 5.14. If you're older than that, you'll just have to dereference into a full on hash.

How do I sort hash of hashes by a sub key/value using perl?

I would like sort the following Hash. parentXX should be sort with the value of __displayorder, the Xtopic of a parent and Xprod of a topic should sort alphabeticly.
$VAR1 = {
'views' => {
'parent23' => {
'__displayorder' => 2,
'vtopic1' => {
'gprod1.1' => undef,
'aprod1.2' => undef,
},
'btopic2' => {
'tprod2.1' => undef,
'mprod2.2' => undef,
},
},
'parent98' => {
'__displayorder' => 1,
'atopic1' => {
'qprod1.1' => undef,
'jprod1.2' => undef,
},
'xtopic2' => {
'kprod2.1' => undef,
'fprod2.2' => undef,
}
}
}
}
You can't sort a hash. Can you make do with having the names of the views in the specific order?
my $views = $VAR1->{views};
my #sorted_view_keys = sort {
$views->{$a}{__displayorder} cmp $views->{$b}{__displayorder}
} keys(%$views);
Or maybe you want the sorted views?
my #sorted_views = map { $views->{$_} } #sorted_view_keys;
-or-
my #sorted_views = #$views[#sorted_view_keys];
As already mentioned, you can't sort a normal Perl hash. Perl hashes are unordered. But you can use the CPAN module Tie::IxHash to get an ordered hash. The lines below transform all the sub-hashes from your sample output into Tie::IxHash hashes and do some sorting (e.g. alphabetically or by display order):
use Tie::IxHash;
my $views = $VAR1->{views};
while(my($view_key, $view) = each %$views) {
while(my($topic, $prods) = each %$view) {
next if $topic =~ m{^__};
tie my %new_prods, 'Tie::IxHash', (map { ($_ => $prods->{$_}) } sort keys %$prods);
$view->{$topic} = \%new_prods;
}
tie my %new_view, 'Tie::IxHash', (map { ($_ => $view->{$_}) } sort keys %$view);
$views->{$view_key} = \%new_view;
}
tie my %new_views, 'Tie::IxHash', (map { ($_ => $views->{$_}) } sort { $views->{$a}->{__displayorder} <=> $views->{$b}->{__displayorder} } keys %$views);
$VAR1->{views} = \%new_views;

Perl - Removing unwanted elements from an arrayref

I'm writing a script that parses the "pure-ftpwho -s" command to get a list of the current transfers. But when a user disconnects from the FTP and reconnects back and resumes a transfer, the file shows up twice. I want to remove the ghosted one with Perl. After parsing, here is what the arrayref looks like (dumped with Data::Dumper)
$VAR1 = [
{
'status' => 'DL',
'percent' => '20',
'speed' => '10',
'file' => 'somefile.txt',
'user' => 'user1',
'size' => '14648'
},
{
'status' => 'DL',
'percent' => '63',
'speed' => '11',
'file' => 'somefile.txt',
'user' => 'user1',
'size' => '14648'
},
{
'status' => 'DL',
'percent' => '16',
'speed' => '60',
'file' => 'somefile.txt',
'user' => 'user2',
'size' => '14648'
}
];
Here user1 and user2 are downloading the same file, but user1 appears twice because the first one is a "ghost". What's the best way to check and remove elements that I don't need (in this case the first element of the arrayref). The condition to check is that - If the "file" key and "user" key is the same, then delete the hashref that contains the smaller value of "percent" key (if they're the same then delete all except one).
If order in the original arrayref doesn't matter, this should work:
my %users;
my #result;
for my $data (#$arrayref) {
push #{ $users{$data->{user}.$data->{file}} }, $data;
}
for my $value (values %users) {
my #data = sort { $a->{percent} <=> $b->{percent} } #$value;
push #result, $data[-1];
}
This can definitely be improved for efficiency.
The correct solution in this case would have been to use a hash when parsing the log file. Put all information into a hash, say %log, keyed by user and file:
$log{$user}->{$file} = {
'status' => 'DL',
'percent' => '20',
'speed' => '10',
'size' => '14648'
};
etc. Latter entries in the log file would overwrite earlier ones. Alternatively, you can choose to overwrite entries with lower percent completed with ones that have higher completion rates.
Using a hash would get rid of a lot of completely superfluous code working around the choice of the wrong data structure.
For what it's worth, here's my (slightly) alternative approach. Again, it doesn't preserve the original order:
my %most_progress;
for my $data ( sort { $b->{percent} <=> $a->{percent} } #$data ) {
next if exists $most_progress{$data->{user}.$data->{file}};
$most_progress{$data->{user}.$data->{file}} = $data;
}
my #clean_data = values %most_progress;
This will preserve order:
use strict;
use warnings;
my $data = [ ... ]; # As posted.
my %pct;
for my $i ( 0 .. $#{$data} ){
my $r = $data->[$i];
my $k = join '|', $r->{file}, $r->{user};
next if exists $pct{$k} and $pct{$k}[1] >= $r->{percent};
$pct{$k} = [$i, $r->{percent}];
}
#$data = #$data[sort map $_->[0], values %pct];
my %check;
for (my $i = 0; $i <= $#{$arrayref}; $i++) {
my $transfer = $arrayref->[$i];
# check the transfer for user and file
my $key = $transfer->{user} . $transfer->{file};
$check{$key} = { } if ( !exists $check{$key} );
if ( $transfer->{percent} <= $check{$key}->{percent} ) {
# undefine this less advanced transfer
$arrayref->[$i] = undef;
} else {
# remove the other transfer
$arrayref->[$check{$key}->{index}] = undef if exists $check{$key}->{index};
# set the new standard
$check{$key} = { index => $i, percent => $transfer->{percent} }
}
}
# remove all undefined transfers
$arrayref = [ grep { defined $_ } #$arrayref ];
Variation on the theme with Perl6::Gather
use Perl6::Gather;
my #cleaned = gather {
my %seen;
for (sort { $b->{percent} <=> $a->{percent} } #$data) {
take unless $seen{ $_->{user} . $_->{file} }++;
}
};