Perl associative arrays and variable assignment - arrays

I'm having trouble understanding how this is supposed to work. I have defined my two hashes outside of the while loop. And the goal is to be able to detect the shell type, and assign the appropriate hash to a new $shell variable. Here is the code I'm current trying..
#!/usr/bin/perl
use strict;
use warnings;
use POSIX;
use DateTime;
use Term::ANSIColor qw(:constants);
local $Term::ANSIColor::AUTORESET = 1;
my $dt = DateTime->now; # Stores current date and time as datetime object
my %multicity = (
url => "example.com",
ftpuser => "user",
ftppass => "pass",
remote_dir => "/httpdocs/",
dbname => "database"
);
my %singlecity = (
url => "example2.com",
ftpuser => "user",
ftppass => "pass",
remote_dir => "/httpdocs/",
dbname => "database"
);
open (MYFILE, 'sites.txt');
LINE: while (<MYFILE>) {
next LINE if /^#/;
my ($shelltype, $siteurl, $ftpuser, $ftppass, $dbname) = split /\|/;
# 1|example.com|user|pass|databasename - This should be a singlecity shell type.
if ($shelltype == 1) { my $shell = \%multicity; } else { my $shell = \%singlecity; };
print "Shelltype: $shelltype\n";
print "Should be $shell{url}\n";
}
close (MYFILE);
I've tried many different things with no avail so I'm finally resorting to the pros here at stackoverflow to get some direction!
Any help is very much appreciated. Thank you.

Your my $shell is lexical inside of the if block. Move it outside the if or it is not available there. Adding some indentation helps to spot that.
my $shell;
if ($shelltype == 1) {
$shell = \%multicity;
} else {
$shell = \%singlecity;
};
After that, you will get a warning saying %shell is undefined. That's because you are using $shell{url} but you defined $shell as a hash ref, so you need $shell->{url} or $$shell{url} in your print.

Two issues:
Your my $shell variable is lexically scoped to the if statement. It won't be visible outside.
The $shell variable is a hashref. So you have to access elements using $shell->{url}. Note the arrow.
So the following should work:
my $shell;
if ($shelltype == 1) { $shell = \%multicity; } else { $shell = \%singlecity; };
print "Shelltype: $shelltype\n";
print "Should be $shell->{url}\n";

This line:
print "Should be $shell{url}\n";
should be:
print "Should be $shell->{url}\n";
The reason is that you are assigning a reference to one of your two hashes to $shell, so to access a value in it you need to de-reference (using the -> operator).
In addition, as pointed out by nwellholf, the $shell variable is local to the if statement so you would get a compilation error there.

Related

What is the difference between {} and ->{} in Perl?

So if I have some data object and I want to access whats inside the element of that object
Whats the difference between
$Data{isEnabled})
$Data->{isEnabled}
my data is basically like this
for my $characterData (#{$AllCharacters->{'characters'}}) {
$Data{isEnabled})
$Data->{isEnabled}
and i want to access certain elements of my characterData but I'm not sure when to use
$Data{isEnabled})
vs
$Data->{isEnabled}
Like for example why does the first print work but the second fails?
use strict;
use warnings;
my %info = (NAME => "John", HOST => "Local", PORT => 80);
print $info{PORT};
print $info->{PORT};
The first expression accesses a key within a hash:
my %data = (is_enabled => 1);
print $data{is_enabled}), "\n";
In the second expression, data is not a hash, but a *hash reference. It would typically be declared as:
my $data = { is_enabled => 1 };
Since this is a reference, we need to use the dereferencing operator (->) to access the hash content:
print $data->{is_enabled}, "\n";
If you are iterating through an array of hashes, as your code seems to show, then each element is a hash reference. You need to use the second syntax:
my #all_data = ( { is_enabled => 1 }, { is_enabled => 0 } );
for my $data (#all_data) {
print $data->{is_enabled}, "\n";
}
You can read more about references in the perlref documentation page.
$Data->{isEnabled}
is equivalent to
${ $Data }{isEnabled}
I prefer the "arrow" notation, but it serves my explanation better to compare
$Data{isEnabled}
with
${ $Data }{isEnabled}
In the first case ($Data{isEnabled}), we are accessing an element of a hash %Data.
In the second case, we also appear to have a hash lookup, but we have a block ({ $Data }) where a name would normally be expected. It is indeed a hash lookup, but instead of accessing a named hash, we are accessing a referenced hash. The block (or the expression to the left of the ->) is expected to return a reference to the hash the program should access.
A reference is a means of referencing a variable through it's location in memory rather than by its name. Consider the following example:
my $ref;
if (condition()) {
$ref = \%hash1;
} else {
$ref = \%hash2;
}
say $ref->{id};
This will print $hash1{id} or $hash2{id} based on whether condition() returns a true value or not.

Directly access nested JSON data in perl?

I'm not familiar with hash/reference syntax with Perl and it makes my eyes hurt trying.
I have the following JSON:
{
"Arg":"Custom_Light state alias protocol",
"Results": [
{
"Name":"Custom_Light",
"Internals": { },
"Readings": {
"protocol": { "Value":"V3", "Time":"2017-01-14 18:49:18" },
"state": { "Value":"off", "Time":"2017-03-05 10:39:50" }
},
"Attributes": { "alias": "Kitchen light" }
} ],
"totalResultsReturned":1
}
How do I directly get the Reading > Protocol Value and Reading > state Value as well as the Attributes > Alias?
I am using the default JSON encoder/decoder and it works splendid. Using Dumper($json) I get all the JSON, but I have no clue how to directly access it without using foreach with all the arrays within arrays in this.
I have tried the following:
my $json = from_json( $readout, { utf8 => 1 } );
print "No. Entries:", scalar(keys($json)); #works, returns 3
my #results = %$json{Results};
Dumper(#results[1]); #I get the Results array
From here it already is ugly. What's that %$ doing there? I thought I could do something like print ${ $json->{'Results'}->[1] }{'Readings'}; but that leads me nowhere.
Give me wisdom. How do I access the Protocol value directly? How do I access the state value directly? And finally, how to get to the alias Attribute?
I don't know what I'm doing but I'm getting somewhere with my $test = %{${%$json{Results}}[0]}{Name}; #I get "Custom_Light", nice. Is this the way to go with a gazillion of weird % and $ just randomly thrown in?
You want
$json->{Results}[0]{Readings}{protocol}{Value}
$json->{Results}[0]{Readings}{state}{Value}
$json->{Results}[0]{Attributes}{alias}
However, since the Results item is an array, you are likely to want to iterate over all of its elements, although in this case there is only one element
I find it useful to extract one level of reference at a time into temporary variables. It would look like this
my $results = $json->{Results};
for my $result ( #$results ) {
my $readings = $result->{Readings};
my $attributes = $result->{Attributes};
printf "Protocol: %s\n", $readings->{protocol}{Value};
printf "State: %s\n", $readings->{state}{Value};
printf "Alias: %s\n", $attributes->{alias};
print "\n";
}
Have a look at perlreftut, perldsc, and perlref, it will help you understand how to access deeply nested structures in Perl.
print "No. Entries:", scalar(keys($json)); #works, returns 3
Actually, this will no longer work. Using keys on a scalar, was an experimental feature added in Perl 5.14 that allowed each, keys, push, pop, shift, splice, unshift, and values to be called with a scalar argument. This experiment was considered unsuccessful, and was removed in 5.23. See also Experimental values on scalar is now forbidden. So, you should dereference the hash reference $json before applying keys:
print "No. Entries:", scalar keys %$json;
As described in perlref, %$ref dereferences the hash reference $ref. Next, lets look at this line:
my #results = %$json{Results};
This actually first creates a new (anonymous) hash ( Result => $json->{Result} ) and then assigns this to #results making #result = ( 'Result', $json->{Result} ). So that is why you now can refer to $json->{Result}[0] as $result[1].
But this is obscure coding, and probably not intended as well. So to return to your question, to get the Value field you could write:
my $value = $json->{Results}[0]{Readings}{state}{Value};
And to get the alias field:
my $alias = $json->{Results}[0]{Attributes}{alias};

Array reference versus scalar reference

I have this code segment to put together a hash of parameters which I will pass to a function. The hash value containing the IP address is supposed to be an array reference, but the function I'm passing my parameters to thinks it's a scalar reference.
My code is:
my $paramList = "ldap_ip_addresses=['192.168.1.100']|ldap_port=389|ldap_protocol=ldap";
my #paramTuples = split(/\|/, $paramList);
my %nasProps;
foreach my $paramTuple (#paramTuples) {
my($key, $val) = split(/=/, $paramTuple, 2);
# SetProperties can also take hashes or arrays
my $eval_val = eval $val;
if (ref($eval_val) =~ /ARRAY/) {
$val = \$eval_val;
}
$nasProps{$key} = $val;
}
From the debugger, my parameter hash looks like this:
DB<18> x \%nasProps
0 HASH(0x303f8f0)
'ldap_authentication_type' => 'anonymous'
'ldap_ip_addresses' => REF(0x303fa70)
-> ARRAY(0x8284eb8)
0 '192.168.1.100'
'ldap_port' => 389
'ldap_protocol' => 'ldap'
It looks like a reference to an array so I'm not sure where I'm going wrong.
Since $eval_val is already a reference to an array, there is no need to make a reference to the reference. Change:
$val = \$eval_val;
to:
$val = $eval_val;
You are unnecessarily taking the reference to a reference with
$val = \$eval_val;
You have established on the previous line that $eval_val is a reference to an array, so you can use it as it is without taking a reference to it again.
In addition, you should ignore the result of ref $eval_val except to check that it is true — i.e. $eval_val is a reference of some sort.
Your code should look more like this. You need to fall back to the original $val value only if eval returns undef, usually meaning that the string wasn't compilable code.
Note also that you should reserve capital letters for global Perl variables, such as package names. Lexical variable identifiers should contain only lower-case letters, decimal digits and underscores.
use strict;
use warnings;
my $param_list = "ldap_ip_addresses=['192.168.1.100']|ldap_port=389|ldap_protocol=ldap";
my #param_tuples = split /\|/, $param_list;
my %nas_props;
for my $param_tuple (#param_tuples) {
my ($key, $val) = split /=/, $param_tuple, 2;
$nas_props{$key} = eval($val) // $val;
}
use Data::Dump;
dd \%nas_props;
output
{
ldap_ip_addresses => ["192.168.1.100"],
ldap_port => 389,
ldap_protocol => "ldap",
}
Here is a short alternative in functional style:
my %nasProps =
map /\[/ ? eval : $_,
split /[|=]/, $paramList;
However, it only works if you can guarantee that = is not included in any parameter values.

hash inside perl structures

I try to create an array of perl structures. Each struct contains two scalars and a hash.
Later I want to find an item of the array, get the hash and find a scalar inside the hash.
I can find the item inside the array and get the scalars.
But I don't know hot to correctly get the hash and a value inside it.
I tried with/without reference.
Thanks a lot
#hash
%latestInfo = (
8 => '30',
);
#struct
package Myobj;
use Class::Struct;
struct( name => '$', majorVer => '$', latestVer => '%');
$w1 = new Myobj;
$w1->name('test');
$w1->majorVer(5);
$w1->latestVer($latestInfo);
#array with all version information
#versions=($w1, ...);
sub getVersionFromMajor
{
foreach $ver (#versions) {
if ($ver->majorVer eq $_[0]) {
return $ver;
}
}
}
#
#main
#
#ok: get version info from structures/array
local($ver) = getVersionFromMajor(5);
local($n) = $ver->name;
#fail: get hash inside item
my $latest = \$ver->latestVer;
%lat = $ver->latestVer;
#fail: get value inside hash
local($m) = $latest{8};
This bit:
$w1->latestVer($latestInfo);
Should be:
$w1->latestVer(\%latestInfo);
%latestInfo and $latestInfo are two unrelated variables - %latestInfo is your hash, and $latestInfo is an undeclared (and thus undef) scalar. \%latestInfo is a scalar reference to %latestInfo, which is what the latestVer method (created by Class::Struct) wants you to give it.
Perl would have told you about $latestInfo not existing if you'd done use strict and declared all your variables.
Also, this bit:
%lat = $ver->latestVer;
Should be:
%lat = %{ $ver->latestVer };

In perl how to compare two arrays of objects (comparison logic being coded in a separate subroutine)?

Let me be specific to my problem instead of generalizing it and confusing the audience. In my code I have set of network addresses (members of object-group actually) stored in individual arrays. I would like to compare whether Group A is a subset of Group B.
I am using Net::IP module to parse the IP addresses and use "overlaps" sub-routine to determine if an element (could be individual IP or a subnet) is a superset of another element.
The challenge I am facing is in returning success status only if each element of Group A, belongs to any one element of Group B.
Here is a way I thought of and proceeding to try to code it likewise:
$status = "match";
foreach $ip (#group_a) {
if a_in_b($ip,#group_b) #this sub-routine would be similar but with different comparison function
{
next;
}
else
{
$status = "no match";
last;}
}
Please suggest me if there is a better way to do it, would love to pick up new techniques. The above technique doesn't look sound at all! As I was searching for for some solutions, some references seem to suggest as if I could try using the smart match operator and overload it. But overloading is beyond my level of sophistication in perl, so kindly help!
EDIT:
Updated my code as per suggestion. Here is the working version (still need to add bits and pieces for error catching)
use Net::IP;
use strict;
use warnings;
my #subnet = ("10.1.128.0/24","10.1.129.0/24","10.1.130.0/24","10.1.108.4");
my #net = ("10.1.128.0/21","10.1.108.0/22");
sub array_subset {
my ($x, $y) = #_;
a_in_b ($_, #$y) or return '' foreach #$x;
return 1;
};
sub a_in_b {
my $node1 = shift(#_);
my #ip_list = #_;
for my $node2 (#ip_list) {
print $node2, "\n";
my $ip1 = new Net::IP ($node1) || die;
my $ip2 = new Net::IP ($node2) || die;
print "$node1 $node2 \n";
if ($ip1->overlaps($ip2)==$IP_A_IN_B_OVERLAP) {
return 1;
}
}
return "";
}
if (array_subset(\#subnet, \#net)) {
print "Matches";
}else
{
print "Doesn't match"
}
Overloading ~~ is a bit of overkill. I would suggest using List::MoreUtils:
use List::MoreUtils qw/all/;
if (all { a_in_b($_, #bignet) } #smallnet) {
# do something
};
Or just rewrite your own code as a sub, and in a more perlish way:
sub array_subset {
my ($x, $y) = #_;
a_in_b ($_, #$y) or return '' foreach #$x;
return 1;
};
# somewhere in the code
if (array_subset(\#subnet, \#net)) {
# do something
};

Resources