Perl: Simple INI file info retrieval - perl

I have a perl script that's reading an INI file like this:
[placeholder_title]
Hostname = 127.0.0.1
Port = 161
The library that I'm using for this is Config::Tiny.
Normally when reading the ini file, I would have something like this:
$Config = Config::Tiny->read( 'configfile.ini' );
my $Hostname_property = $Config->{placeholder_title}->{Hostname};
Now I have a case where the section title in the config file is decided by the user, so I don't exactly know what it is.
Before I actually had multiple sections in the config file, so I would iterate through them like this:
foreach my $Section (keys %{$Config}) {
my $Hostname_property = $Config->{$Section}->{Hostname};
my $Port_property = $Config->{$Section}->{Port};
But what if I were to have only 1 section in total?
Is there a particular keyword I can use to substitute for the section name?
I've tried the similar looping logic from the prior example something like this:
$Config = Config::Tiny->read( 'configfile.ini' );
my $Section = keys %{$Config};
my $Hostname_property = $Config->{$Section}->{Hostname};
print $Hostname_property, "\n";
But then I get an error that $Hostname_property is not initialized, so my $Section variable clearly isn't doing what I hoped it to do.
If anybody can help me out or at least point me in the right direction, it would be greatly appreciated.
Thank you.

The reason my $Section = keys %{$Config}; doesn't work is that you're calling keys in scalar context, so it's returning the number of keys. Try calling it in list context instead:
my ($Section) = keys %{$Config};
This will set $Section to the first key. ("first" in whatever order keys is returning the keys in. If there's only one key, that doesn't matter.)

It's okay for a hash to have only one key. Consequently, it's okay if there's only one section in your ini file.
For example, if we have a file called blah.ini with contents of
[title]
foo=bar
blah=baz
and if we run the following code:
use strict;
use warnings;
use Config::Tiny;
my $cfg=Config::Tiny->read("blah.ini");
use Data::Dumper;
print Dumper($cfg) . "\n";
Then we get the output
$VAR1 = bless( {
'title' => {
'blah' => 'baz',
'foo' => 'bar'
}
}, 'Config::Tiny' );
Consequently, we can do something like the following:
use strict;
use warnings;
use Config::Tiny;
my $cfg=Config::Tiny->read("blah.ini");
foreach my $title(sort keys %$cfg)
{
foreach my $setting (sort keys %{$cfg->{$title}})
{
print "title: $title,setting $setting, value $cfg->{$title}->{$setting}\n";
}
}
And the output is
title: title,setting blah, value baz
title: title,setting foo, value bar

Related

How can I work with multiple values using ->?

I am new to Perl and made some changes to an existing script, but I am not sure if this a right usage in Perl. In C# we do things differently, so is the code sample below correct?
$group->{$type}{class} = 1;
The code I added is
$group->{$type}{class} = 1;
$group->{$name}{port} = 1;
Is this right? Can $group point to both type and name. I tried this with a sample Perl script and it seemed to set and return '1' correctly. But I am not sure if this is how I should do this.
Yes, that looks correct. You are building a complex data structure, specifically a hash of hashes (HoH). The group hash has two keys, $type and $name. The $type subhash has one key, class. The $name subhash has one key, port. It looks like this, roughly, if you dumped it or declared it all at once:
$group = {
$type => {
class => 1
},
$name => {
port => 1
}
}
Of course, $type and $name will evaluate to whatever they're set to. It won't store the reference in the hash.
Um ... this is perl. If it works, it's right. But, to your question. In your code $group is a reference to a hash (maybe called a dictionary in c#). I think you are probably looking for this:
my $group={}; # make the ref
my #types = ('hot','cold','warm'); # make some types
my #names = ('sink','bath','drain'); # and some names
foreach my $type (#types){
$group->{'type'}->{$type}++; # add a new $type to the "type" sub hash
}
foreach my $name (#names){
$group->{'name'}->{$name}++; # add a new $nameto the "name" sub hash
}
Now cycle through the types for example:
foreach my $typeKey (keys %{$group->{'type'}}){
print "type is " . $typeKey; # this is from the #types array
print ", value = " . $group->{'type'}->{$typeKey}; # this would be 1
}

How do I add a new Key,Value pair to a Hash in an array of hash in perl?

Hi I have a need to add a new key,value pair to the hash entries within an array of hashes.
Below is some sample code which does not work(simplified with only 1 array entry) The output of the print statement just contains the 1 entry.
my #AoH;
push #AoH, { TEST1 => 'testvalue' };
for my $hash (#AoH)
{
$hash{'TEST2'} = 'testvalue2';
print Dumper($hash);
}
What am I doing wrong?
Thank you.
This code looks a little strange so I am going to assume it was done like that for the purposes of showing it briefly here, but the main thing you need to do to fix your code is change:
$hash{'TEST2'} = 'testvalue2';
to:
$$hash{'TEST2'} = 'testvalue2';
or:
$hash->{'TEST2'} = 'testvalue2';
The extra '$' or '->' dereferences the hash reference '$hash'. Since neither is there, it treats $hash{'TEST2'} as a different variable: '%hash' (not '$hash') and assigns 'testvalue2' to that. You would have gotten a good error message:
Global symbol "%hash" requires explicit package name at - line XX
if you tried to run this code with:
use strict;
use warnings;
at the beginning... which you should always do, so do that every time from now on.
use strict;
use warnings;
use Data::Dumper;
my #AoH=();
my %data_source_hash=(
TEST1 => 'testvalue1',
TEST2 => 'testvalue2'
);
# adds whole hash as the array element
push #AoH,{ %data_source_hash };
print Dumper(#AoH);
#AoH=();
print "---------------------------\n";
# adds each hash $key, $value pair as an element
while ( my ($key, $value) = each %data_source_hash )
{
push #AoH, { $key => $value };
}
print Dumper(#AoH);
#AoH=();
print "---------------------------\n";
# adds extra hash entry to each array element
push #AoH, { TEST1 => 'testvalue' };
push #AoH, { TEST3 => 'testvalue3' };
foreach my $el (#AoH)
{
my $key = 'TEST2';
$$el{$key} = $data_source_hash{$key};
}
print Dumper(#AoH);

Using a : in a variable name in perl

I'm really new to perl and have an issue where I load in XML using XML::Simple and I have the tag names as hash names. I want to get at the hash stored with the name "xsd:schema" but obviously $xsd:schema doesn't work. I've spent ages googling it and cant find how to do it.
How do I get at that hash so I can find out the key values?
Edit:
Sorry I didn't explain myself very well. I want to find out the keys and values of those keys in a hash several levels deep but the name xsd:schema is causing a syntax error:
foreach my $attributes (keys %{ $data{$xsd:schema}{$xsd:element}}){
print "$attributes : ${$data}{$xsd:schema}{$xsd:element}{$attributes}\n";
}
Edit 2:
Here is how I did it.
$schemaData = $data->{'xsd:schema'}->{'xsd:element'}->{'xsd:complexType'}->{'xsd:sequence'}->{'xsd:element'}->{'xsd:complexType'}->{'xsd:sequence'}->{'xsd:element'};
print Dumper($schemaData);
foreach my $fieldName (keys %{ $schemaData}){
$fieldType = $schemaData->{$fieldName}->{'type'};
print "$fieldType\n";
}
Use Data::Dumper to visualize complex data structures.
XML::Simple does not create new variables, it creates a reference. See Perl Data Structures Cookbook.
use Data::Dumper;
use XML::Simple;
my $x = XMLin(q(<r xmlns:xsd="xsd"><xsd:schema atr="a">a</xsd:schema></r>));
print Dumper $x;
print $x->{'xsd:schema'}{content};
Output:
$VAR1 = {
'xsd:schema' => {
'content' => 'a',
'atr' => 'a'
},
'xmlns:xsd' => 'xsd'
};
a

XML::Simple: Parsing nested hashes/array

I am trying to parse an XML file. The Xml file can be found # http://pastebin.com/fvuwbrh9.
I have saved this xml file as packages.xml.
Goal: List all the names which are surrounded by <packagereq> tag in the XML (I am referring the packagereq which fall under the group in the dumper output).
I wrote below script called rpm.pl:
#!/usr/bin/perl -w
use strict;
use XML::Simple;
use Data::Dumper;
my $ref = XMLin ('packages.xml');
#print Dumper ($ref);
foreach my $a ( keys %{ $ref->{group} } )
{
if ( exists $ref->{group}->{$a}->{packagelist} )
{
foreach my $b ( #{ $ref->{group}->{$a}->{packagelist}->{packagereq} } )
{
print $b->{content}."\n"; ### <<< referring the Dumper out put
}
}
}
Now my script goes half way throgh and prints the package names but then it gets terminated with below error:
Not an ARRAY reference at rpm.pl line 29.
After above error, the script does not process rest of the XML file and terminates.
Above error makes me believe that somewhere value of $ref->{group}->{$a}->{packagelist}->{packagereq} is not an ARRAY reference.
I have gone as carefuly as I can throguh the XML file (OR the Dumper output) but found that packagereq always points to an ARRAY reference unless and of course I overlooked something but I doubt so.
Could you provide some input on why is it complaining about Not an ARRAY ref.
Thanks.
XML::Simple, the most complicated XML parser to use. Add the following:
my $ref = XMLin ('packages.xml',
KeyAttr => [qw( id )],
ForceArray => [qw( group packagereq ignoredep )],
);

How do I convert Data::Dumper output back into a Perl data structure?

I was wondering if you could shed some lights regarding the code I've been doing for a couple of days.
I've been trying to convert a Perl-parsed hash back to XML using the XMLout() and XMLin() method and it has been quite successful with this format.
#!/usr/bin/perl -w
use strict;
# use module
use IO::File;
use XML::Simple;
use XML::Dumper;
use Data::Dumper;
my $dump = new XML::Dumper;
my ( $data, $VAR1 );
Topology:$VAR1 = {
'device' => {
'FOC1047Z2SZ' => {
'ChassisID' => '2009-09',
'Error' => undef,
'Group' => {
'ID' => 'A1',
'Type' => 'Base'
},
'Model' => 'CATALYST',
'Name' => 'CISCO-SW1',
'Neighbor' => {},
'ProbedIP' => 'TEST',
'isDerived' => 0
}
},
'issues' => [
'TEST'
]
};
# create object
my $xml = new XML::Simple (NoAttr=>1,
RootName=>'data',
SuppressEmpty => 'true');
# convert Perl array ref into XML document
$data = $xml->XMLout($VAR1);
#reads an XML file
my $X_out = $xml->XMLin($data);
# access XML data
print Dumper($data);
print "STATUS: $X_out->{issues}\n";
print "CHASSIS ID: $X_out->{device}{ChassisID}\n";
print "GROUP ID: $X_out->{device}{Group}{ID}\n";
print "DEVICE NAME: $X_out->{device}{Name}\n";
print "DEVICE NAME: $X_out->{device}{name}\n";
print "ERROR: $X_out->{device}{error}\n";
I can access all the element in the XML with no problem.
But when I try to create a file that will house the parsed hash, problem arises because I can't seem to access all the XML elements. I guess, I wasn't able to unparse the file with the following code.
#!/usr/bin/perl -w
use strict;
#!/usr/bin/perl
# use module
use IO::File;
use XML::Simple;
use XML::Dumper;
use Data::Dumper;
my $dump = new XML::Dumper;
my ( $data, $VAR1, $line_Holder );
#this is the file that contains the parsed hash
my $saveOut = "C:/parsed_hash.txt";
my $result_Holder = IO::File->new($saveOut, 'r');
while ($line_Holder = $result_Holder->getline){
print $line_Holder;
}
# create object
my $xml = new XML::Simple (NoAttr=>1, RootName=>'data', SuppressEmpty => 'true');
# convert Perl array ref into XML document
$data = $xml->XMLout($line_Holder);
#reads an XML file
my $X_out = $xml->XMLin($data);
# access XML data
print Dumper($data);
print "STATUS: $X_out->{issues}\n";
print "CHASSIS ID: $X_out->{device}{ChassisID}\n";
print "GROUP ID: $X_out->{device}{Group}{ID}\n";
print "DEVICE NAME: $X_out->{device}{Name}\n";
print "DEVICE NAME: $X_out->{device}{name}\n";
print "ERROR: $X_out->{device}{error}\n";
Do you have any idea how I could access the $VAR1 inside the text file?
Regards,
newbee_me
$data = $xml->XMLout($line_Holder);
$line_Holder has only the last line of your file, not the whole file, and not the perl hashref that would result from evaling the file. Try something like this:
my $ref = do $saveOut;
The do function loads and evals a file for you. You may want to do it in separate steps, like:
use File::Slurp "read_file";
my $fileContents = read_file( $saveOut );
my $ref = eval( $fileContents );
You might want to look at the Data::Dump module as a replacement for Data::Dumper; its output is already ready to re-eval back.
Basically to load Dumper data you eval() it:
use strict;
use Data::Dumper;
my $x = {"a" => "b", "c"=>[1,2,3],};
my $q = Dumper($x);
$q =~ s{\A\$VAR\d+\s*=\s*}{};
my $w = eval $q;
print $w->{"a"}, "\n";
The regexp (s{\A\$VAR\d+\s*=\s*}{}) is used to remove $VAR1= from the beginning of string.
On the other hand - if you need a way to store complex data structure, and load it again, it's much better to use Storable module, and it's store() and retrieve() functions.
This has worked for me, for hashes of hashes. Perhaps won't work so well with structures which contain references other structures. But works well enough for simple structures, like arrays, hashes, or hashes of hashes.
open(DATA,">",$file);
print DATA Dumper(\%g_write_hash);
close(DATA);
my %g_read_hash = %{ do $file };
Please use dump module as a replacement for Data::Dumper
You can configure the variable name used in Data::Dumper's output with $Data::Dumper::Varname.
Example
use Data::Dumper
$Data::Dumper::Varname = "foo";
my $string = Dumper($object);
eval($string);
...will create the variable $foo, and should contain the same data as $object.
If your data structure is complicated and you have strange results, you may want to consider Storable's freeze() and thaw() methods.