dereferencing a multi-tiered hash in Perl - perl

I cannot dereference the %spec_hash
Is it a multilevel hash?
#!/perl/bin/perl
use FOOConf; #custom module
use Data::Dumper ;
FOOConf::makeDBConnection(production);
use strict;
use warnings;
my $dbh=$EVTConf::dbh;
my $query = "select e_risk_symbol from gsd_etds where level_name='EXCH_CS' and e_exch_dest='XISX' and e_symbol_comment in ('Bin_6','Bin_56')";
if(!$dbh) {
print "Error connecting to DataBase; $DBI::errstr\n";
}
my $cur_msg = $dbh->prepare($query) or die "\n\nCould not prepare statement:".$dbh->errstr;
$cur_msg->execute();
while (my #row=$cur_msg->fetchrow_array) {
$spec_hash{'XISX'}{$row[0]}=1;
}
while(($key,$value) = each(%spech_hash)) {
print $key. "," .$value ;
}
I can see what is going into hash :
#!/perl/bin/perl
use strict;
use warnings ;
use FOOConf; # custom module we use for db access.
FOOConf::makeDBConnection(production); # amkes a database connection.
my $dbh=$EVTConf::dbh;
my $query = "select e_risk_symbol from gsd_etds where level_name='EXCH_CS' and e_exch_dest='XISX' and e_symbol_comment in ('Bin_6','Bin_56')";
if(!$dbh) {
print "Error connecting to DataBase; $DBI::errstr\n";
}
my $cur_msg = $dbh->prepare($query) or die "\n\nCould not prepare statement:".$dbh->errstr;
$cur_msg->execute();
while (my #row=$cur_msg->fetchrow_array) {
foreach $row(#row) {
print "$row ";
}
}
print "\n";
this is what i get ;
MTG GPS WM JBL ISIL MBI BA ILMN FCEL NDAQ CMS HOLX
INTC CYBX STLD MDT CTSH ASBC AMP KLAC LXK X MON
SYY HIG UNM AMGN STZ KMP SONC ECA BEBE EAT PLCE
SPN LAMR PDCO XLP GME CSGP EXC BHP
I can see what is getting loaded in to the $spec_hash by walking through the
#row array, and I can dump the %spec_hash
while (my #row=$cur_msg->fetchrow_array) {
$spec_hash{'XISX'}{$row[0]}=1;
}
print Dumper(%spec_hash)
In the dump is XISX the name of the hash and FCEL the key in hash XISX and the value of key FCEL is 1
foo#fooserver:/tmp/walt $ ./just_db.row.dumper
$VAR1 = 'XISX';
$VAR2 = {
'FCEL' => 1,
'GPS' => 1,
'MCO' => 1,
'DPZ' => 1,
'WM' => 1,
'SPLS' => 1,
'ILMN' => 1,
'BWLD' => 1,
'CTSH' => 1,
'EWU' => 1,
'MDVN' => 1,
'PDCO' => 1,
'AFAM' => 1,
'SHW' => 1,
I just can't dereference
"$spec_hash{'XISX'}{$row[0]}=1;"
there is somthing I don't understand about the way this hash is loaded.

Do this dump to see a better hash structure:
print Dumper(\%spec_hash);
To dereference you need:
while(my($key,$value) = each(%{$spech_hash{'XISX'}})) {

Related

printing out a multilevel hash perl 5

I have to make sense of this script , without making major changes and insulting the guy who made it. I can't change the hashes, even though it would be easy to load data into arrays and then split. The guy who wrote this (my boss) loves his multilevel hashes. I have to print out the %extend_hash multilevel hash - and I don't understand how to get to the last level. I need to print this out in to a CSV file that cane be read by salespeople.
It looks like it goes out like 6 levels.
I have to sort keys of a hash, of a hash, of a hash ...etc.
#!/scripts/perl/bin/perl
use strict;
use warnings;
use DBI;
my $dbUser = 'foo_01';
my $dbPass = 'foo_01';
my $dbSid = 'foo.WORLD';
my $dbh = DBI->connect("dbi:Oracle:$dbSid","$dbUser","$dbPass") or die( "Couldn't connect: $!" );
#sub read_extend
my %extend_hash = ();
my $query = "select level_id,e_risk_symbol,e_exch_dest,penny,specialist from etds_extend";
if(!$dbh) {
print "Error connecting to DataBase; $DBI::errstr\n";
}
my $cur_msg = $dbh->prepare($query) or die "\n\nCould not prepare statement: ".$dbh->errstr;
$cur_msg->execute();
while (my #row=$cur_msg->fetchrow_array) {
$extend_hash{$row[0]}{$row[1]}{$row[2]}{'penny'}=$row[3];
$extend_hash{$row[0]}{$row[1]}{$row[2]}{'specialist'}=$row[4];
}
for my $what_row0 (sort keys %extend_hash) {
for my $what_row1 (sort keys %{$extend_hash {$what_row0} }) {
for my $what_row2 (sort keys ..... I am lost.
I don't know how to print out the %extend_hash down to the lowest level
I am trying to make it comma delimited, able to be pumped into an email and read by salespeople.
6,ACI,ARCX,specialist,1
6,ACI,ARCX,penny,0
6,MCHP,ARCX,specialist,1,
6,MCHP,ARCX,penny,0
6,BC,AMXO,specialist,1
6,BC,AMXO,penny,0
6,WM,XISX,specialist,1
6,WM,XISX,penny,0
6,PK,AMXO,specialist,1
6,PK,AMXO,penny,0
6,SPLS,XISX,specialist,1
6,SPLS,XISX,penny,0
If I use Data::Dumper I get this which is great, but the sales/marketing guys will get confused. They will not be able to see ARCX penny0 in realtions to the group '6'. I don't think they are able to mentally walt through the data::dump
$VAR1 = {
'6' => {
'IACI' => {
'ARCX' => {
'specialist' => '1',
'penny' => '0'
}
},
'MCHP' => {
'ARCX' => {
'specialist' => '1',
'penny' => '0'
}
},
'BC' => {
'AMXO' => {
'specialist' => '1',
'penny' => '0'
}
},
'WM' => {
'XISX' => {
'specialist' => '1',
'penny' => '0'
}
},
'PKD' => {
'AMXO' => {
'specialist' => '1',
'penny' => '0'
}
},
'SPLS' => {
'XISX' => {
'specialist' => '1',
'penny' => '0'
}
}
}
};
update - amazing work I_alarmed_alien - this should be a good stackoverflow reference (my comments are disabled)
for my $level_1 (sort keys %extend_hash) {
for my $level_2 (sort keys %{$extend_hash{$level_1} }) {
for my $level_3 (sort keys %{$extend_hash{$level_1}{$level_2}}) {
for my $type (sort keys %{$extend_hash{$level_1}{$level_2}{$level_3}} ) {
print "$level_1, $level_2, $level_3 $type" . " $extend_hash{$level_1}{$level_2}{$level_3}{$type}" ."\n" ;
}
}
}
}
got it - I_alarmed_alien amazing work outputing a hash of a hash of a hash of a hash of a hash of a hash
6, XLNX, AMXO , specialist 1
6, XLP, AMXO , penny 0
6, XLP, AMXO , specialist 1
6, XLP, XISX , penny 0
6, XLP, XISX , specialist 1
6, XLV, AMXO , penny 0
6, XLV, AMXO , specialist 1
6, XLY, AMXO , penny 0
6, XLY, AMXO , specialist 1
6, YUM, AMXO , penny 0
6, YUM, AMXO , specialist 1
6, ZINC, XISX , penny 0
6, ZINC, XISX , specialist 1
6, ZMH, AMXO , penny 0
6, ZMH, AMXO , specialist 1
to traverse the whole structure and create the comma-separated table (quick and dirty, but working solution):
my $VAR1 = <insert the hash here>
sub printval
{
my ($val, $path) = #_;
if (ref($val) eq "HASH")
{
printval ($val->{$_}, ($path?"$path,":"")."$_") foreach (keys %{$val});
}
else
{
print "$path,$val\n";
}
}
printval($VAR1);
You're almost there with your code. Here is how to get to the bottom of the hash:
foreach my $l1 (keys %extend_hash) {
# '6'
foreach my $l2 (keys %{$extend_hash{$l1}}) {
# IACI, MCHP, BC, etc.
foreach my $l3 (keys %{$extend_hash{$l1}{$l2}}) {
# ARCX, AMXO, XISX, etc.
foreach my $k (keys %{$extend_hash{$l1}{$l2}{$l3}}) {
print "$l1, $l2, $l3, $k, " . $extend_hash{$l1}{$l2}{$l3}{$k} . "\n";
}
}
}
}
Note that the hash keys are not accessed in any particular order, so you may want to sort them -- e.g. foreach my $l1 (sort keys %extend_hash).
Hashes of hashes of hashes of hashes are fun! ;)
ETA: Here is a more generic function for recursing into arbitrarily deep hashes-of-hashes-of-hashes-of...
sub print_hash {
# href = reference to the hash we're examining (i.e. \%extend_hash)
# so_far = arrayref containing the hash keys we are accessing
my $href = shift;
my $so_far = shift;
foreach my $k (keys %$href) {
# put $k on to the array of keys
push #$so_far, $k;
# if $href->{$k} is a reference to another hash, call print_hash on that hash
if (ref($href->{$k}) eq 'HASH') {
print_hash($href->{$k}, $so_far);
} else {
# $href->{$k} is a scalar, so print out #$so_far (our list of hash keys)
# and the value in $href->{$k}
print join(", ", #$so_far, $href->{$k}) . "\n";
}
# we've finished looking at $href->{$k}, so remove $k from the array of keys
pop #$so_far;
}
}
print_hash($hash, []);
if you only want to print it, use Data::Dumper http://perldoc.perl.org/Data/Dumper.html
To check whether a certain variable is a hash reference, use:
if (ref($r) eq "HASH") {
print "r is a reference to a hash.\n";
}
use Data::Dumper;
print Dumper %extend_hash;

Perl populating a hash from an array of hashes

I have a script where I am trying to populate a perl hash
I can dereference them fine when I do it individually
while(my($key,$value) = each(%{$spec_hash{'XISX'}})) {
print $key, "," .$value ;
print "\n";
}
while(my($key,$value) = each(%{$spec_hash{'XCBO'}})) {
print $key, "," .$value ;
print "\n";
}
However when i just try and dereference the %spec_hash It only containst one $exch reference, while it should had two - the XISX and the XCBO.
But it never gets to the XCBO.
#!/sbcimp/dyn/data/EVT/GSD/scripts/perl/bin/perl
use FOOConf; # this is our custom DBI module
use Data::Dumper ;
FOOConf::makeDBConnection(production);
my $dbh=$FOOConf::dbh;
my $query = "select e_risk_symbol from gsd_etds where level_name='EXCH_CS' and e_exch_dest='XISX' and e_symbol_comment in ('Bin_6','Bin_56')";
if(!$dbh) {
print "Error connecting to DataBase; $DBI::errstr\n";
}
my $cur_msg = $dbh->prepare($query) or die "\n\nCould not prepare statement:".$dbh->errstr;
$cur_msg->execute();
while (my #row=$cur_msg->fetchrow_array) {
$spec_hash{'XISX'}{$row[0]}=1;
}
$query = "select e_risk_symbol from gsd_etds where level_name='EXCH_CS' and e_exch_dest='XCBO' and e_combo_type='9999'";
if(!$dbh) {
print "Error connecting to DataBase; $DBI::errstr\n";
}
$cur_msg = $dbh->prepare($query) or die "\n\nCould not prepare statement: ".$dbh->errstr;
$cur_msg->execute();
while (my #row=$cur_msg->fetchrow_array) {
$spec_hash{'XCBO'}{$row[0]}=1;
}
#while(my($key,$value) = each(%spec_hash)) {
# print $key, "," .$value ;
# print "\n";
# }
#
# foreach my $exch (sort keys %spec_hash) {
# print "$exch: $spec_hash{$exch}" ;
# }
print Dumper(\%spec_hash);
this is the dumper - shouldn't the dumper contain the XCBO as well?
Why does the hash only have the XISX elements?
$VAR1 = {
'XISX' => {
'FCEL' => 1,
'GPS' => 1,
'MCO' => 1,
'DPZ' => 1,
'WM' => 1,
'SPLS' => 1,
'ILMN' => 1,
'BWLD' => 1,
'CTSH' => 1,
'EWU' => 1,
'MDVN' => 1,
'PDCO' => 1,
'AFAM' => 1,
'SHW' => 1,
}
};
Are you sure that you are populating it with those values?
Try adding a print statement in the while loop, something like this:
while (my #row=$cur_msg->fetchrow_array) {
$spec_hash{'XCBO'}{$row[0]}=1;
print "DEBUG $row[0]\n";
}
My guess is that your query is not returning any results to add to the hash. Unless I missed something, your other code looks fine.

How to use DBD::CSV to get column names row?

I am using DBD::CSV to show csv data. The code I have is:
#! perl
use strict;
use warnings;
use DBI;
my $dbh = DBI->connect("dbi:CSV:", undef, undef, {
f_dir => ".",
f_ext => ".txt/r",
f_lock => 2,
csv_eol => "\n",
csv_sep_char => "|",
csv_quote_char => '"',
csv_escape_char => '"',
csv_class => "Text::CSV_XS",
csv_null => 1,
csv_tables => {
info => {
file => "countries.txt"
}
},
FetchHashKeyName => "NAME_lc",
}) or die $DBI::errstr;
$dbh->{csv_tables}->{countries} = {
skip_first_row => 0,
col_names => ["a","b","c","d"],
raw_header => 1,
};
my $sth = $dbh->prepare ("select * from countries limit 1");
$sth->execute;
while (my #row = $sth->fetchrow_array) {
print join " ", #row;
print "\n"
}
The countries.txt file is like this:
ISO_COUNTRY|COUNTRY_NAME|REGION_CODE|REGION_NAME
AF|Afghanistan|A|Asia
AX|"Aland Islands"|E|Europe
AL|Albania|E|Europe
But when I ran this script, it returns
AF Afghanistan A Asia
I wanted it to return:
ISO_COUNTRY COUNTRY_NAME REGION_CODE REGION_NAME
Does any one know how to achieve this using DBD::CSV module?
Another question is why the col_names attribute setting didn't take effect?
How to make it return the following?
a b c d
$sth->{NAME}, $sth->{NAME_lc} and $sth->{NAME_uc} return a reference to an array containing the names.
my $sth = $dbh->prepare("select * from countries limit 1");
$sth->execute;
print "$_\n" for #{ $sth->{NAME} };

how to use hashes by calling the value in perl

I'm currently writing a script to check the existance of files reside in several directories. I'm writing in hash and plan to assign same numeric number to those directories that need to perform same subroutines. And thus I'll call by the value names. In the other words, those directories match the value will do same subroutine else it will be dump into a list so that it will be print out in summary later. I'm writing the script as below but it seem doesn't perform correctly as mit seem doesn't capture the value at all. May I know where goes wrong here? Note I want to call the hash by value but not key.
my %hashDir = (dirA => 1, dirB => 2, dirC =>3 , dirD => 1, dirE =>2, dirF =>1);
my $key = "";
my $value = "" ;
my $buf ;
my $d = "$basedir/$buf";
while (($key, $value) = each (%hashDir)) {
if (exists $hashDir{'1'}) {
print "test1\n" ;
subroutine1() ;
} elsif (exists $hashDir{'2'}) {
print "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" ;
subroutine2() ;
} else {
$missingdir .= "\"$buf\" " ;
print "test3\n" ;
}
}
I don't think you understand how to access elements in a hash. When you do exists $hashDir{'1'}, you're looking to see if '1' is a key in the hash. It is not. I think you want to do:
if ($hashDir{$key} == 1)
or since you already have the value,
if ($value == 1)
Use meaningful names rather than $key/$value.
Use a "dispatch table" to decide what subroutine to call.
#!/usr/bin/perl
use warnings;
use strict;
print "Enter path to top-level directory: ";
chomp(my $basedir = <STDIN>);
chdir $basedir or die "could not cd to '$basedir' $!";
my %hashDir = (
dirA => 1,
dirB => 2,
dirC => 3,
dirD => 1,
dirE => 2,
dirF => 1,
);
my %dispatch = (
1 => \&subroutine1,
2 => \&subroutine2,
3 => \&subroutine3,
);
my #missing;
while ( my($dir, $group) = each (%hashDir) ){
if (-d $dir) {
$dispatch{$group}->($dir);
}
else {
push #missing, $dir;
}
}
print 'Missing dirs: ', join(', ', #missing), "\n" if #missing;
sub subroutine1 { warn "subroutine1() got called for $_[0] directory\n" }
sub subroutine2 { warn "subroutine2() got called for $_[0] directory\n" }
sub subroutine3 { warn "subroutine3() got called for $_[0] directory\n" }

Extracting CN's with LDAP?

I have this code
#!/usr/bin/perl
use warnings;
use strict;
use Net::LDAP;
use Data::Dumper;
my $dn="CN=...";
my $password="xxx";
my $ldap = Net::LDAP->new('example.com') or die "$#";
my $mesg = $ldap->bind($dn, password=>$password);
if ($mesg->code) { die "uuuu $mesg"; }
$mesg = $ldap->search(base => "dc=test,dc=example,dc=com", filter => "(name=LIST)",);
my $ref = $mesg->entry->get_value("member", asref => 1);
print Dumper $ref;
foreach my $string (#{$ref}) {
$string =~ /CN=(.+?),.*/;
print $1 . "\n";
}
which outputs the CN's using regular expressions:
aaaa
bbbb
cccc
...
Using Dumper can I see the structure
$VAR1 = [
'CN=aaaa,OU=test,DC=test,DC=example,DC=com',
'CN=bbbb,OU=test,DC=test,DC=example,DC=com',
'CN=cccc,OU=test,DC=test,DC=example,DC=com',
So I am wondering if there is a more "LDAP" way to extract these CN's, instead of using regular expressions?
Update:
Based on Javs answer this is the solution.
my $ref = $mesg->entry->get_value("member", asref => 1);
foreach my $string (#{$ref}) {
print ldap_explode_dn($string)->[0]{CN} . "\n";
}
You can:
use Net::LDAP::Util qw(ldap_explode_dn);
and use it on your attribute like this:
ldap_explode_dn($mesg->entry->get_value('member'));
to get this array of hashes:
$VAR1 = [
{
'CN' => 'aaaa'
},
{
'OU' => 'test'
},
{
'DC' => 'test'
},
{
'DC' => 'example'
},
{
'DC' => 'com'
}
];
You do realize that CN is usually an attribute in LDAP directories?
Why not just query for the attribute CN for all returned objects? Then no parsing required.