Odd number of elements in Perl hash - perl

I'm having some trouble with hashes. I need to get a list of version with key => value pairs into a hash but kept getting an error. The code just below is my recent attempt. Some of the strings have been changed and unneeded code isn't included.
I've looked all over the net but haven't been able to find anything that can help me. I've used Perl for a long time but haven't used hashes and arrays much. Most of my Perl experience has had to do with regex and shell execution. If I was using PHP, I would just use a multidimensional array but this is Perl and there's a lot more to the script I'm writing than what is shown or I'd switch to PHP.
I appreciate whoever takes the time to help. Thanks!
sub sub1 {
# Read file which populates #rows with each line.
my %data;
for (my $i=2; $i <= scalar #rows - 1; $i++) {
$ver =~ s/\s//m;
$data{ $ver } = [
'version', $ver,
'available', $table_tree->cell($jt,1)->as_text,
'bsupport', $table_tree->cell($jt,2)->as_text,
'ssupport', $table_tree->cell($jt,3)->as_text,
'eol', $table_tree->cell($jt,3)->as_text,
'utype', $table_tree->cell($jt,5)->as_text,
'lreleases', $table_tree->cell($jt,7)->as_text
];
};
return %data;
}
sub check_ {
# line 199 follows
my (%hash) = #_;
print Dumper (\%hash)."\n";
}
my %data = sub1($file);
check_(%data);
Warning:
Odd number of elements in hash assignment at ./file.pl line 199 (#1)
(W misc) You specified an odd number of elements to initialize a hash,
which is odd, because hashes come in key/value pairs.
The %hash when dumped is:
$VAR1 = {
'string1' => [
'version',
'string1',
'available',
'stringa',
'bsupport',
'stringb',
'ssupport',
'stringc',
'eol',
'stringd',
'utype',
'stringe',
'lreleases',
'stringf'
],
'string2' => [
'version',
'string2',
'available',
'stringa',
'bsupport',
'stringb',
'ssupport',
'stringc',
'eol',
'stringd',
'utype',
'stringe',
'lreleases',
'stringf'
],
'string3' => [
'version',
'string3',
'available',
'stringa',
'bsupport',
'stringb',
'ssupport',
'stringc',
'eol',
'stringd',
'utype',
'stringe',
'lreleases',
'stringf'
],
# ...
}
I was originally trying to have my has be as follows. Where $VAR1 = { 'stringN' => { ... } would be any number with any number of key => value pairs but would also get the same error. I had it working but it would always generate the error.
$VAR1 = {
'string1' => {
'version' => 'string1',
'available' => 'stringa',
'bsupport' => 'stringb',
'ssupport' => 'stringc',
'eol' => 'stringd',
'utype' => 'stringe',
'lreleases' => 'stringf'
},
'string2' => {
'version' => 'string2',
'available' => 'stringa',
'bsupport' => 'stringb',
'ssupport' => 'stringc',
'eol' => 'stringd',
'utype' => 'stringe',
'lreleases' => 'stringf'
},
'string3' => {
'version' => 'string3',
'available' => 'stringa',
'bsupport' => 'stringb',
'ssupport' => 'stringc',
'eol' => 'stringd',
'utype' => 'stringe',
'lreleases' => 'stringf'
}
# ...
}

It's because you're using [ which is the anonymous array constructor. Try using { instead.
And it might be a bit more idiomatic if you did:
$data{ $ver } = {
version => $jver,
available => $table_tree->cell($jt,1)->as_text,
};
Oh, and indent your code. That for loop doesn't finish where you (might!) think it does. Especially - check where the return happens. (And what $jt is set to - it appears unrelated to $i)

Related

How to address specific array element in complex data structure in perl

I have a complex json data structure in perl like in the following example. I want to address an array element and store data.
Variable
$VAR1 = {
'dummy' => 'foo',
'profiles' => {
'Tags' => [
{
'###PLACEHOLDER###',
}
],
}
I can for example add an element at "###PLACEHOLDER###" but want later in the perl script to add beneath that Placeholder additional information.
Normally i would address these elements with $var->{profiles}->{Tags}->{PLACEHOLDER} but this is not working with an array.
I dont want to create everytime a foreach loop when i know the name exactly.
Any advice?
[UPDATE: used dpathr instead of dpath for the references to structures]
[UPDATE: used dpath instead of dpathr for the references to elements]
Data::DPath can do what you require. Here's code which returns a reference to any structure (hash or array) which contains an element whose value is ###PLACEHOLDER###:
use strict;
use warnings;
use Data::Dumper;
use Data::DPath qw[ dpath dpathr ];
my $struct = {
'dummy' => 'foo',
'profiles' => {
'ARRAY' => [ '###PLACEHOLDER###' ],
'HASH' => { key => '###PLACEHOLDER###' },
},
};
my $path = dpath( '//[value eq "###PLACEHOLDER###"]/..' );
my #matches = $path->match( $struct );
print Dumper \#matches;
It results in:
$VAR1 = [
[
'###PLACEHOLDER###'
],
{
'key' => '###PLACEHOLDER###'
}
];
If you want direct access to the element, change the path to
my $path = dpathr( '//*[value eq "###PLACEHOLDER###"]' );
with the result:
$VAR1 = [
\'###PLACEHOLDER###',
\'###PLACEHOLDER###'
];
It's not clear to me what you what "adding an element at ###PLACEHOLDER###" means. Elements can be added to arrays and hashes, and it's not clear to which array or hash you are referring.
To append an element to the array referenced by $var->{profiles}{Tags}, use
push #{ $var->{profiles}{Tags} }, $val;
This results in
$VAR1 = {
'dummy' => 'foo',
'profiles' => {
'Tags' => [
{
'###PLACEHOLDER###' => undef,
},
$val
],
}
To add an element to the hash referenced by the last element of the array referenced by $var->{profiles}{Tags}, use
$var->{profiles}{Tags}[-1]{$key} = $val;
This results in
$VAR1 = {
'dummy' => 'foo',
'profiles' => {
'Tags' => [
{
'###PLACEHOLDER###' => undef,
$key => $val,
},
],
}
Of course, if $key is ###PLACEHOLDER###, this results in
$VAR1 = {
'dummy' => 'foo',
'profiles' => {
'Tags' => [
{
'###PLACEHOLDER###' => $val,
},
],
}

Perl- Get Hash Value from Multi level hash

I have a 3 dimension hash that I need to extract the data in it. I need to extract the name and vendor under vuln_soft-> prod. So far, I manage to extract the "cve_id" by using the following code:
foreach my $resultHash_entry (keys %hash){
my $cve_id = $hash{$resultHash_entry}{'cve_id'};
}
Can someone please provide a solution on how to extract the name and vendor. Thanks in advance.
%hash = {
'CVE-2015-6929' => {
'cve_id' => 'CVE-2015-6929',
'vuln_soft' => {
'prod' => {
'vendor' => 'win',
'name' => 'win 8.1',
'vers' => {
'vers' => '',
'num' => ''
}
},
'prod' => {
'vendor' => 'win',
'name' => 'win xp',
'vers' => {
'vers' => '',
'num' => ''
}
}
},
'CVE-2015-0616' => {
'cve_id' => 'CVE-2015-0616',
'vuln_soft' => {
'prod' => {
'name' => 'unity_connection',
'vendor' => 'cisco'
}
}
}
}
First, to initialize a hash, you use my %hash = (...); (note the parens, not curly braces). Using {} declares a hash reference, which you have done. You should always use strict; and use warnings;.
To answer the question:
for my $resultHash_entry (keys %hash){
print "$hash{$resultHash_entry}->{vuln_soft}{prod}{name}\n";
print "$hash{$resultHash_entry}->{vuln_soft}{prod}{vendor}\n";
}
...which could be slightly simplified to:
for my $resultHash_entry (keys %hash){
print "$hash{$resultHash_entry}{vuln_soft}{prod}{name}\n";
print "$hash{$resultHash_entry}{vuln_soft}{prod}{vendor}\n";
}
because Perl always knows for certain that any deeper entries than the first one is always a reference, so the deref operator -> isn't needed here.

Possible to detect hash with more than one key?

I am collecting data in a hash of hashes which looks like
$VAR1 = {
'502' => {
'user2' => '0'
},
'501' => {
'git' => '0',
'fffff' => '755'
},
'19197' => {
'user4' => '755'
}
};
The problem is in 501. Two keys may not occur. Is it possible to detect this?
Update
Fixed typo in hash.
If you are only going to store one key-value pair under each key of the main hash, why not use a 2-element array instead? That way you can check for existence before making each new insert, without needing to check the size of the hash or knowing what its keys are. The structure I'm proposing is this:
$VAR1 = {
'502' => [ 'user2', '0' ],
'501' => [ 'git', '0' ],
'19197' => [ 'user4', '755' ]
}
Assuming your hashref above is named $var :
my #bad = grep { scalar keys %{$var->{$_}} > 1 } keys %$var;
Results in an array of hash keys that have more than one hashref within them. Using your data above:
# perl test.pl
$VAR1 = {
'501' => {
'git' => '0',
'fffff' => '755'
},
'502' => {
'user2' => '0'
},
'19197' => {
'user4' => '755'
}
};
$VAR1 = '501';
Then you could access each element that is detected as bad with:
foreach my $key ( #bad ) {
# do something to or with $var->{$key}
}
keys(%{$VAR1{'501'}}) == 2 where the rest would be one.
Also, syntax error on that key, but I assume it's a typo.

How to read data from a hash in Perl?

I have the following XML file:
<?xml version='1.0'?>
<preferences>
<font role="console">
<fname>Courier</fname>
<size>9</size>
</font>
<font role="default">
<fname>Times New Roman</fname>
<size>14</size>
</font>
<font role="titles">
<fname>Helvetica</fname>
<size>10</size>
</font>
</preferences>
I managed to read it and dump it out. Now I am supposed to read all the key value pairs.
Here is the script:
#!/usr/bin/perl
use warnings;
use strict;
# use module
use XML::Simple;
use Data::Dumper;
my $data = XMLin('test.xml');
# print Dumper(%data);
while ( my ($key, $value) = each(%$data) ) {
print "$key => $value\n";
}
Nothing prints inside the loop... What could be the problem? I am new to this and wrote my Hello World script and this all in the same day, so I will take any advice on the code.
This works just fine:
my $data = XMLin('test.xml');
print Dumper($data);
And it gives me:
$VAR1 = {
'font' => [
{
'fname' => 'Courier',
'role' => 'console',
'size' => '9'
},
{
'fname' => 'Times New Roman',
'role' => 'default',
'size' => '14'
},
{
'fname' => 'Helvetica',
'role' => 'titles',
'size' => '10'
}
]
};
I am guessing that inside the while loop I need to loop through each of the arrays. Am I right?
use strict;
Is your friend. It would have told you:
Global symbol "%data" requires explicit package name
What you want is %$data
In other words: $data and %data counts as two different variables.
Update:
As you changed the whole question, my answer makes little sense now.. As does your question. You have printed it. What else do you need?
If you wanted to print that structure, you'd need something like (untested):
for my $key1 (keys %$data) {
for my $array_value (#{ $data->{$key1} }) {
for my $key2 (keys %$array_value) {
print "$key2 => $array_value->{$key2}\n";
}
}
}
If you wanted to access a value directly:
print $data->{font}[0]{'fname'}
You'll need to experiment to get what you need. In the Data::Dumper output, you can easily see which values are hashes and which are arrays:
$VAR1 = { # The curly bracket denotes a beginning hash
'font' => [ # Square bracket = array begins
{ # The first array element is a hash
'fname' => 'Courier', # Inside the hash
'role' => 'console',
'size' => '9'
}, # Hash ends
{ # Next array value, new hash begins
'fname' => 'Times New Roman',
'role' => 'default',
'size' => '14'
},
{
'fname' => 'Helvetica',
'role' => 'titles',
'size' => '10'
}
] # Array ends
}; # Hash ends
Try with:
while ( my ($key, $value) = each(%$data) ) {
....

perl - setting up conditions to find correct key in a hash

Problem:
Seeing exists argument is not a HASH or ARRAY element
Need help setting up several conditions to grab the right key.
Code: (I'm not sure also if my conditions are set up correctly. Need advice troubleshooting)
my $xml = qx(#cmdargs);
my $data = XMLin($xml);
my $size=0;
# checking for error string, if file not found then just exit
# otherwise check the hash keys for filename and get its file size
if (exists $data->{class} =~ /FileNotFound/) {
print "The directory: $Path does not exist\n";
exit;
} elsif (exists $data->{file}->{path}
and $data->{file}->{path} =~/test-out-XXXXX/) {
$size=$data->{file}->{size};
print "FILE SIZE:$size\n";
} else {
# print "Nothing to print.\n";
}
# print "$data";
print Dumper( $data );
My Data:
Data structure for xml file with FileNotFound:
$VAR1 = {
'file' => {},
'path' => '/source/feeds/customer/testA',
'class' => 'java.io.FileNotFoundException',
'message' => '/source/feeds/customer/testA: No such file or directory.'
};
Data structure for xml file found:
$VAR1 = {
'recursive' => 'no',
'version' => '0.20.202.1.1101050227',
'time' => '2011-09-30T02:49:39+0000',
'filter' => '.*',
'file' => {
'owner' => 'test_act',
'replication' => '3',
'blocksize' => '134217728',
'permission' => '-rw-------',
'path' => '/source/feeds/customer/test/test-out-00000',
'modified' => '2011-09-30T02:48:41+0000',
'size' => '135860644',
'group' => '',
'accesstime' => '2011-09-30T02:48:41+0000'
},
The interpreter is probably thinking you meant:
exists($data->{class}=~/FileNotFound/)
Try:
exists $data->{class} and $data->{class}=~/FileNotFound/
instead.