Perl : parse a file and grab blocks - perl

impossible to parse the file below and grab the blocks in an hash table or simple tab.
I would like to have an hash table with for example
[serv-test] => parent=PRODUCTION.Windows,host=1.1.1.1
Problem is I can delimit the start of a block (with /\[.*\]/) but impossible to delimit the end. The end of my blocks is the start of another.
My file:
authreq=false
default.secured=false
port=3181
protocol=TCP
seclevel=2
secured=false
[serv-test]
parent=PRODUCTION.Windows
host=1.1.1.1
[citrix]
parent=PRODUCTION.Windows
host=1.1.1.2
[cluster-serv]
parent=PRODUCTION.Unix._INFRA
host=1.1.1.3
port=3182

Instead of worrying about getting a hash, be satisfied with getting the data. If you give the top a section name, you have an INI File:
[Default]
authreq=false
default.secured=false
port=3181
protocol=TCP
seclevel=2
secured=false
[serv-test]
parent=PRODUCTION.Windows
host=1.1.1.1
[citrix]
parent=PRODUCTION.Windows
host=1.1.1.2
[cluster-serv]
parent=PRODUCTION.Unix._INFRA
host=1.1.1.3
port=3182
Now you can use Config::IniFiles:
use v5.10;
use Config::IniFiles;
my $cfg = Config::IniFiles->new(
-file => "test.ini"
) or die "#Config::IniFiles::errors";
say "Port is ", $cfg->val( 'Default', 'port' );
say "Cluster host is ", $cfg->val( 'cluster-serv', 'host' );
If you really want the hash, that's not so hard:
use Config::IniFiles;
use Data::Dumper;
my $cfg = Config::IniFiles->new(
-file => "test.ini"
) or die "#Config::IniFiles::errors";
my %hash;
foreach my $section ( $cfg->Sections ) {
foreach my $parameter ( $cfg->Parameters( $section ) ) {
$hash{$section}{$parameter} = $cfg->val( $section, $parameter );
}
}
say Dumper \%hash;
Now you have:
$VAR1 = {
'citrix' => {
'parent' => 'PRODUCTION.Windows',
'host' => '1.1.1.2'
},
'Default' => {
'secured' => 'false',
'port' => '3181',
'protocol' => 'TCP',
'default.secured' => 'false',
'authreq' => 'false',
'seclevel' => '2'
},
'serv-test' => {
'host' => '1.1.1.1',
'parent' => 'PRODUCTION.Windows'
},
'cluster-serv' => {
'port' => '3182',
'parent' => 'PRODUCTION.Unix._INFRA',
'host' => '1.1.1.3'
}
};

Don't reinvent the wheel. There are plenty of existing modules for working with INI-style files, including Config::Tiny, Config::INI, and Config::IniFiles, just to name a few.

Related

Perl DBM::Deep - add/delete in an arrayref of hashrefs

I've been working with DBM::Deep and so far, it's been easy to Read and Update the keys in the DB but when it comes to adding or deleting entities it gets a little complicated and I can't see how it could be done.
I've imported an XML file with XML::Hash and then copied on a DBM::Deep object. So the result is somehow complicated ... The objective of course is to be able to recreate the XML file easily.
So this code:
use DBM::Deep;
use List::Util qw(first);
use Data::Dumper;
my $db = DBM::Deep->new('foo.db');
my $devices = $db->{foo}->{devices}->{device};
(my $match) = grep { $_->{hostname} eq 'myfoo' } #$devices;
print Dumper ($match);
print Dumper($devices);
Gives the following output for the first print:
$VAR1 = bless( {
'enable' => '0',
'hostname' => 'myfoo',
'auth' => 'myauth',
'ip' => 'myip',
'protocol' => 'ssh'
}, 'DBM::Deep::Hash' );
The second print shows:
$VAR1 = bless( [
bless( {
'enable' => '0',
'hostname' => 'myfoo',
'auth' => 'myauth',
'ip' => 'myip',
'protocol' => 'ssh'
}, 'DBM::Deep::Hash' ),
bless( {
'ip' => 'myotherip',
'hostname' => 'myotherfoo',
'auth' => 'myauth',
'protocol' => 'telnet'
}, 'DBM::Deep::Hash' ),
and so on.
Can someone please help me to understand how to Create and Delete in this data structure?

Config::IniFiles hash behaves different than manually written hash

I am loading a config file, which ends up as an embedded hash, with Config::IniFiles. After that, I want to modify the resulting hash by, for some keys, bringing its values one level up. In the example below, I am aiming for this as a result:
$VAR1 = {
'max_childrensubtree' => '7',
'port' => '1984',
'user' => 'someuser',
'password' => 'somepw',
'max_width' => '20',
'host' => 'localhost',
'attrs' => {
'subattr2' => 'cat',
'topattr1' => 'cat',
'subattr2_1' => 'pt',
'subattr1' => 'rel'
},
'max_descendants' => '1000'
};
So for the keys params and basex at the highest level, I want to move its contents (key-value pairs) to the highest level - and remove the items themselves. In short:
(
a => {
'key1' => 'ok',
'key2' => 'hello'
}
)
turns into
(
'key1' => 'ok',
'key2' => 'hello'
)
The strange thing is that what I am trying to do does not work on a hash built from a read INI file, but it does work with a manually inserted hash. In other words, this works:
#!/usr/bin/perl
use utf8;
use strict;
use warnings;
use Data::Dumper;
my %ini = (
'params' => {
'max_width' => '20',
'max_childrensubtree' => '7',
'max_descendants' => '1000'
},
'attrs' => {
'topattr1' => 'cat',
'subattr1' => 'rel',
'subattr2' => 'cat',
'subattr2_1' => 'pt',
},
'basex' => {
'host' => 'localhost',
'port' => '1984',
'user' => 'someuser',
'password' => 'somepw'
}
);
&_parse_ini(\%ini);
sub _parse_ini {
my $ref = shift;
foreach (('params', 'basex')) {
foreach my $k (keys %{$ref->{$_}}) {
$ref->{$k} = $ref->{$_}->{$k};
}
delete $ref->{$_};
}
print Dumper($ref);
}
But this does not:
#!/usr/bin/perl
use utf8;
use strict;
use warnings;
use Data::Dumper;
use Config::IniFiles;
# Load config file
tie my %ini, 'Config::IniFiles', (-file => $ARGV[0]);
&_parse_ini(\%ini);
sub _parse_ini {
my $ref = shift;
foreach (('params', 'basex')) {
foreach my $k (keys %{$ref->{$_}}) {
$ref->{$k} = $ref->{$_}->{$k};
}
delete $ref->{$_};
}
print Dumper($ref);
}
The input ini file for this example would be:
[params]
max_width = 20
max_childrensubtree = 7
max_descendants = 1000
[attrs]
topattr1 = cat
subattr1 = rel
subattr2 = cat
subattr2_1 = pt
[basex]
host = localhost
port = 1984
user = admin
password = admin
I have been looking in the documentation and on SO for similar issues but have found none. It appears that the hashes are identical (Config::IniFiles doesn't seem to add something specific), so I have no idea why it works for 'manual' hashes, and not for read-in ones.
The two hashes are not identical at all, although they may appear to be from the point of view of the data they contain.
The first one is a regular hash. You can do whatever you like with it.
The second one is a tied hash. It becomes an object of Config::IniFiles, but with a hash like interface. So whilst it appears to be a hash, the package can override the methods for storing or fetching information in the hash however it likes.
In this particular case, it looks like Config::IniFiles will only store a new key value in the hash if the value is hash ref. So you can't flatten out the tied hash as you want. Instead you'll have to create a new hash and copy the data in to it to do what you want.

mapping hardcoded config files

I have searched for modules to read config files such as Config, Config::Tiny, Config::Simple. I am not too vague about using those, are there any modules for storing/reading dbi config and usernames/passwords? I have attempted to do this myself, I am wanting to have the config file in a hash data structure for easy importing into my module. Is their an easier way to do what I am attempting or a preferred module that could be suggested?
Example config file:
[database]
db=newsdb
host=example.com
user=test
pass=test
[login]
user=john
pass=doe
Coding:
use strict;
use warnings;
use File::Slurp;
use Data::Dumper;
# get database info
my %conf =
map { /^\[database/ ? () : $_ }
grep { /^\w+.*$/ }
map { s/\s?\n?\r?//g; (split /=/)[0,1] } read_file('database.conf');
print Dumper \%conf;
$VAR1 = {
'pass' => 'test',
'db' => 'newsdb',
'user' => 'test',
'host' => 'example.com'
};
The Config module is not used to read configuration files, it gives detailed information on the configuration of your perl instead.
An easy route here would be to use Config::Simple, and then
Config::Simple->import_from("database.conf" => \my %config);
print Dumper \%config;
Output:
$VAR1 = {
'database.host' => 'example.com',
'login.pass' => 'doe',
'login.user' => 'john',
'database.user' => 'test',
'database.db' => 'newsdb',
'database.pass' => 'test'
};
Alternatively, to access just one block, we could do
my $config = Config::Simple->new("database.conf")->get_block("database");
print Dumper $config;
which would give
$VAR1 = {
'pass' => 'test',
'db' => 'newsdb',
'user' => 'test',
'host' => 'example.com'
};
as output. Read the documentation for more information.
It gets even simpler with Config::Tiny:
my $config = Config::Tiny->read("database.conf");
print Dumper $config;
would give
$VAR1 = bless( {
'database' => {
'pass' => 'test',
'db' => 'newsdb',
'user' => 'test',
'host' => 'example.com'
},
'login' => {
'pass' => 'doe',
'user' => 'john'
}
}, 'Config::Tiny' );
so the database portion could be selected with
print Dumper $config->{database}
which would output
$VAR1 = {
'pass' => 'test',
'db' => 'newsdb',
'user' => 'test',
'host' => 'example.com'
};
You can learn more in the documentation.

Tie Reading and Writing Perl Config Files

I'm using the PerlMonk example I found on:
Reading and Writing Perl Config Files
Configuration.pl:
%CFG = (
'servers' => {
'SRV1' => {
'IP' => 99.32.4.0,
'user' => 'aname',
'pswd' => 'p4ssw0rd',
'status' => 'unavailable'
},
'SRV2' => {
'IP' => 129.99.10.5
'user' => 'guest',
'pswd' => 'guest'
'status' => 'unavailable'
}
},
'timeout' => 60,
'log' => {
'file' => '/var/log/my_log.log',
'level' => 'warn',
},
'temp' => 'remove me'
);
It is working great, but the only issue is when reading and writing the HASH like configuration is being 'out of order'.
Is there a way to keep it TIED?
This important since the configuration file will be also edited manually, so I want the keys and values in the same order.
You could tie config variable before using it, so later hash keys will stay in same order as before,
use strict;
use warnings;
use Tie::IxHash;
tie my %CFG, 'Tie::IxHash';
%CFG = (
'servers' => {
'SRV1' => {
'IP' => '99.32.4.0',
'user' => 'aname',
'pswd' => 'p4ssw0rd',
'status' => 'unavailable'
},
'SRV2' => {
'IP' => '129.99.10.5',
'user' => 'guest',
'pswd' => 'guest',
'status' => 'unavailable'
}
},
'timeout' => 60,
'log' => {
'file' => '/var/log/my_log.log',
'level' => 'warn',
},
'temp' => 'remove me'
);
use Data::Dumper;
print Dumper \%CFG;
If you use JSON then you have the advantage that your software is safe from a malicious attack (or perhaps accidental corruption). JSON also has a simpler syntax than Perl data structures, and it is easier to recover from syntax errors.
Setting the canonical option will create the data with the keys in sorted order, and so generate the same output for the same Perl data every time. If you need the data in a specific order other than alphabetical then you can use the Tie::IxHash module as #mpapec describes in his answer.
Alternatively you can use the sort_by method from the Pure Perl version of the module that lets you pass a collation subroutine. That would let you prescribe the order of your keys, and could be as simple as using a hash that relates all the possible key values with a numerical sort order.
This program uses the sort_by method to reconstruct the JSON in the same order as the keys appear in your original hash. That is unlikely to be the order you want, but the mechanism is there. It works by looking up each key in a hash table to determine how they should be ordered. Any keys (like SVR1 and SVR2 here) that don't appear in the hash are sorted in alphabetical order by default.
use strict;
use warnings;
use JSON::PP ();
my %CFG = (
'servers' => {
'SRV1' => {
'IP' => '99.32.4.0',
'user' => 'aname',
'pswd' => 'p4ssw0rd',
'status' => 'unavailable'
},
'SRV2' => {
'IP' => '129.99.10.5',
'user' => 'guest',
'pswd' => 'guest',
'status' => 'unavailable'
}
},
'timeout' => 60,
'log' => {
'file' => '/var/log/my_log.log',
'level' => 'warn',
},
'temp' => 'remove me'
);
my %sort_order;
my $n = 0;
$sort_order{$_} = ++$n for qw/ servers timeout log temp /;
$sort_order{$_} = ++$n for qw/ IP user pswd status /;
$sort_order{$_} = ++$n for qw/ file level /;
my $json = JSON::PP->new->pretty->sort_by(\&json_sort);
print $json->encode(\%CFG);
sub json_sort {
my ($aa, $bb) = map $sort_order{$_}, $JSON::PP::a, $JSON::PP::b;
$aa and $bb and $aa <=> $bb or $JSON::PP::a cmp $JSON::PP::b;
}
generates this output
{
"servers" : {
"SRV1" : {
"IP" : "99.32.4.0",
"user" : "aname",
"pswd" : "p4ssw0rd",
"status" : "unavailable"
},
"SRV2" : {
"IP" : "129.99.10.5",
"user" : "guest",
"pswd" : "guest",
"status" : "unavailable"
}
},
"timeout" : 60,
"log" : {
"file" : "/var/log/my_log.log",
"level" : "warn"
},
"temp" : "remove me"
}
which can simply be saved to a file and similarly restored.

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.