Multidimensional arrays using integers and strings - perl

I am trying to setup a basic error checking system where it will catch shell errors run by a system call. execute_command is a webmin function that runs a system call and then sets an error message to its 4th parameter. I basically call execute_command_error("adduser test"), knowing that I already have a user called test created and based on my predefined arrays, id expect it to print
Unable to add userUnable to
add that user because it already
exists on the system.
but instead I get:
Uhhhhhhhhh? Uhhhhhhhhh?
I have verified that $exe and $return are "adduser" and 1, respectifully.
What am I not understanding about arrays? It seems to ignore the string and or number and just go by the last definition with 3 elements. What is a solution to this, or a better solution?
Here is ths code:
$ErrorMsg['adduser',1,'title'] = "Unable to add user";
$ErrorMsg['adduser',1,'msg'] = "Unable to add that user because it already exists on the system.";
$ErrorMsg['random',2,'duaisdhai'] = "Uhhhhhhhhh?";
sub execute_command_error
{
my $error = "";
my $cmd = $_[0];
$return = execute_command($cmd, undef, undef, \$error)>>8;
if ($error) {
my ($exe) = $cmd =~ m|^(.*?)[ ]|;
$exe_title = $ErrorMsg[$exe,$return,'title'];
$exe_msg = $ErrorMsg[$exe,$return,'msg'];
print $exe_title."<br>";
print $exe_msg ."<br>";
}
}
Update:
I am thinking that I need to use hashes, I have no idea why I thought I could use strings in indices. With that said, little research has led me to something like this:
%ErrorMsgs = ('adduser' => {
'1' => {
'title' => 'Unable to add user',
'msg' => 'Unable to add that user because it already exists on the system.',
},
},
);
Now how would I reference it using a variable? because neither of these work:
$exe_title = $ErrorMsgs{"$exe"}{"$return"}{"title"};
$exe_title = $ErrorMsgs{$exe}{$return}{title};

First, see perldsc for the proper syntax for doing multidimensional structures. Your arrays don't make any sense.
If you had warnings turned on, you would have seen a "Argument isn't numeric" warning to tell you that you can't use strings in any meaningful way in an array index.
But the hash you posted in your update should work fine.
#!/usr/bin/perl
use strict;
use warnings;
## ^^ these things are your friends
my %ErrorMsgs = ('adduser' => {
'1' => {
'title' => 'Unable to add user',
'msg' => 'Unable to add that user because it already exists on the system.',
},
},
);
my $exe = 'adduser';
my $return = 1;
print $ErrorMsgs{$exe}{$return}{title}; # works
If you're not getting the output you expect, it's because there's something wrong with $exe or $return -- they might not be defined in the scope where you're trying to use them. Turning on strict and warnings will help track the issue down.

{ 'key' => 'val' } creates a hash reference, so you dereference before looking up a key.
$exe_title = $ErrorMsgs{$exe}->{$return}->{"title"};
You also don't need to quote $exe or $return, since these already hold strings.
Note that Perl doesn't support multidimensional indices; a multidimensional array is just an array of arrays, so you need to use [] for each index. In scalar context, the comma operator returns the value of the rightmost expression, so the following lines are equivalent:
$ErrorMsg[0,1,2] = "foo";
$ErrorMsg[2] = "foo";
Note that in list context, the comma operator returns a list of values, which gives us slices:
#a=qw(f o o);
#a[3,4,5] = qw(b a r);
print join(',', #a), "\n";
# output: f,o,o,b,a,r
#ErrMsg{qw(title msg)} = ('Unable to add user', 'Unable to add that user because it already exists on the system.')

Related

How do I decipher an array of hashes?

I totally got this question wrong. Am using the method from TMDB:
my #results = $search->find(id => 'tt0114694', source => 'imdb_id');
I thought the output was in JSON format, so that's what confused me, which kept me running in circles because I was looking at it all wrong.
Didn't realize the data below, from Dumper, was the actual hashes the I had to go through.
This is where I am running into a wall, So the data below is a hash with five keys. The fifth key, the I want, contains another array. It is that array I cannot read into. I try dereferencing that into a hash, and that is where I fail.
The code I am trying is:
foreach my $narray (#results){
print $narray->{"movie_results"};
my #newarray = $narray->{"movie_results"};
foreach my $otherarray (#newarray){
my %innerhash = $otherarray;
print %innerhash;
print "\n";
}
}
It will print out an array, but I am unable to read the hash in that array.
p.s. I had to format this output as code, or else it came out with no line breaks.
$VAR1 = {
'tv_season_results' => [],
'tv_results' => [],
'person_results' => [],
'tv_episode_results' => [],
'movie_results' => [
{
'adult' => bless( do{\(my $o = 0)}, 'JSON::PP::Boolean' ),
'vote_average' => '6.8',
'original_title' => 'Tommy Boy',
'vote_count' => 635,
'id' => 11381,
'release_date' => '1995-03-31',
'overview' => 'Party animal Tommy Callahan is a few cans short of a six-pack. But when the family business starts tanking, it\'s up to Tommy and number-cruncher Richard Hayden to save the day.',
'genre_ids' => [
35
],
'title' => 'Tommy Boy',
'video' => $VAR1->{'movie_results'}[0]{'adult'},
'poster_path' => '/g32WbO9nbY5ydpux5hIoiJkLEQi.jpg',
'original_language' => 'en',
'backdrop_path' => '/bZ4diYf7oyDVaRYeWG42Oify2mB.jpg',
'popularity' => '13.945'
}
]
};
You mention that you thought you'd get JSON output, but got something else. The module made a web request for you, received the JSON response, and translated that to a Perl data structure. That Perl version of the JSON is what you see in the dump.
A JSON object turns into a Perl hash, so that's what you see in the top level of the data structure. That's the single thing find returns (more on that in a moment):
Here's what you have, removing the outer foreach loop:
my #newarray = $narray->{"movie_results"};
foreach my $otherarray (#newarray){
my %innerhash = $otherarray;
print %innerhash;
print "\n";
}
The value in $narray->{"movie_results"} is an array reference. All references are scalars, and those scalars point to some data structure. When you assign that scalar to an array, you just end up with a one element array that's the same reference. Instead, you can
my $movie_results = $narray->{"movie_results"};
You then dereference that reference to treat it as an array:
foreach my $result ( #$movie_results ){ ... }
Or, the v5.24 postfix dereferencing way that I find slightly more pleasing since it reads better, especially when you skip the intermediate variable:
foreach my $result ( $movie_results->#* ){ ... }
foreach my $result ( $narray->{"movie_results"}->#* ){ ... }
That thing in $result is another hash reference.
References and data structures are about half of the content of Intermediate Perl, but there is also the Perl data structures cookbook (perldsc).
Improving your question a bit
You can help us a lot by showing us a complete, working demonstration of your problem. Here's what I cobbled together:
use v5.10;
use TMDB;
use Data::Dumper;
my $tmdb = TMDB->new( apikey => $ENV{TMDB_API_KEY} );
my #results = $tmdb->search->find(
id => 'tt0114694',
source => 'imdb_id'
);
say Dumper( \#results );
There was a question about the results of find. The documentation example shows it returning a list (well, the result being assigned to a named array, which implies that), but there's not actual documentation for find. It returns the decoded JSON from the response. Assigning it to a scalar (which will be a reference) works just fine too:
my $result = $tmdb->search->find(
id => 'tt0114694',
source => 'imdb_id'
);
say Dumper( $results );
The return value comes from TMDB::Sesssion::talk(), which is just this (or the empty list):
return $self->json->decode(
Encode::decode( 'utf-8-strict', $response->{content} ) );
That's not a big deal. That just means you don't need the outer foreach. That's not on you because the example in the docs tells you to do exactly what you did.
Now a better program
Putting all that together, here's a simple program pared down to just what you need:
use v5.10;
use TMDB;
my $tmdb = TMDB->new( apikey => $ENV{TMDB_API_KEY} );
my $result = $tmdb->search->find(
id => 'tt0114694',
source => 'imdb_id'
);
foreach my $item ( $result->{movie_results}->#* ) {
say "Title: $item->{title}";
}
Ref aliasing
There's an experimental feature called ref aliasing that lets you assign a reference to a reference of a named variable. It's an alias, so you can access and change the same data, just with a named variable. Something this is handy when you don't like
use v5.10;
use TMDB;
use experimental qw(refaliasing);
my $tmdb = TMDB->new( apikey => $ENV{TMDB_API_KEY} );
# response is a hash ref, so ref alias to a named hash
\my %result = $tmdb->search->find(
id => 'tt0114694',
source => 'imdb_id'
);
# the part you want is an array ref, so alias that
\my #movie_results = $result{movie_results};
# each item in the array is a hash ref, so alias those too
foreach \my %item ( #movie_results ) {
say "Title: $item{title}";
}
When dealing with reference, use the same syntax as if you weren't, but replace the name of the variable with a block that returns the reference.
%NAME -> %{ $ref } Or just %$ref
$NAME{...} -> ${ $ref }{...} Although $ref->{...} easier to read.
#NAME -> #{ $ref } Or just #$ref
$NAME[...] -> ${ $ref }[...] Although $ref->[...] easier to read.
Let's give $VAR a better name,
my $response = $VAR1;
This means you want
my $results = $response->{movie_results};
for my $result (#$results) {
for my $key (keys(%$result)) {
say "$key: $result->{$key}";
}
}
See
perlreftut
Perl Dereferencing Syntax
%newhash{$newkey} should be $newhash{$newkey}.

Accessing and displaying key-value pairs in Perl from subroutine

New to Perl. Got syntax errors in accessing key-value pairs from subroutine.
sub displayObj{
my %obj = shift; //the hash. It is a JSON object after decode_json()
my $field = shift; //the key we are searching. It is a string.
my $serialized = "{}"; //Initialization
foreach my $key (keys %obj){
if($key eq $field){
$serialized = $obj[{$field}];
}
}
print "$serialized\n";
}
It is giving me a compilation error in the if block.
I would like to know:
Should I use % or $ in my %obj?
How to access the key-value pair (also a JSON object) and assign it to $serialized using $obj?
I think you're trying to write a subroutine that displays the value of a hash element given its key
But you're missing the basic purpose of hashes: they're content-addressable. That means there's no need to iterate through all the keys of a hash comparing them to the string you're looking for, you can write just $hash{key} and the search will be done for you very quickly using a hashing system (hence the name of the data type). This is just the same as using $array[$i] to access an array element directly instead of looping over all of the indices of the array comparing each one to $i until you find the element you're looking for
If you're really dealing with objects then you shouldn't be accessing their internal data like this anyway. An object will have accessor methods that return the values you're supposed to be using; anything else is part of the internal working of the class and is meant to be private
The syntax error is in this line
$serialized = %obj[{$field}]
where it looks like you're trying to use both a hash key {...} and an array index [...]. That won't work
You don't show how you're calling your subroutine, but I imagine you're passing a reference to a hash, which is a scalar value and must be treated as such inside the subroutine
This program shows a working version of what I think you intended
use strict;
use warnings 'all';
my $obj = {
aa => 1,
cc => 2,
};
displayObj($obj, 'cc');
displayObj($obj, 'bb');
sub displayObj {
my $obj = shift; # The hash. It is a JSON object after decode_json()
my $field = shift; # The key we are searching. It is a string.
my $serialized = '{}'; # Initialization
foreach my $key ( keys %$obj ) {
if ( $key eq $field ) {
$serialized = $obj->{$field};
}
}
print "$serialized\n";
}
output
2
{}
But the loop can be removed altogether as I described, leaving
sub displayObj {
my ($obj, $field) = #_;
my $serialized = $obj->{$field} // '{}';
print "$serialized\n";
}
which produces exactly the same result. In fact there's little point in creating a subroutine to do this; you can write just
print $obj->{bb} // '{}', "\n"
with the same effect
I usually do this way:
sub displayObj{
my $objref = shift;
my %obj = %{$objref};
}

Search duplicates in Hash of Hash

i cant resolv this in my mind, its too much to me, perhaps someone can help me:
#Hosts = ("srv1","db1","srv2","db3","srv3","db3","srv3","db4","srv3","db5");
my $count = #Hosts;
$count= $count / 2;
my %hash;
$i = 0;
$ii = 1;
$j = 0;
$jj = 0;
while ($jj < $count) {
$hash{$j}{$Hosts[$i]} = $Hosts[$ii];
$i = $i + 2;
$ii = $ii +2;
$j++;
$jj++
}
print Dumper(\%hash);
Output:
$VAR1 = {
'4' => {
'srv4' => 'db3'
},
'1' => {
'srv2' => 'db3'
},
'3' => {
'srv3' => 'db3'
},
'0' => {
'srv1' => 'db1'
},
'2' => {
'srv3' => 'db3'
}
'5' => {
'srv3' => 'db5'
}
};
I Know this i ugly code, i dont know how to do that better, what i need to do is find douple servers and douple dbs, and put the positions and the string of the duplicates in an array ore something like that, i want to generate a Nagvis Map file out of that.
The Icinga Config file contains am Member string like this:
members srv1, db1, srv2, db3, srv3, db3, srv3, db3, srv4
It has pairs server, db, server, db, here is a sample of the Nagvis Config:
define host {
object_id=5e78fb
host_name=srv1
x=237
y=122
}
define service {
object_id=30646e
host_name=srv1
service_description=db1
x=52
y=122
}
define host {
object_id=021861
host_name=srv2
x=237
y=217
}
define service {
object_id=a5e725
host_name=srv1
service_description=db2
x=52
y=217
}
Thanks in advance
You need to clarify exactly what you want. It's very difficult to tell by your description.
And, your code is in very poor condition. Indenting loops and if statements like this:
while ($jj < $anzahl) {
$hash{$j}{$Hosts[$i]} = $Hosts[$ii];
$i = $i + 2;
$ii = $ii +2;
$j++;
$jj++
}
Makes your code much easier to understand. You also use generic names. What data is stored in #array? Is it a list of systems. Call it #systems. What is $i and $jj suppose to represent? What do you want $hash{$j}{$Hosts[$i]} to represent?
You should always, always, always add the following lines to the top of your program:
use strict;
use warnings;
If you use strict, you must declare all of your variables with my. This makes sure you don't do things like have #array in one place and #Hosts in another. These two lines will catch about 90% of your errors.
I don't know if you want a list of all the DB system that connect to the various servers or if you want a list of the various servers that connect to the DB systems. Therefore, I'll give you both.
I am guessing that your #array is a list of all of your machines and databases in one list:
use strict;
use warnings;
use feature qw(say); # Allows me to use "say" instead of "print"
use Data::Dumper;
my #systems = qw( # The qw(...) is like putting quotes around each word.
svr1 db1 # A nice way to define an array...
srv2 db3
srv3 db3
srv3 db4
srv3 db5
);
my %db_systems; # Database systems with their servers.
my %servers; # Servers with their database systems.
for (;;) { # Loop forever (until I say otherwise)
my $server = shift #systems;
#
# Let's check to make sure that there's a DB machine for this server
#
if ( not #systems ) {
die qq(Cannot get database for server "$server". Odd number of items in array);
}
my $database = shift #systems;
$servers{$server}->{$database} = 1;
$db_systems{$database}->{$server} = 1;
last if not #systems; # break out of loop if there are no more systems
}
say "Servers:" . Dumper \%servers;
say "Databases: " . Dumper \%db_systems;
This produces:
Servers:$VAR1 = {
'srv3' => {
'db4' => 1,
'db3' => 1,
'db5' => 1
},
'svr1' => {
'db1' => 1
},
'srv2' => {
'db3' => 1
}
};
Databases: $VAR1 = {
'db4' => {
'srv3' => 1
},
'db3' => {
'srv3' => 1,
'srv2' => 1
},
'db5' => {
'srv3' => 1
},
'db1' => {
'svr1' => 1
}
};
Is this close to what you want?
Addendum
Hi this is working!! Now i need to understand how to access the Values to print them in my file. This hash of hash thing is kind off ruff to mee. Thanks for that quick Help!
You need to read the Perl tutorial on References and the Perl Reference Page on References.
In Perl, all data is scalar which means that variables talk about single values. In other programming languages, you have structures or records, but not Perl.
Even arrays and hashes are nothing but collections of individual bits of data. What happens when you need something a bit more complex?
A reference is a memory location of another Perl data structure. You could have references to scalar variables like $foo, but that wouldn't do you much good in most circumstances. Where this is helpful is when you have a reference pointing to an array or a hash. This way, you could have much more complex structures that can be used to represent this data.
Imagine an array of ten items ($foo[0] to $foo[9]). Each entry in the array is pointing to another array of ten items. There are now 101 separate arrays being referenced here. We can treat them as a single structure, but it's important to remember that they are separate arrays.
I have a reference to an array at $foo[0]. How do I get access to the array itself? I do what is known as a dereference. To do that, I use curly braces with the right sigil in front. (The sigil is the $, #, or % you see in front of Perl variables:
$foo[0]; # Reference to an array
my #temp = #{ $foo[0] }; # Dereferencing.
my $temp[0]; # Now I can access that inner array
Having to use a temporary array each time I have to dereference it is rather clumsy, so I don't have to:
$foo[0]; # Reference to an array
my $value = ${ $foo[0] }[0]; # Getting the value of an item in my array reference
You can see that last is a bit hard to read. Imagine if I have a hash of a hash of an array of items:
my $phone = ${ ${ ${ $employee{$emp_number} }{phone} }[0] }{NUMBER};
It's a bit unwieldy. Fortunately, Perl allows you a few shortcuts. First, I can nest the references and use the default precedence:
my $phone = $employee{$emp_number}{phone}[0]{NUMBER};
I prefer using the -> notation:
my $phone = $employee{$emp_number}->{phone}->[0]->{NUMBER};
The arrow notion is cleaner because it separates the parts out, and it reminds you these are references!. and, not some complex structure data structure. This helps remind you when you have to do a dereference such as when you use the key, pop, or push commands:
for my $field ( keys %{ $employee } ) { # Dereference the hash
say "Field $field = " . $employee{$emp_number}->{$field}
if ( not ref $employee{$emp_number}->{$field} );
}
Look up the ref to see what it does and why I am only interested in printing out the field if ref returns an empty string.
By now, you should be able to see how to access your hash of hashes using the -> syntax:
my $db_for_server = $servers{$server}->{$database};
And you can use two loops:
for my $server ( keys %servers } {
my %db_systems = %{ $servers{$server} }; # Dereferencing
for my $db_system ( keys %db_systems } {
say "Server $server has a connection to $db_systems{$db_system}";
}
}
Or, without an intermediate hash...
for my $server { keys %servers } {
for my $db_system ( keys %{ $servers{$server} } ) {
say "Server $server has a connection to " . $servers{$server}->{$db_system};
}
}
Now, go out there and get a good book on Modern Perl. You need to learn good programming techniques like using good variable names, indenting, and using strict and warnings in order to help you write better programs that are easier to decipher and support.

Display data from an array of objects

I'm trying to display data from an array of objects obtained using another company's API, but I am getting errors when I attempt to using a foreach loop.
I'm using Dumper to display everything in the array.
print Dumper($object);
Partial output from Dumper:
'enable_dha_thresholds' => 'false',
'members' => [
bless( {
'ipv4addr' => '192.168.1.67',
'name' => 'name.something.com'
}, 'Something::Network::Member' ),
bless( {
'ipv4addr' => '192.168.1.68',
'name' => 'name.something.com'
}, 'Something::Network::Member' )
],
'comment' => 'This is a comment',
I'm trying to extract the "members" which appears to be a double array:
//this works
print $members->enable_dha_thresholds();
//this works
print $members[0][0]->ipv4addr;
//does not work
foreach my $member ($members[0]){
print "IP". $member->ipv4addr()."\n";
}
I receive this error:
Can't call method "ipv4addr" on unblessed reference at ./script.pl line 12.
I'm not sure I entirely understand "blessed" vs "unblessed" in Perl since I am new to the language.
print $members[0][0]->ipv4addr; //this
works
so $members[0] is an array reference.
You have to dereference the array:
foreach my $member ( #{ $members[0] } ){
print "IP". $member->ipv4addr()."\n";
}
The error refering to an "unblessed reference" tells you you aren't using an object; rather you provide an array-reference, which isn't the same :)
HTH,
Paul
It's an issue of "array reference" vs. "array". $members[0] is an array reference; the foreach operator works with arrays (or lists, to be pedantic). You will want to say
foreach my $member ( #{$members[0]} ) { ...
to iterate over the elements that $members[0] refers to.
The syntax is tricky, and you will probably make a few more mistakes along the way with this stuff. The relevant docs to get you up to speed are in perlref (or perlreftut), perllol, and also perldsc and perlobj.
"blessed" by the way, means that a reference "knows" what kind of object it is and what package it should look in to see what methods it can run. When you get an "unblessed reference" warning or error, that usually means you passed something that was not an object somewhere that expected an object -- in this case, $members[0] is the unblessed reference while you intended to pass the blessed references $members[0][0], $members[0][1], etc.

Testing for different types of hash values in Perl?

I'm writing a small Perl script that goes through an XML file via XML::Simple
my $xml = new XML::Simple;
my $detail= $xml->XMLin($xml_local);
Sometimes, the contents of an element in the XML are empty.
When there is no content in an element in the XML, and I try to print out the contents using:
print $detail->{Parsing}->{Through}->{XML}->{ElementContents}
I get the output:
HASH(0x18948c4)
......or something similar..... the only difference is the chars between the ()'s
I want to test if the content is empty and default the variable to something else - maybe '' or "" - anything but the hash reference/address/whatever that is.
I tried this, but got an error that its not an array reference:
print $detail->{Parsing}->{Through}->{XML}->{ElementContents}[0]
UPDATE
Output of one of the elements using Data::Dumper:
'something' => [
{
'somedetail' => '',
'somedetail' => '',
'somedetail' => 'http://www.google.com'
'somedetail' => 'google',
'somedetail' => '1',
'somedetail' => '01/21/02'
},
How can I test for these '' empty strings using Perl? They are returned as HASH(0x18948c4) unless some filtering is enabled.
The reason it prints HASH(0x18948c4) is because the contents of that value are NOT in fact empty, but a hashref. When you print something, Perl tries to stringify that something, and stringified result of a hash reference is HASH(address) where address is the address of the actual hash.
Print the actual contents of that hashref as follows:
use Data::Dumper;
print Data::Dumper->Dump([$detail->{Parsing}->{Through}->{XML}->{ElementContents}]);
If as you say there are "no contents", it will probably be an empty hashref:
$VAR1 = {};
If so, you can check for it via:
if (ref($detail->{Parsing}->{Through}->{XML}->{ElementContents}) eq ref({})
&& !keys %{ $detail->{Parsing}->{Through}->{XML}->{ElementContents} })
print "No contents, empty hashref";
}
First condition ensures it's a hashref, second, that the hash resulting from its dereference has zero elements as its keys - meaning it's an empty hash being referenced.
However, I seriously doubt it's an empty hash from what I recall about XML::Simple - and doing the Data::Dumper print as shown above will show you HOW to deal with it. You should always print out unknown data structures this way to figure out what to do with them.
E.g., if your Data::Dumper output was:
$VAR1 = {
'a' => 1
};
Then you need to print $detail->{Parsing}->{Through}->{XML}->{ElementContents}->{a}, obviously. Again, be careful to only print something that is a scalar and not an arrayref or hashref, so go down the data structure as much as needed to get to a scalar.
This is a modified version of DVK's answer that worked for me:
if (ref($detail->{Parsing}->{Through}->{XML}->{ElementContents}) eq ref({}))
{
...empty element content...
}
I needed to remove the 2nd condition of the if(condition1 && condition2) statement he gave me.