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

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.

Related

Perl ... create horizontal children of a %hash using #array items

I've been banging my head on this awhile and searched many ways. I'm sure this is going to boil down to being really basic.
I have data in an #array that I want to move to a tree in a %hash.
This might be something more appropriate to JSON? But I haven't delved into it before and I don't need to save out/restore this information.
Desire:
Create a dependent tree of USB devices that can nest under each other that can track the end point (deviceC) through a hub (deviceB) and finally the root (deviceA).
Example:
Simplified (I hope ... this isn't from the actual longer script):
I want to convert an array in this format:
my #array = ['deviceA','deviceB','deviceC'];
to multidimensional hashes equal to:
my %hash = ('deviceA' => { 'deviceB' => { 'deviceC' => '' } } )
that would dump like:
$VAR1 = {
'deviceA' => {
'deviceB' => {
'deviceC' => ''
}
}
};
For just looking at a single device this isn't necessary, but I'm building out an IOMMU -> PCI Device -> USB map that contains many devices.
NOTES:
I'm trying to avoid installing CPAN modules so the script is to similar systems (Proxmox VE)
The last device (deviceC above) has no children
value '' is fine
undef would probably work
mixing the types would work but I need to know how to set that
I will never need to modify or manipulate the hash once created
I don't know the right way to recurse the #array to populate the %hash children. * I want the data horizontal for each USB device
I'd switch to an Object/package but each device can have a different set of children (or none) making it infeasible to know Object names
Some USB devices have no children (root hubs) ... similar to %hash = ('deviceA' => '')
Some have 1 child that is the final device ... similar to %hash = ('deviceA' => { 'deviceB' =>'' } )
Some have multiple steps between the root via additional hub(s) ... similar to %hash = ('deviceA' => { 'deviceB' => { 'deviceC' => '' } } ) or more
Starting point :
This is basic and incomplete but will run:
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper qw(Dumper);
# data in from parsing usb device path:
my #array = ['deviceA','deviceB','deviceC'];
# needs to be converted to:
my %hash = ('deviceA' => { 'deviceB' => { 'deviceC' => '' } } );
print "\n\%hash:\n" . Dumper \%hash;
Pseudo-code
This section is NOT working code in any form. I'm just trying to make a note of what I'm thinking. I know the format is wrong, I've tried multiple ways to create this and I'd look even dumber showing all of my attempts :)
I'm very new to refs and I'm not going to try and get that right here. The idea below is:
For each item in #array:
Create a way (either a ref or a copy of the current hash) that can be used next iteration to place the next child
Attach item as a child of the previous iteration with an empty value (that can be appended if there is further iteration)
my #array = ['deviceA','deviceB','deviceC'];
my %hash = {};
my %trackref;
for (#array) {
%trackref = %hash; # a copy of the existing that won't change when %hash updates
$hash{last_child} ::append_child:: $_;
}
You're actually pretty close, but it seems that you need to understand references a bit better. perldoc perlref is probably a good starting point to understand references.
A few mistakes in your code, before looking at the solution:
my #array = [ ... ];: [] creates an arrayref, not an array, which means that #array actually stores a single scalar item: a reference to another array. Use () to initialize an array: my #array = ( ... );.
my %hash = {};: similarly, {} creates a hashref, not a hash. Which means that this lines stores a single hashref in %hash, which will cause this warning: Reference found where even-sized list expected at hash.pl line (because a hash contains keys-values and you only provided a key). Use () for a simple (ie, not a hashref) hash. In this case however, you don't need to initialize %hash: my %hash; and my %hash = () do the same thing (that is, create an empty hash).
%trackref = %hash; copies the content of %hash in %trackref. Which means that, contrary to what the name "trackref" implies, %trackref doesn't contain a reference to anything, but a copy of %hash. Use \%hash to create a reference to %hash.
Note that if you already have a hashref, then assigning it to another variables copies the reference. For instance, if you do my $hash1 = {}; my $hash2 = $hash1, then both $hash1 and $hash2 reference the same hash.
So, fixing those issues in your attempt, we get:
my #array = ('deviceA','deviceB','deviceC');
my %hash;
my $trackref = \%hash;
for my $usb (#array) {
$trackref->{$usb} = {};
$trackref = $trackref->{$usb};
}
print Dumper \%hash;
Which outputs:
$VAR1 = {
'deviceA' => {
'deviceB' => {
'deviceC' => {}
}
}
};
The main change that I did was to replace your $hash{last_child} ::append_child:: $_; by $trackref->{$_} = {};. But the idea remains the same: Attach item as a child of the previous iteration with an empty value to reuse your words.
To help you understand the code a bit better, let's see what happens in the loop step by step:
Before the first iteration, %hash is empty and $trackref references %hash.
In the first iteration, we put deviceA => {} in $trackref (or, more pedantically, we associate {} with the key deviceA in $trackref). Since $trackref references %hash, this puts deviceA => {} in %hash. Then, we store in $trackref this new {} that we just created, which means that $trackref now references $hash{deviceA}.
In the second iteration, we put deviceB => {} in $trackref. $trackeref references $hash{deviceA} (which we created in the previous iteration), which means that %hash is now (deviceA => { deviceB => {} }). We then store in $trackref the new {}.
And so on...
You'll note that in the innermost hash, {} is associated to the key deviceC. When iterating of the hash, you can thus know if you are at the end by doing something like if (%$hash) (instead of just if ($hash) if this last {} would have been undef or ''). Let me know if that's an issue: we can add a bit of code to convert this {} into undef (alternatively, you can do it yourself, it will be a good exercise to get used to references)
Minor remark: #array and %hash are poor array and hash names, because the # already indicates an array, and % already indicates a hash. It's possible that you used those names just for this small example for your question, in which case, no problem. However, if you use those names in your actual code, consider changing them for something more explicit... #usb_devices and %usb_devices_tree maybe?

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 };

Referenced array dropped in size to one element

Dear fellow perl programmers,
I wanted to access to this array
my #vsrvAttribs = qw(
Code
Description
vsrv_id
vsrv_name
vsrv_vcpu_no
vsrv_vmem_size
vsrv_vdspace_alloc
vsrv_mgmt_ip
vsrv_os
vsrv_virt_platf
vsrv_owner
vsrv_contact
vsrv_state
);
through a variable composed of a variable and a string suffix, which of course led to the error message like this
Can't use string ("#vsrvAttribs") as an ARRAY ref while "strict refs" in use at cmdbuild.pl line 262.`
Therefore I decided to get the reference to the array through a hash
my %attribs = ( vsrv => #vsrvAttribs );
And this is the code where I need to get the content of aforementioned array
foreach my $classTypeKey (keys %classTypes) {
my #attribs = $attribs{$classTypeKey};
print Dumper(\#attribs);
}
It seems I can get the reference to the array #vsrvAttribs, but when I checked the content of the array with Dumper , the array have got only one element
$VAR1 = [
'Code'
];
Do you have any idea where could be the problem?
How do you store the array in a hash and access it later?
You need to store your array by reference like this:
my %attribs = ( vsrv => \#vsrvAttribs );
Note the backslash before the # sigil. This tells perl that you want a reference to the array.
Then when access the array stored in $attribs{vsrv} you need to treat it as a reference instead of as an array. You'll do something like this:
foreach my $classTypeKey (keys %classTypes) {
# make a copy of the array by dereferencing
my #attribs = #{ $attribs{$classTypeKey} };
# OR just use the array reference if profiling shows performance issues:
my $attribs = $attribs{$classTypeKey}
# these will show the same thing if you haven't done anything to #attribs
# in the interim
print Dumper(\#attribs);
print Dumper($attribs);
}
Why did you only get one value and where did the rest of the array go?
Your missing values from #vsrvAttribs weren't lost they were assigned as keys and values to %attribs itself. Try adding the following just after you made your assignment and you'll see it for yourself:
my %attribs = ( vsrv => #vsrvAttribs );
print Dumper(\%attribs);
You'll see output like this:
$VAR1 = {
'vsrv_contact' => 'vsrv_state',
'vsrv_virt_platf' => 'vsrv_owner',
'vsrv' => 'Code',
'vsrv_name' => 'vsrv_vcpu_no',
'vsrv_mgmt_ip' => 'vsrv_os',
'Description' => 'vsrv_id',
'vsrv_vmem_size' => 'vsrv_vdspace_alloc'
};
This is because perl interpreted your assignment by expanding the contents #vsrvAttribs as multiple arguments to the list literal ():
my %attribs = (
# your key => first value from array
vsrv => 'Code',
# subsequent values of the array
Description => 'vsrv_id',
vsrv_name => 'vsrv_vcpu_no',
vsrv_vmem_size => 'vsrv_vdspace_alloc',
vsrv_mgmt_ip => 'vsrv_os',
vsrv_virt_platf => 'vsrv_owner',
vsrv_contact => 'vsrv_state',
);
This is legal in perl and there are reasons where you might want to do this but in your case it wasn't what you wanted.
Incidentally, you would have been warned that perl was doing something that you might not want if you had an even number of elements in your array. Your 13 elements plush the hash key "vsrv" makes 14 which is even. Perl will take any list with an even number of elements and happily make it into a hash. If your array had another element for 15 elements total with the hash key you would get a warning: Odd number of elements in hash assignment at foo.pl line 28.
See "Making References" and "Using References" in perldoc perlreftut for more information.
If you use a bare array in a hash definition like
my %attribs = ( vsrv => #vsrv_attribs )
the array is expanded and used as key/value pairs, so you will get
my %attribs = (
vsrv => 'Code',
Description => 'vsrv_id',
vsrv_name => 'vsrv_vcpu_no',
vsrv_vmem_size => 'vsrv_vdspace_alloc',
...
)
The value of a Perl hash element can only be a scalar value, so if you want an array of values there you have to take a reference, as shown below
It is also a bad idea to use capitals in Perl identifiers for anything except globals, such as package names. Local names are conventional lower-case alphanumeric plus underscore, so $class_type_key instead of $classTypeKey
use strict;
use warnings;
use Data::Dumper;
my #vsrv_attribs = qw(
Code
Description
vsrv_id
vsrv_name
vsrv_vcpu_no
vsrv_vmem_size
vsrv_vdspace_alloc
vsrv_mgmt_ip
vsrv_os
vsrv_virt_platf
vsrv_owner
vsrv_contact
vsrv_state
);
my %attribs = (
vsrc => \#vsrv_attribs,
);
for my $class_type_key (keys %attribs) {
my $attribs = $attribs{$class_type_key};
print Dumper $attribs;
}
output
$VAR1 = [
'Code',
'Description',
'vsrv_id',
'vsrv_name',
'vsrv_vcpu_no',
'vsrv_vmem_size',
'vsrv_vdspace_alloc',
'vsrv_mgmt_ip',
'vsrv_os',
'vsrv_virt_platf',
'vsrv_owner',
'vsrv_contact',
'vsrv_state'
];

Resources